|
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 module.metadata = { |
|
7 "stability": "experimental", |
|
8 "engines": { |
|
9 "Firefox": "> 28" |
|
10 } |
|
11 }; |
|
12 |
|
13 const { Cu, Ci } = require("chrome"); |
|
14 const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); |
|
15 const { subscribe, send, Reactor, foldp, lift, merges, keepIf } = require("../../event/utils"); |
|
16 const { InputPort } = require("../../input/system"); |
|
17 const { OutputPort } = require("../../output/system"); |
|
18 const { LastClosed } = require("../../input/browser"); |
|
19 const { pairs, keys, object, each } = require("../../util/sequence"); |
|
20 const { curry, compose } = require("../../lang/functional"); |
|
21 const { getFrameElement, getOuterId, |
|
22 getByOuterId, getOwnerBrowserWindow } = require("../../window/utils"); |
|
23 const { patch, diff } = require("diffpatcher/index"); |
|
24 const { encode } = require("../../base64"); |
|
25 const { Frames } = require("../../input/frame"); |
|
26 |
|
27 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
28 const HTML_NS = "http://www.w3.org/1999/xhtml"; |
|
29 const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html"); |
|
30 |
|
31 const mailbox = new OutputPort({ id: "frame-mailbox" }); |
|
32 |
|
33 const frameID = frame => frame.id.replace("outer-", ""); |
|
34 const windowID = compose(getOuterId, getOwnerBrowserWindow); |
|
35 |
|
36 const getOuterFrame = (windowID, frameID) => |
|
37 getByOuterId(windowID).document.getElementById("outer-" + frameID); |
|
38 |
|
39 const listener = ({target, source, data, origin, timeStamp}) => { |
|
40 // And sent received message to outbox so that frame API model |
|
41 // will deal with it. |
|
42 if (source && source !== target) { |
|
43 const frame = getFrameElement(target); |
|
44 const id = frameID(frame); |
|
45 send(mailbox, object([id, { |
|
46 outbox: {type: "message", |
|
47 source: {id: id, ownerID: windowID(frame)}, |
|
48 data: data, |
|
49 origin: origin, |
|
50 timeStamp: timeStamp}}])); |
|
51 } |
|
52 }; |
|
53 |
|
54 // Utility function used to create frame with a given `state` and |
|
55 // inject it into given `window`. |
|
56 const registerFrame = ({id, url}) => { |
|
57 CustomizableUI.createWidget({ |
|
58 id: id, |
|
59 type: "custom", |
|
60 removable: true, |
|
61 onBuild: document => { |
|
62 let view = document.createElementNS(XUL_NS, "toolbaritem"); |
|
63 view.setAttribute("id", id); |
|
64 view.setAttribute("flex", 2); |
|
65 |
|
66 let innerFrame = document.createElementNS(HTML_NS, "iframe"); |
|
67 innerFrame.setAttribute("id", id); |
|
68 innerFrame.setAttribute("src", url); |
|
69 innerFrame.setAttribute("seamless", "seamless"); |
|
70 innerFrame.setAttribute("sandbox", "allow-scripts"); |
|
71 innerFrame.setAttribute("scrolling", "no"); |
|
72 innerFrame.setAttribute("data-is-sdk-inner-frame", true); |
|
73 innerFrame.setAttribute("style", [ "border:none", |
|
74 "position:absolute", "width:100%", "top: 0", |
|
75 "left: 0", "overflow: hidden"].join(";")); |
|
76 |
|
77 let outerFrame = document.createElementNS(XUL_NS, "iframe"); |
|
78 outerFrame.setAttribute("src", OUTER_FRAME_URI + "#" + |
|
79 encode(innerFrame.outerHTML)); |
|
80 outerFrame.setAttribute("id", "outer-" + id); |
|
81 outerFrame.setAttribute("data-is-sdk-outer-frame", true); |
|
82 outerFrame.setAttribute("type", "content"); |
|
83 outerFrame.setAttribute("transparent", true); |
|
84 outerFrame.setAttribute("flex", 2); |
|
85 outerFrame.setAttribute("style", "overflow: hidden;"); |
|
86 outerFrame.setAttribute("scrolling", "no"); |
|
87 outerFrame.setAttribute("disablehistory", true); |
|
88 outerFrame.setAttribute("seamless", "seamless"); |
|
89 |
|
90 view.appendChild(outerFrame); |
|
91 |
|
92 return view; |
|
93 } |
|
94 }); |
|
95 }; |
|
96 |
|
97 const unregisterFrame = CustomizableUI.destroyWidget; |
|
98 |
|
99 const deliverMessage = curry((frameID, data, windowID) => { |
|
100 const frame = getOuterFrame(windowID, frameID); |
|
101 const content = frame && frame.contentWindow; |
|
102 |
|
103 if (content) |
|
104 content.postMessage(data, content.location.origin); |
|
105 }); |
|
106 |
|
107 const updateFrame = (id, {inbox, owners}, present) => { |
|
108 if (inbox) { |
|
109 const { data, target:{ownerID}, source } = present[id].inbox; |
|
110 if (ownerID) |
|
111 deliverMessage(id, data, ownerID); |
|
112 else |
|
113 each(deliverMessage(id, data), keys(present[id].owners)); |
|
114 } |
|
115 |
|
116 each(setupView(id), pairs(owners)); |
|
117 }; |
|
118 |
|
119 const setupView = curry((frameID, [windowID, state]) => { |
|
120 if (state && state.readyState === "loading") { |
|
121 const frame = getOuterFrame(windowID, frameID); |
|
122 // Setup a message listener on contentWindow. |
|
123 frame.contentWindow.addEventListener("message", listener); |
|
124 } |
|
125 }); |
|
126 |
|
127 |
|
128 const reactor = new Reactor({ |
|
129 onStep: (present, past) => { |
|
130 const delta = diff(past, present); |
|
131 |
|
132 // Apply frame changes |
|
133 each(([id, update]) => { |
|
134 if (update === null) |
|
135 unregisterFrame(id); |
|
136 else if (past[id]) |
|
137 updateFrame(id, update, present); |
|
138 else |
|
139 registerFrame(update); |
|
140 }, pairs(delta)); |
|
141 }, |
|
142 onEnd: state => each(unregisterFrame, keys(state)) |
|
143 }); |
|
144 reactor.run(Frames); |