michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "experimental", michael@0: "engines": { michael@0: "Firefox": "> 28" michael@0: } michael@0: }; michael@0: michael@0: const { Cu, Ci } = require("chrome"); michael@0: const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); michael@0: const { subscribe, send, Reactor, foldp, lift, merges, keepIf } = require("../../event/utils"); michael@0: const { InputPort } = require("../../input/system"); michael@0: const { OutputPort } = require("../../output/system"); michael@0: const { LastClosed } = require("../../input/browser"); michael@0: const { pairs, keys, object, each } = require("../../util/sequence"); michael@0: const { curry, compose } = require("../../lang/functional"); michael@0: const { getFrameElement, getOuterId, michael@0: getByOuterId, getOwnerBrowserWindow } = require("../../window/utils"); michael@0: const { patch, diff } = require("diffpatcher/index"); michael@0: const { encode } = require("../../base64"); michael@0: const { Frames } = require("../../input/frame"); michael@0: michael@0: const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: const HTML_NS = "http://www.w3.org/1999/xhtml"; michael@0: const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html"); michael@0: michael@0: const mailbox = new OutputPort({ id: "frame-mailbox" }); michael@0: michael@0: const frameID = frame => frame.id.replace("outer-", ""); michael@0: const windowID = compose(getOuterId, getOwnerBrowserWindow); michael@0: michael@0: const getOuterFrame = (windowID, frameID) => michael@0: getByOuterId(windowID).document.getElementById("outer-" + frameID); michael@0: michael@0: const listener = ({target, source, data, origin, timeStamp}) => { michael@0: // And sent received message to outbox so that frame API model michael@0: // will deal with it. michael@0: if (source && source !== target) { michael@0: const frame = getFrameElement(target); michael@0: const id = frameID(frame); michael@0: send(mailbox, object([id, { michael@0: outbox: {type: "message", michael@0: source: {id: id, ownerID: windowID(frame)}, michael@0: data: data, michael@0: origin: origin, michael@0: timeStamp: timeStamp}}])); michael@0: } michael@0: }; michael@0: michael@0: // Utility function used to create frame with a given `state` and michael@0: // inject it into given `window`. michael@0: const registerFrame = ({id, url}) => { michael@0: CustomizableUI.createWidget({ michael@0: id: id, michael@0: type: "custom", michael@0: removable: true, michael@0: onBuild: document => { michael@0: let view = document.createElementNS(XUL_NS, "toolbaritem"); michael@0: view.setAttribute("id", id); michael@0: view.setAttribute("flex", 2); michael@0: michael@0: let innerFrame = document.createElementNS(HTML_NS, "iframe"); michael@0: innerFrame.setAttribute("id", id); michael@0: innerFrame.setAttribute("src", url); michael@0: innerFrame.setAttribute("seamless", "seamless"); michael@0: innerFrame.setAttribute("sandbox", "allow-scripts"); michael@0: innerFrame.setAttribute("scrolling", "no"); michael@0: innerFrame.setAttribute("data-is-sdk-inner-frame", true); michael@0: innerFrame.setAttribute("style", [ "border:none", michael@0: "position:absolute", "width:100%", "top: 0", michael@0: "left: 0", "overflow: hidden"].join(";")); michael@0: michael@0: let outerFrame = document.createElementNS(XUL_NS, "iframe"); michael@0: outerFrame.setAttribute("src", OUTER_FRAME_URI + "#" + michael@0: encode(innerFrame.outerHTML)); michael@0: outerFrame.setAttribute("id", "outer-" + id); michael@0: outerFrame.setAttribute("data-is-sdk-outer-frame", true); michael@0: outerFrame.setAttribute("type", "content"); michael@0: outerFrame.setAttribute("transparent", true); michael@0: outerFrame.setAttribute("flex", 2); michael@0: outerFrame.setAttribute("style", "overflow: hidden;"); michael@0: outerFrame.setAttribute("scrolling", "no"); michael@0: outerFrame.setAttribute("disablehistory", true); michael@0: outerFrame.setAttribute("seamless", "seamless"); michael@0: michael@0: view.appendChild(outerFrame); michael@0: michael@0: return view; michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: const unregisterFrame = CustomizableUI.destroyWidget; michael@0: michael@0: const deliverMessage = curry((frameID, data, windowID) => { michael@0: const frame = getOuterFrame(windowID, frameID); michael@0: const content = frame && frame.contentWindow; michael@0: michael@0: if (content) michael@0: content.postMessage(data, content.location.origin); michael@0: }); michael@0: michael@0: const updateFrame = (id, {inbox, owners}, present) => { michael@0: if (inbox) { michael@0: const { data, target:{ownerID}, source } = present[id].inbox; michael@0: if (ownerID) michael@0: deliverMessage(id, data, ownerID); michael@0: else michael@0: each(deliverMessage(id, data), keys(present[id].owners)); michael@0: } michael@0: michael@0: each(setupView(id), pairs(owners)); michael@0: }; michael@0: michael@0: const setupView = curry((frameID, [windowID, state]) => { michael@0: if (state && state.readyState === "loading") { michael@0: const frame = getOuterFrame(windowID, frameID); michael@0: // Setup a message listener on contentWindow. michael@0: frame.contentWindow.addEventListener("message", listener); michael@0: } michael@0: }); michael@0: michael@0: michael@0: const reactor = new Reactor({ michael@0: onStep: (present, past) => { michael@0: const delta = diff(past, present); michael@0: michael@0: // Apply frame changes michael@0: each(([id, update]) => { michael@0: if (update === null) michael@0: unregisterFrame(id); michael@0: else if (past[id]) michael@0: updateFrame(id, update, present); michael@0: else michael@0: registerFrame(update); michael@0: }, pairs(delta)); michael@0: }, michael@0: onEnd: state => each(unregisterFrame, keys(state)) michael@0: }); michael@0: reactor.run(Frames);