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

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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";
     6 module.metadata = {
     7   "stability": "stable"
     8 };
    10 const observers = require('./system/events');
    11 const { contract: loaderContract } = require('./content/loader');
    12 const { contract } = require('./util/contract');
    13 const { getAttachEventType, WorkerHost } = require('./content/utils');
    14 const { Class } = require('./core/heritage');
    15 const { Disposable } = require('./core/disposable');
    16 const { WeakReference } = require('./core/reference');
    17 const { Worker } = require('./content/worker');
    18 const { EventTarget } = require('./event/target');
    19 const { on, emit, once, setListeners } = require('./event/core');
    20 const { on: domOn, removeListener: domOff } = require('./dom/events');
    21 const { pipe } = require('./event/utils');
    22 const { isRegExp } = require('./lang/type');
    23 const { merge } = require('./util/object');
    24 const { windowIterator } = require('./deprecated/window-utils');
    25 const { isBrowser, getFrames } = require('./window/utils');
    26 const { getTabs, getTabContentWindow, getTabForContentWindow,
    27         getURI: getTabURI } = require('./tabs/utils');
    28 const { ignoreWindow } = require('sdk/private-browsing/utils');
    29 const { Style } = require("./stylesheet/style");
    30 const { attach, detach } = require("./content/mod");
    31 const { has, hasAny } = require("./util/array");
    32 const { Rules } = require("./util/rules");
    33 const { List, addListItem, removeListItem } = require('./util/list');
    34 const { when: unload } = require("./system/unload");
    36 // Valid values for `attachTo` option
    37 const VALID_ATTACHTO_OPTIONS = ['existing', 'top', 'frame'];
    39 const pagemods = new Set();
    40 const workers = new WeakMap();
    41 const styles = new WeakMap();
    42 const models = new WeakMap();
    43 let modelFor = (mod) => models.get(mod);
    44 let workerFor = (mod) => workers.get(mod);
    45 let styleFor = (mod) => styles.get(mod);
    47 // Bind observer
    48 observers.on('document-element-inserted', onContentWindow);
    49 unload(() => observers.off('document-element-inserted', onContentWindow));
    51 let isRegExpOrString = (v) => isRegExp(v) || typeof v === 'string';
    53 // Validation Contracts
    54 const modOptions = {
    55   // contentStyle* / contentScript* are sharing the same validation constraints,
    56   // so they can be mostly reused, except for the messages.
    57   contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
    58     msg: 'The `contentStyle` option must be a string or an array of strings.'
    59   }),
    60   contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
    61     msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
    62   }),
    63   include: {
    64     is: ['string', 'array', 'regexp'],
    65     ok: (rule) => {
    66       if (isRegExpOrString(rule))
    67         return true;
    68       if (Array.isArray(rule) && rule.length > 0)
    69         return rule.every(isRegExpOrString);
    70       return false;
    71     },
    72     msg: 'The `include` option must always contain atleast one rule as a string, regular expression, or an array of strings and regular expressions.'
    73   },
    74   attachTo: {
    75     is: ['string', 'array', 'undefined'],
    76     map: function (attachTo) {
    77       if (!attachTo) return ['top', 'frame'];
    78       if (typeof attachTo === 'string') return [attachTo];
    79       return attachTo;
    80     },
    81     ok: function (attachTo) {
    82       return hasAny(attachTo, ['top', 'frame']) &&
    83         attachTo.every(has.bind(null, ['top', 'frame', 'existing']));
    84     },
    85     msg: 'The `attachTo` option must be a string or an array of strings. ' +
    86       'The only valid options are "existing", "top" and "frame", and must ' +
    87       'contain at least "top" or "frame" values.'
    88   },
    89 };
    91 const modContract = contract(merge({}, loaderContract.rules, modOptions));
    93 /**
    94  * PageMod constructor (exported below).
    95  * @constructor
    96  */
    97 const PageMod = Class({
    98   implements: [
    99     modContract.properties(modelFor),
   100     EventTarget,
   101     Disposable,
   102     WeakReference
   103   ],
   104   extends: WorkerHost(workerFor),
   105   setup: function PageMod(options) {
   106     let mod = this;
   107     let model = modContract(options);
   108     models.set(this, model);
   110     // Set listeners on {PageMod} itself, not the underlying worker,
   111     // like `onMessage`, as it'll get piped.
   112     setListeners(this, options);
   114     let include = model.include;
   115     model.include = Rules();
   116     model.include.add.apply(model.include, [].concat(include));
   118     if (model.contentStyle || model.contentStyleFile) {
   119       styles.set(mod, Style({
   120         uri: model.contentStyleFile,
   121         source: model.contentStyle
   122       }));
   123     }
   125     pagemods.add(this);
   127     // `applyOnExistingDocuments` has to be called after `pagemods.add()`
   128     // otherwise its calls to `onContent` method won't do anything.
   129     if (has(model.attachTo, 'existing'))
   130       applyOnExistingDocuments(mod);
   131   },
   133   dispose: function() {
   134     let style = styleFor(this);
   135     if (style)
   136       detach(style);
   138     for (let i in this.include)
   139       this.include.remove(this.include[i]);
   141     pagemods.delete(this);
   142   }
   143 });
   144 exports.PageMod = PageMod;
   146 function onContentWindow({ subject: document }) {
   147   // Return if we have no pagemods
   148   if (pagemods.size === 0)
   149     return;
   151   let window = document.defaultView;
   152   // XML documents don't have windows, and we don't yet support them.
   153   if (!window)
   154     return;
   155   // We apply only on documents in tabs of Firefox
   156   if (!getTabForContentWindow(window))
   157     return;
   159   // When the tab is private, only addons with 'private-browsing' flag in
   160   // their package.json can apply content script to private documents
   161   if (ignoreWindow(window))
   162     return;
   164   for (let pagemod of pagemods) {
   165     if (pagemod.include.matchesAny(document.URL))
   166       onContent(pagemod, window);
   167   }
   168 }
   170 // Returns all tabs on all currently opened windows
   171 function getAllTabs() {
   172   let tabs = [];
   173   // Iterate over all chrome windows
   174   for (let window in windowIterator()) {
   175     if (!isBrowser(window))
   176       continue;
   177     tabs = tabs.concat(getTabs(window));
   178   }
   179   return tabs;
   180 }
   182 function applyOnExistingDocuments (mod) {
   183   let tabs = getAllTabs();
   185   tabs.forEach(function (tab) {
   186     // Fake a newly created document
   187     let window = getTabContentWindow(tab);
   188     if (has(mod.attachTo, "top") && mod.include.matchesAny(getTabURI(tab)))
   189       onContent(mod, window);
   190     if (has(mod.attachTo, "frame")) {
   191       getFrames(window).
   192         filter((iframe) => mod.include.matchesAny(iframe.location.href)).
   193         forEach((frame) => onContent(mod, frame));
   194     }
   195   });
   196 }
   198 function createWorker (mod, window) {
   199   let worker = Worker({
   200     window: window,
   201     contentScript: mod.contentScript,
   202     contentScriptFile: mod.contentScriptFile,
   203     contentScriptOptions: mod.contentScriptOptions,
   204     // Bug 980468: Syntax errors from scripts can happen before the worker
   205     // can set up an error handler. They are per-mod rather than per-worker
   206     // so are best handled at the mod level.
   207     onError: (e) => emit(mod, 'error', e)
   208   });
   209   workers.set(mod, worker);
   210   pipe(worker, mod);
   211   emit(mod, 'attach', worker);
   212   once(worker, 'detach', function detach() {
   213     worker.destroy();
   214   });
   215 }
   217 function onContent (mod, window) {
   218   // not registered yet
   219   if (!pagemods.has(mod))
   220     return;
   222   let isTopDocument = window.top === window;
   223   // Is a top level document and `top` is not set, ignore
   224   if (isTopDocument && !has(mod.attachTo, "top"))
   225     return;
   226   // Is a frame document and `frame` is not set, ignore
   227   if (!isTopDocument && !has(mod.attachTo, "frame"))
   228     return;
   230   let style = styleFor(mod);
   231   if (style)
   232     attach(style, window);
   234   // Immediatly evaluate content script if the document state is already
   235   // matching contentScriptWhen expectations
   236   if (isMatchingAttachState(mod, window)) {
   237     createWorker(mod, window);
   238     return;
   239   }
   241   let eventName = getAttachEventType(mod) || 'load';
   242   domOn(window, eventName, function onReady (e) {
   243     if (e.target.defaultView !== window)
   244       return;
   245     domOff(window, eventName, onReady, true);
   246     createWorker(mod, window);
   247   }, true);
   248 }
   250 function isMatchingAttachState (mod, window) {
   251   let state = window.document.readyState;
   252   return 'start' === mod.contentScriptWhen ||
   253       // Is `load` event already dispatched?
   254       'complete' === state ||
   255       // Is DOMContentLoaded already dispatched and waiting for it?
   256       ('ready' === mod.contentScriptWhen && state === 'interactive')
   257 }

mercurial