addon-sdk/source/lib/sdk/page-worker.js

changeset 0
6474c204b198
     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 +}

mercurial