|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 "use strict"; |
|
5 |
|
6 const { Ci } = require("chrome"); |
|
7 const { InputPort } = require("./system"); |
|
8 const { getFrameElement, getOuterId, |
|
9 getOwnerBrowserWindow } = require("../window/utils"); |
|
10 const { isnt } = require("../lang/functional"); |
|
11 const { foldp, lift, merges, keepIf } = require("../event/utils"); |
|
12 const { object } = require("../util/sequence"); |
|
13 const { compose } = require("../lang/functional"); |
|
14 const { LastClosed } = require("./browser"); |
|
15 const { patch } = require("diffpatcher/index"); |
|
16 |
|
17 const Document = Ci.nsIDOMDocument; |
|
18 |
|
19 const isntNull = isnt(null); |
|
20 |
|
21 const frameID = frame => frame.id; |
|
22 const browserID = compose(getOuterId, getOwnerBrowserWindow); |
|
23 |
|
24 const isInnerFrame = frame => |
|
25 frame && frame.hasAttribute("data-is-sdk-inner-frame"); |
|
26 |
|
27 // Utility function that given content window loaded in our frame views returns |
|
28 // an actual frame. This basically takes care of fact that actual frame document |
|
29 // is loaded in the nested iframe. If content window is not loaded in the nested |
|
30 // frame of the frame view it returs null. |
|
31 const getFrame = document => |
|
32 document && document.defaultView && getFrameElement(document.defaultView); |
|
33 |
|
34 const FrameInput = function(options) { |
|
35 const input = keepIf(isInnerFrame, null, |
|
36 lift(getFrame, new InputPort(options))); |
|
37 return lift(frame => { |
|
38 if (!frame) return frame; |
|
39 const [id, owner] = [frameID(frame), browserID(frame)]; |
|
40 return object([id, {owners: object([owner, options.update])}]); |
|
41 }, input); |
|
42 }; |
|
43 |
|
44 const LastLoading = new FrameInput({topic: "document-element-inserted", |
|
45 update: {readyState: "loading"}}); |
|
46 exports.LastLoading = LastLoading; |
|
47 |
|
48 const LastInteractive = new FrameInput({topic: "content-document-interactive", |
|
49 update: {readyState: "interactive"}}); |
|
50 exports.LastInteractive = LastInteractive; |
|
51 |
|
52 const LastLoaded = new FrameInput({topic: "content-document-loaded", |
|
53 update: {readyState: "complete"}}); |
|
54 exports.LastLoaded = LastLoaded; |
|
55 |
|
56 const LastUnloaded = new FrameInput({topic: "content-page-hidden", |
|
57 update: null}); |
|
58 exports.LastUnloaded = LastUnloaded; |
|
59 |
|
60 // Represents state of SDK frames in form of data structure: |
|
61 // {"frame#1": {"id": "frame#1", |
|
62 // "inbox": {"data": "ping", |
|
63 // "target": {"id": "frame#1", "owner": "outerWindowID#2"}, |
|
64 // "source": {"id": "frame#1"}} |
|
65 // "url": "resource://addon-1/data/index.html", |
|
66 // "owners": {"outerWindowID#1": {"readyState": "loading"}, |
|
67 // "outerWindowID#2": {"readyState": "complete"}} |
|
68 // |
|
69 // |
|
70 // frame#2: {"id": "frame#2", |
|
71 // "url": "resource://addon-1/data/main.html", |
|
72 // "outbox": {"data": "pong", |
|
73 // "source": {"id": "frame#2", "owner": "outerWindowID#1"} |
|
74 // "target": {"id": "frame#2"}} |
|
75 // "owners": {outerWindowID#1: {readyState: "interacitve"}}}} |
|
76 const Frames = foldp(patch, {}, merges([ |
|
77 LastLoading, |
|
78 LastInteractive, |
|
79 LastLoaded, |
|
80 LastUnloaded, |
|
81 new InputPort({ id: "frame-mailbox" }), |
|
82 new InputPort({ id: "frame-change" }), |
|
83 new InputPort({ id: "frame-changed" }) |
|
84 ])); |
|
85 exports.Frames = Frames; |