Sat, 03 Jan 2015 20:18:00 +0100
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 }