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.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | "use strict"; |
michael@0 | 5 | |
michael@0 | 6 | // The panel module currently supports only Firefox. |
michael@0 | 7 | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps |
michael@0 | 8 | module.metadata = { |
michael@0 | 9 | "stability": "stable", |
michael@0 | 10 | "engines": { |
michael@0 | 11 | "Firefox": "*" |
michael@0 | 12 | } |
michael@0 | 13 | }; |
michael@0 | 14 | |
michael@0 | 15 | const { Ci } = require("chrome"); |
michael@0 | 16 | const { setTimeout } = require('./timers'); |
michael@0 | 17 | const { isPrivateBrowsingSupported } = require('./self'); |
michael@0 | 18 | const { isWindowPBSupported } = require('./private-browsing/utils'); |
michael@0 | 19 | const { Class } = require("./core/heritage"); |
michael@0 | 20 | const { merge } = require("./util/object"); |
michael@0 | 21 | const { WorkerHost } = require("./content/utils"); |
michael@0 | 22 | const { Worker } = require("./content/worker"); |
michael@0 | 23 | const { Disposable } = require("./core/disposable"); |
michael@0 | 24 | const { WeakReference } = require('./core/reference'); |
michael@0 | 25 | const { contract: loaderContract } = require("./content/loader"); |
michael@0 | 26 | const { contract } = require("./util/contract"); |
michael@0 | 27 | const { on, off, emit, setListeners } = require("./event/core"); |
michael@0 | 28 | const { EventTarget } = require("./event/target"); |
michael@0 | 29 | const domPanel = require("./panel/utils"); |
michael@0 | 30 | const { events } = require("./panel/events"); |
michael@0 | 31 | const systemEvents = require("./system/events"); |
michael@0 | 32 | const { filter, pipe, stripListeners } = require("./event/utils"); |
michael@0 | 33 | const { getNodeView, getActiveView } = require("./view/core"); |
michael@0 | 34 | const { isNil, isObject, isNumber } = require("./lang/type"); |
michael@0 | 35 | const { getAttachEventType } = require("./content/utils"); |
michael@0 | 36 | const { number, boolean, object } = require('./deprecated/api-utils'); |
michael@0 | 37 | const { Style } = require("./stylesheet/style"); |
michael@0 | 38 | const { attach, detach } = require("./content/mod"); |
michael@0 | 39 | |
michael@0 | 40 | let isRect = ({top, right, bottom, left}) => [top, right, bottom, left]. |
michael@0 | 41 | some(value => isNumber(value) && !isNaN(value)); |
michael@0 | 42 | |
michael@0 | 43 | let isSDKObj = obj => obj instanceof Class; |
michael@0 | 44 | |
michael@0 | 45 | let rectContract = contract({ |
michael@0 | 46 | top: number, |
michael@0 | 47 | right: number, |
michael@0 | 48 | bottom: number, |
michael@0 | 49 | left: number |
michael@0 | 50 | }); |
michael@0 | 51 | |
michael@0 | 52 | let position = { |
michael@0 | 53 | is: object, |
michael@0 | 54 | map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v), |
michael@0 | 55 | ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)), |
michael@0 | 56 | msg: 'The option "position" must be a SDK object registered as anchor; ' + |
michael@0 | 57 | 'or an object with one or more of the following keys set to numeric ' + |
michael@0 | 58 | 'values: top, right, bottom, left.' |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | let displayContract = contract({ |
michael@0 | 62 | width: number, |
michael@0 | 63 | height: number, |
michael@0 | 64 | focus: boolean, |
michael@0 | 65 | position: position |
michael@0 | 66 | }); |
michael@0 | 67 | |
michael@0 | 68 | let panelContract = contract(merge({ |
michael@0 | 69 | // contentStyle* / contentScript* are sharing the same validation constraints, |
michael@0 | 70 | // so they can be mostly reused, except for the messages. |
michael@0 | 71 | contentStyle: merge(Object.create(loaderContract.rules.contentScript), { |
michael@0 | 72 | msg: 'The `contentStyle` option must be a string or an array of strings.' |
michael@0 | 73 | }), |
michael@0 | 74 | contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), { |
michael@0 | 75 | msg: 'The `contentStyleFile` option must be a local URL or an array of URLs' |
michael@0 | 76 | }) |
michael@0 | 77 | }, displayContract.rules, loaderContract.rules)); |
michael@0 | 78 | |
michael@0 | 79 | |
michael@0 | 80 | function isDisposed(panel) !views.has(panel); |
michael@0 | 81 | |
michael@0 | 82 | let panels = new WeakMap(); |
michael@0 | 83 | let models = new WeakMap(); |
michael@0 | 84 | let views = new WeakMap(); |
michael@0 | 85 | let workers = new WeakMap(); |
michael@0 | 86 | let styles = new WeakMap(); |
michael@0 | 87 | |
michael@0 | 88 | const viewFor = (panel) => views.get(panel); |
michael@0 | 89 | const modelFor = (panel) => models.get(panel); |
michael@0 | 90 | const panelFor = (view) => panels.get(view); |
michael@0 | 91 | const workerFor = (panel) => workers.get(panel); |
michael@0 | 92 | const styleFor = (panel) => styles.get(panel); |
michael@0 | 93 | |
michael@0 | 94 | // Utility function takes `panel` instance and makes sure it will be |
michael@0 | 95 | // automatically hidden as soon as other panel is shown. |
michael@0 | 96 | let setupAutoHide = new function() { |
michael@0 | 97 | let refs = new WeakMap(); |
michael@0 | 98 | |
michael@0 | 99 | return function setupAutoHide(panel) { |
michael@0 | 100 | // Create system event listener that reacts to any panel showing and |
michael@0 | 101 | // hides given `panel` if it's not the one being shown. |
michael@0 | 102 | function listener({subject}) { |
michael@0 | 103 | // It could be that listener is not GC-ed in the same cycle as |
michael@0 | 104 | // panel in such case we remove listener manually. |
michael@0 | 105 | let view = viewFor(panel); |
michael@0 | 106 | if (!view) systemEvents.off("popupshowing", listener); |
michael@0 | 107 | else if (subject !== view) panel.hide(); |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | // system event listener is intentionally weak this way we'll allow GC |
michael@0 | 111 | // to claim panel if it's no longer referenced by an add-on code. This also |
michael@0 | 112 | // helps minimizing cleanup required on unload. |
michael@0 | 113 | systemEvents.on("popupshowing", listener); |
michael@0 | 114 | // To make sure listener is not claimed by GC earlier than necessary we |
michael@0 | 115 | // associate it with `panel` it's associated with. This way it won't be |
michael@0 | 116 | // GC-ed earlier than `panel` itself. |
michael@0 | 117 | refs.set(panel, listener); |
michael@0 | 118 | } |
michael@0 | 119 | } |
michael@0 | 120 | |
michael@0 | 121 | const Panel = Class({ |
michael@0 | 122 | implements: [ |
michael@0 | 123 | // Generate accessors for the validated properties that update model on |
michael@0 | 124 | // set and return values from model on get. |
michael@0 | 125 | panelContract.properties(modelFor), |
michael@0 | 126 | EventTarget, |
michael@0 | 127 | Disposable, |
michael@0 | 128 | WeakReference |
michael@0 | 129 | ], |
michael@0 | 130 | extends: WorkerHost(workerFor), |
michael@0 | 131 | setup: function setup(options) { |
michael@0 | 132 | let model = merge({ |
michael@0 | 133 | defaultWidth: 320, |
michael@0 | 134 | defaultHeight: 240, |
michael@0 | 135 | focus: true, |
michael@0 | 136 | position: Object.freeze({}), |
michael@0 | 137 | }, panelContract(options)); |
michael@0 | 138 | models.set(this, model); |
michael@0 | 139 | |
michael@0 | 140 | if (model.contentStyle || model.contentStyleFile) { |
michael@0 | 141 | styles.set(this, Style({ |
michael@0 | 142 | uri: model.contentStyleFile, |
michael@0 | 143 | source: model.contentStyle |
michael@0 | 144 | })); |
michael@0 | 145 | } |
michael@0 | 146 | |
michael@0 | 147 | // Setup view |
michael@0 | 148 | let view = domPanel.make(); |
michael@0 | 149 | panels.set(view, this); |
michael@0 | 150 | views.set(this, view); |
michael@0 | 151 | |
michael@0 | 152 | // Load panel content. |
michael@0 | 153 | domPanel.setURL(view, model.contentURL); |
michael@0 | 154 | |
michael@0 | 155 | setupAutoHide(this); |
michael@0 | 156 | |
michael@0 | 157 | // Setup listeners. |
michael@0 | 158 | setListeners(this, options); |
michael@0 | 159 | let worker = new Worker(stripListeners(options)); |
michael@0 | 160 | workers.set(this, worker); |
michael@0 | 161 | |
michael@0 | 162 | // pipe events from worker to a panel. |
michael@0 | 163 | pipe(worker, this); |
michael@0 | 164 | }, |
michael@0 | 165 | dispose: function dispose() { |
michael@0 | 166 | this.hide(); |
michael@0 | 167 | off(this); |
michael@0 | 168 | |
michael@0 | 169 | workerFor(this).destroy(); |
michael@0 | 170 | detach(styleFor(this)); |
michael@0 | 171 | |
michael@0 | 172 | domPanel.dispose(viewFor(this)); |
michael@0 | 173 | |
michael@0 | 174 | // Release circular reference between view and panel instance. This |
michael@0 | 175 | // way view will be GC-ed. And panel as well once all the other refs |
michael@0 | 176 | // will be removed from it. |
michael@0 | 177 | views.delete(this); |
michael@0 | 178 | }, |
michael@0 | 179 | /* Public API: Panel.width */ |
michael@0 | 180 | get width() modelFor(this).width, |
michael@0 | 181 | set width(value) this.resize(value, this.height), |
michael@0 | 182 | /* Public API: Panel.height */ |
michael@0 | 183 | get height() modelFor(this).height, |
michael@0 | 184 | set height(value) this.resize(this.width, value), |
michael@0 | 185 | |
michael@0 | 186 | /* Public API: Panel.focus */ |
michael@0 | 187 | get focus() modelFor(this).focus, |
michael@0 | 188 | |
michael@0 | 189 | /* Public API: Panel.position */ |
michael@0 | 190 | get position() modelFor(this).position, |
michael@0 | 191 | |
michael@0 | 192 | get contentURL() modelFor(this).contentURL, |
michael@0 | 193 | set contentURL(value) { |
michael@0 | 194 | let model = modelFor(this); |
michael@0 | 195 | model.contentURL = panelContract({ contentURL: value }).contentURL; |
michael@0 | 196 | domPanel.setURL(viewFor(this), model.contentURL); |
michael@0 | 197 | // Detach worker so that messages send will be queued until it's |
michael@0 | 198 | // reatached once panel content is ready. |
michael@0 | 199 | workerFor(this).detach(); |
michael@0 | 200 | }, |
michael@0 | 201 | |
michael@0 | 202 | /* Public API: Panel.isShowing */ |
michael@0 | 203 | get isShowing() !isDisposed(this) && domPanel.isOpen(viewFor(this)), |
michael@0 | 204 | |
michael@0 | 205 | /* Public API: Panel.show */ |
michael@0 | 206 | show: function show(options={}, anchor) { |
michael@0 | 207 | if (options instanceof Ci.nsIDOMElement) { |
michael@0 | 208 | [anchor, options] = [options, null]; |
michael@0 | 209 | } |
michael@0 | 210 | |
michael@0 | 211 | if (anchor instanceof Ci.nsIDOMElement) { |
michael@0 | 212 | console.warn( |
michael@0 | 213 | "Passing a DOM node to Panel.show() method is an unsupported " + |
michael@0 | 214 | "feature that will be soon replaced. " + |
michael@0 | 215 | "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877" |
michael@0 | 216 | ); |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | let model = modelFor(this); |
michael@0 | 220 | let view = viewFor(this); |
michael@0 | 221 | let anchorView = getNodeView(anchor || options.position || model.position); |
michael@0 | 222 | |
michael@0 | 223 | options = merge({ |
michael@0 | 224 | position: model.position, |
michael@0 | 225 | width: model.width, |
michael@0 | 226 | height: model.height, |
michael@0 | 227 | defaultWidth: model.defaultWidth, |
michael@0 | 228 | defaultHeight: model.defaultHeight, |
michael@0 | 229 | focus: model.focus |
michael@0 | 230 | }, displayContract(options)); |
michael@0 | 231 | |
michael@0 | 232 | if (!isDisposed(this)) |
michael@0 | 233 | domPanel.show(view, options, anchorView); |
michael@0 | 234 | |
michael@0 | 235 | return this; |
michael@0 | 236 | }, |
michael@0 | 237 | |
michael@0 | 238 | /* Public API: Panel.hide */ |
michael@0 | 239 | hide: function hide() { |
michael@0 | 240 | // Quit immediately if panel is disposed or there is no state change. |
michael@0 | 241 | domPanel.close(viewFor(this)); |
michael@0 | 242 | |
michael@0 | 243 | return this; |
michael@0 | 244 | }, |
michael@0 | 245 | |
michael@0 | 246 | /* Public API: Panel.resize */ |
michael@0 | 247 | resize: function resize(width, height) { |
michael@0 | 248 | let model = modelFor(this); |
michael@0 | 249 | let view = viewFor(this); |
michael@0 | 250 | let change = panelContract({ |
michael@0 | 251 | width: width || model.width || model.defaultWidth, |
michael@0 | 252 | height: height || model.height || model.defaultHeight |
michael@0 | 253 | }); |
michael@0 | 254 | |
michael@0 | 255 | model.width = change.width |
michael@0 | 256 | model.height = change.height |
michael@0 | 257 | |
michael@0 | 258 | domPanel.resize(view, model.width, model.height); |
michael@0 | 259 | |
michael@0 | 260 | return this; |
michael@0 | 261 | } |
michael@0 | 262 | }); |
michael@0 | 263 | exports.Panel = Panel; |
michael@0 | 264 | |
michael@0 | 265 | // Note must be defined only after value to `Panel` is assigned. |
michael@0 | 266 | getActiveView.define(Panel, viewFor); |
michael@0 | 267 | |
michael@0 | 268 | // Filter panel events to only panels that are create by this module. |
michael@0 | 269 | let panelEvents = filter(events, ({target}) => panelFor(target)); |
michael@0 | 270 | |
michael@0 | 271 | // Panel events emitted after panel has being shown. |
michael@0 | 272 | let shows = filter(panelEvents, ({type}) => type === "popupshown"); |
michael@0 | 273 | |
michael@0 | 274 | // Panel events emitted after panel became hidden. |
michael@0 | 275 | let hides = filter(panelEvents, ({type}) => type === "popuphidden"); |
michael@0 | 276 | |
michael@0 | 277 | // Panel events emitted after content inside panel is ready. For different |
michael@0 | 278 | // panels ready may mean different state based on `contentScriptWhen` attribute. |
michael@0 | 279 | // Weather given event represents readyness is detected by `getAttachEventType` |
michael@0 | 280 | // helper function. |
michael@0 | 281 | let ready = filter(panelEvents, ({type, target}) => |
michael@0 | 282 | getAttachEventType(modelFor(panelFor(target))) === type); |
michael@0 | 283 | |
michael@0 | 284 | // Styles should be always added as soon as possible, and doesn't makes them |
michael@0 | 285 | // depends on `contentScriptWhen` |
michael@0 | 286 | let start = filter(panelEvents, ({type}) => type === "document-element-inserted"); |
michael@0 | 287 | |
michael@0 | 288 | // Forward panel show / hide events to panel's own event listeners. |
michael@0 | 289 | on(shows, "data", ({target}) => emit(panelFor(target), "show")); |
michael@0 | 290 | |
michael@0 | 291 | on(hides, "data", ({target}) => emit(panelFor(target), "hide")); |
michael@0 | 292 | |
michael@0 | 293 | on(ready, "data", ({target}) => { |
michael@0 | 294 | let panel = panelFor(target); |
michael@0 | 295 | let window = domPanel.getContentDocument(target).defaultView; |
michael@0 | 296 | |
michael@0 | 297 | workerFor(panel).attach(window); |
michael@0 | 298 | }); |
michael@0 | 299 | |
michael@0 | 300 | on(start, "data", ({target}) => { |
michael@0 | 301 | let panel = panelFor(target); |
michael@0 | 302 | let window = domPanel.getContentDocument(target).defaultView; |
michael@0 | 303 | |
michael@0 | 304 | attach(styleFor(panel), window); |
michael@0 | 305 | }); |