1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/page-worker.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,172 @@ 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": "stable" 1.11 +}; 1.12 + 1.13 +const { Class } = require('./core/heritage'); 1.14 +const { on, emit, off, setListeners } = require('./event/core'); 1.15 +const { filter, pipe, map, merge: streamMerge, stripListeners } = require('./event/utils'); 1.16 +const { detach, attach, destroy, WorkerHost } = require('./content/utils'); 1.17 +const { Worker } = require('./content/worker'); 1.18 +const { Disposable } = require('./core/disposable'); 1.19 +const { WeakReference } = require('./core/reference'); 1.20 +const { EventTarget } = require('./event/target'); 1.21 +const { unload } = require('./system/unload'); 1.22 +const { events, streamEventsFrom } = require('./content/events'); 1.23 +const { getAttachEventType } = require('./content/utils'); 1.24 +const { window } = require('./addon/window'); 1.25 +const { getParentWindow } = require('./window/utils'); 1.26 +const { create: makeFrame, getDocShell } = require('./frame/utils'); 1.27 +const { contract } = require('./util/contract'); 1.28 +const { contract: loaderContract } = require('./content/loader'); 1.29 +const { has } = require('./util/array'); 1.30 +const { Rules } = require('./util/rules'); 1.31 +const { merge } = require('./util/object'); 1.32 + 1.33 +const views = WeakMap(); 1.34 +const workers = WeakMap(); 1.35 +const pages = WeakMap(); 1.36 + 1.37 +const readyEventNames = [ 1.38 + 'DOMContentLoaded', 1.39 + 'document-element-inserted', 1.40 + 'load' 1.41 +]; 1.42 + 1.43 +function workerFor(page) workers.get(page) 1.44 +function pageFor(view) pages.get(view) 1.45 +function viewFor(page) views.get(page) 1.46 +function isDisposed (page) !views.get(page, false) 1.47 + 1.48 +let pageContract = contract(merge({ 1.49 + allow: { 1.50 + is: ['object', 'undefined', 'null'], 1.51 + map: function (allow) { return { script: !allow || allow.script !== false }} 1.52 + }, 1.53 + onMessage: { 1.54 + is: ['function', 'undefined'] 1.55 + }, 1.56 + include: { 1.57 + is: ['string', 'array', 'undefined'] 1.58 + }, 1.59 + contentScriptWhen: { 1.60 + is: ['string', 'undefined'] 1.61 + } 1.62 +}, loaderContract.rules)); 1.63 + 1.64 +function enableScript (page) { 1.65 + getDocShell(viewFor(page)).allowJavascript = true; 1.66 +} 1.67 + 1.68 +function disableScript (page) { 1.69 + getDocShell(viewFor(page)).allowJavascript = false; 1.70 +} 1.71 + 1.72 +function Allow (page) { 1.73 + return { 1.74 + get script() { return getDocShell(viewFor(page)).allowJavascript; }, 1.75 + set script(value) { return value ? enableScript(page) : disableScript(page); } 1.76 + }; 1.77 +} 1.78 + 1.79 +function injectWorker ({page}) { 1.80 + let worker = workerFor(page); 1.81 + let view = viewFor(page); 1.82 + if (isValidURL(page, view.contentDocument.URL)) 1.83 + attach(worker, view.contentWindow); 1.84 +} 1.85 + 1.86 +function isValidURL(page, url) !page.rules || page.rules.matchesAny(url) 1.87 + 1.88 +const Page = Class({ 1.89 + implements: [ 1.90 + EventTarget, 1.91 + Disposable, 1.92 + WeakReference 1.93 + ], 1.94 + extends: WorkerHost(workerFor), 1.95 + setup: function Page(options) { 1.96 + let page = this; 1.97 + options = pageContract(options); 1.98 + let view = makeFrame(window.document, { 1.99 + nodeName: 'iframe', 1.100 + type: 'content', 1.101 + uri: options.contentURL, 1.102 + allowJavascript: options.allow.script, 1.103 + allowPlugins: true, 1.104 + allowAuth: true 1.105 + }); 1.106 + 1.107 + ['contentScriptFile', 'contentScript', 'contentScriptWhen'] 1.108 + .forEach(prop => page[prop] = options[prop]); 1.109 + 1.110 + views.set(this, view); 1.111 + pages.set(view, this); 1.112 + 1.113 + // Set listeners on the {Page} object itself, not the underlying worker, 1.114 + // like `onMessage`, as it gets piped 1.115 + setListeners(this, options); 1.116 + let worker = new Worker(stripListeners(options)); 1.117 + workers.set(this, worker); 1.118 + pipe(worker, this); 1.119 + 1.120 + if (this.include || options.include) { 1.121 + this.rules = Rules(); 1.122 + this.rules.add.apply(this.rules, [].concat(this.include || options.include)); 1.123 + } 1.124 + }, 1.125 + get allow() { return Allow(this); }, 1.126 + set allow(value) { 1.127 + let allowJavascript = pageContract({ allow: value }).allow.script; 1.128 + return allowJavascript ? enableScript(this) : disableScript(this); 1.129 + }, 1.130 + get contentURL() { return viewFor(this).getAttribute('src'); }, 1.131 + set contentURL(value) { 1.132 + if (!isValidURL(this, value)) return; 1.133 + let view = viewFor(this); 1.134 + let contentURL = pageContract({ contentURL: value }).contentURL; 1.135 + view.setAttribute('src', contentURL); 1.136 + }, 1.137 + dispose: function () { 1.138 + if (isDisposed(this)) return; 1.139 + let view = viewFor(this); 1.140 + if (view.parentNode) view.parentNode.removeChild(view); 1.141 + views.delete(this); 1.142 + destroy(workers.get(this)); 1.143 + }, 1.144 + toString: function () { return '[object Page]' } 1.145 +}); 1.146 + 1.147 +exports.Page = Page; 1.148 + 1.149 +let pageEvents = streamMerge([events, streamEventsFrom(window)]); 1.150 +let readyEvents = filter(pageEvents, isReadyEvent); 1.151 +let formattedEvents = map(readyEvents, function({target, type}) { 1.152 + return { type: type, page: pageFromDoc(target) }; 1.153 +}); 1.154 +let pageReadyEvents = filter(formattedEvents, function({page, type}) { 1.155 + return getAttachEventType(page) === type}); 1.156 +on(pageReadyEvents, 'data', injectWorker); 1.157 + 1.158 +function isReadyEvent ({type}) { 1.159 + return has(readyEventNames, type); 1.160 +} 1.161 + 1.162 +/* 1.163 + * Takes a document, finds its doc shell tree root and returns the 1.164 + * matching Page instance if found 1.165 + */ 1.166 +function pageFromDoc(doc) { 1.167 + let parentWindow = getParentWindow(doc.defaultView), page; 1.168 + if (!parentWindow) return; 1.169 + 1.170 + let frames = parentWindow.document.getElementsByTagName('iframe'); 1.171 + for (let i = frames.length; i--;) 1.172 + if (frames[i].contentDocument === doc && (page = pageFor(frames[i]))) 1.173 + return page; 1.174 + return null; 1.175 +}