1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/ui/frame/view.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,144 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 +* License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +"use strict"; 1.8 + 1.9 +module.metadata = { 1.10 + "stability": "experimental", 1.11 + "engines": { 1.12 + "Firefox": "> 28" 1.13 + } 1.14 +}; 1.15 + 1.16 +const { Cu, Ci } = require("chrome"); 1.17 +const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); 1.18 +const { subscribe, send, Reactor, foldp, lift, merges, keepIf } = require("../../event/utils"); 1.19 +const { InputPort } = require("../../input/system"); 1.20 +const { OutputPort } = require("../../output/system"); 1.21 +const { LastClosed } = require("../../input/browser"); 1.22 +const { pairs, keys, object, each } = require("../../util/sequence"); 1.23 +const { curry, compose } = require("../../lang/functional"); 1.24 +const { getFrameElement, getOuterId, 1.25 + getByOuterId, getOwnerBrowserWindow } = require("../../window/utils"); 1.26 +const { patch, diff } = require("diffpatcher/index"); 1.27 +const { encode } = require("../../base64"); 1.28 +const { Frames } = require("../../input/frame"); 1.29 + 1.30 +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 1.31 +const HTML_NS = "http://www.w3.org/1999/xhtml"; 1.32 +const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html"); 1.33 + 1.34 +const mailbox = new OutputPort({ id: "frame-mailbox" }); 1.35 + 1.36 +const frameID = frame => frame.id.replace("outer-", ""); 1.37 +const windowID = compose(getOuterId, getOwnerBrowserWindow); 1.38 + 1.39 +const getOuterFrame = (windowID, frameID) => 1.40 + getByOuterId(windowID).document.getElementById("outer-" + frameID); 1.41 + 1.42 +const listener = ({target, source, data, origin, timeStamp}) => { 1.43 + // And sent received message to outbox so that frame API model 1.44 + // will deal with it. 1.45 + if (source && source !== target) { 1.46 + const frame = getFrameElement(target); 1.47 + const id = frameID(frame); 1.48 + send(mailbox, object([id, { 1.49 + outbox: {type: "message", 1.50 + source: {id: id, ownerID: windowID(frame)}, 1.51 + data: data, 1.52 + origin: origin, 1.53 + timeStamp: timeStamp}}])); 1.54 + } 1.55 +}; 1.56 + 1.57 +// Utility function used to create frame with a given `state` and 1.58 +// inject it into given `window`. 1.59 +const registerFrame = ({id, url}) => { 1.60 + CustomizableUI.createWidget({ 1.61 + id: id, 1.62 + type: "custom", 1.63 + removable: true, 1.64 + onBuild: document => { 1.65 + let view = document.createElementNS(XUL_NS, "toolbaritem"); 1.66 + view.setAttribute("id", id); 1.67 + view.setAttribute("flex", 2); 1.68 + 1.69 + let innerFrame = document.createElementNS(HTML_NS, "iframe"); 1.70 + innerFrame.setAttribute("id", id); 1.71 + innerFrame.setAttribute("src", url); 1.72 + innerFrame.setAttribute("seamless", "seamless"); 1.73 + innerFrame.setAttribute("sandbox", "allow-scripts"); 1.74 + innerFrame.setAttribute("scrolling", "no"); 1.75 + innerFrame.setAttribute("data-is-sdk-inner-frame", true); 1.76 + innerFrame.setAttribute("style", [ "border:none", 1.77 + "position:absolute", "width:100%", "top: 0", 1.78 + "left: 0", "overflow: hidden"].join(";")); 1.79 + 1.80 + let outerFrame = document.createElementNS(XUL_NS, "iframe"); 1.81 + outerFrame.setAttribute("src", OUTER_FRAME_URI + "#" + 1.82 + encode(innerFrame.outerHTML)); 1.83 + outerFrame.setAttribute("id", "outer-" + id); 1.84 + outerFrame.setAttribute("data-is-sdk-outer-frame", true); 1.85 + outerFrame.setAttribute("type", "content"); 1.86 + outerFrame.setAttribute("transparent", true); 1.87 + outerFrame.setAttribute("flex", 2); 1.88 + outerFrame.setAttribute("style", "overflow: hidden;"); 1.89 + outerFrame.setAttribute("scrolling", "no"); 1.90 + outerFrame.setAttribute("disablehistory", true); 1.91 + outerFrame.setAttribute("seamless", "seamless"); 1.92 + 1.93 + view.appendChild(outerFrame); 1.94 + 1.95 + return view; 1.96 + } 1.97 + }); 1.98 +}; 1.99 + 1.100 +const unregisterFrame = CustomizableUI.destroyWidget; 1.101 + 1.102 +const deliverMessage = curry((frameID, data, windowID) => { 1.103 + const frame = getOuterFrame(windowID, frameID); 1.104 + const content = frame && frame.contentWindow; 1.105 + 1.106 + if (content) 1.107 + content.postMessage(data, content.location.origin); 1.108 +}); 1.109 + 1.110 +const updateFrame = (id, {inbox, owners}, present) => { 1.111 + if (inbox) { 1.112 + const { data, target:{ownerID}, source } = present[id].inbox; 1.113 + if (ownerID) 1.114 + deliverMessage(id, data, ownerID); 1.115 + else 1.116 + each(deliverMessage(id, data), keys(present[id].owners)); 1.117 + } 1.118 + 1.119 + each(setupView(id), pairs(owners)); 1.120 +}; 1.121 + 1.122 +const setupView = curry((frameID, [windowID, state]) => { 1.123 + if (state && state.readyState === "loading") { 1.124 + const frame = getOuterFrame(windowID, frameID); 1.125 + // Setup a message listener on contentWindow. 1.126 + frame.contentWindow.addEventListener("message", listener); 1.127 + } 1.128 +}); 1.129 + 1.130 + 1.131 +const reactor = new Reactor({ 1.132 + onStep: (present, past) => { 1.133 + const delta = diff(past, present); 1.134 + 1.135 + // Apply frame changes 1.136 + each(([id, update]) => { 1.137 + if (update === null) 1.138 + unregisterFrame(id); 1.139 + else if (past[id]) 1.140 + updateFrame(id, update, present); 1.141 + else 1.142 + registerFrame(update); 1.143 + }, pairs(delta)); 1.144 + }, 1.145 + onEnd: state => each(unregisterFrame, keys(state)) 1.146 +}); 1.147 +reactor.run(Frames);