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 widget module currently supports only Firefox. |
michael@0 | 7 | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=560716 |
michael@0 | 8 | module.metadata = { |
michael@0 | 9 | "stability": "deprecated", |
michael@0 | 10 | "engines": { |
michael@0 | 11 | "Firefox": "*" |
michael@0 | 12 | } |
michael@0 | 13 | }; |
michael@0 | 14 | |
michael@0 | 15 | // Widget content types |
michael@0 | 16 | const CONTENT_TYPE_URI = 1; |
michael@0 | 17 | const CONTENT_TYPE_HTML = 2; |
michael@0 | 18 | const CONTENT_TYPE_IMAGE = 3; |
michael@0 | 19 | |
michael@0 | 20 | const ERR_CONTENT = "No content or contentURL property found. Widgets must " |
michael@0 | 21 | + "have one or the other.", |
michael@0 | 22 | ERR_LABEL = "The widget must have a non-empty label property.", |
michael@0 | 23 | ERR_ID = "You have to specify a unique value for the id property of " + |
michael@0 | 24 | "your widget in order for the application to remember its " + |
michael@0 | 25 | "position.", |
michael@0 | 26 | ERR_DESTROYED = "The widget has been destroyed and can no longer be used."; |
michael@0 | 27 | |
michael@0 | 28 | const INSERTION_PREF_ROOT = "extensions.sdk-widget-inserted."; |
michael@0 | 29 | |
michael@0 | 30 | // Supported events, mapping from DOM event names to our event names |
michael@0 | 31 | const EVENTS = { |
michael@0 | 32 | "click": "click", |
michael@0 | 33 | "mouseover": "mouseover", |
michael@0 | 34 | "mouseout": "mouseout", |
michael@0 | 35 | }; |
michael@0 | 36 | |
michael@0 | 37 | // In the Australis menu panel, normally widgets should be treated like |
michael@0 | 38 | // normal toolbarbuttons. If they're any wider than this margin, we'll |
michael@0 | 39 | // treat them as wide widgets instead, which fill up the width of the panel: |
michael@0 | 40 | const AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF = 70; |
michael@0 | 41 | const AUSTRALIS_PANEL_WIDE_CLASSNAME = "panel-wide-item"; |
michael@0 | 42 | |
michael@0 | 43 | const { validateOptions } = require("./deprecated/api-utils"); |
michael@0 | 44 | const panels = require("./panel"); |
michael@0 | 45 | const { EventEmitter, EventEmitterTrait } = require("./deprecated/events"); |
michael@0 | 46 | const { Trait } = require("./deprecated/traits"); |
michael@0 | 47 | const LightTrait = require('./deprecated/light-traits').Trait; |
michael@0 | 48 | const { Loader, Symbiont } = require("./content/content"); |
michael@0 | 49 | const { Cortex } = require('./deprecated/cortex'); |
michael@0 | 50 | const windowsAPI = require("./windows"); |
michael@0 | 51 | const { WindowTracker } = require("./deprecated/window-utils"); |
michael@0 | 52 | const { isBrowser } = require("./window/utils"); |
michael@0 | 53 | const { setTimeout } = require("./timers"); |
michael@0 | 54 | const unload = require("./system/unload"); |
michael@0 | 55 | const { getNodeView } = require("./view/core"); |
michael@0 | 56 | const prefs = require('./preferences/service'); |
michael@0 | 57 | |
michael@0 | 58 | require("./util/deprecate").deprecateUsage( |
michael@0 | 59 | "The widget module is deprecated. " + |
michael@0 | 60 | "Please consider using the sdk/ui module instead." |
michael@0 | 61 | ); |
michael@0 | 62 | |
michael@0 | 63 | // Data types definition |
michael@0 | 64 | const valid = { |
michael@0 | 65 | number: { is: ["null", "undefined", "number"] }, |
michael@0 | 66 | string: { is: ["null", "undefined", "string"] }, |
michael@0 | 67 | id: { |
michael@0 | 68 | is: ["string"], |
michael@0 | 69 | ok: function (v) v.length > 0, |
michael@0 | 70 | msg: ERR_ID, |
michael@0 | 71 | readonly: true |
michael@0 | 72 | }, |
michael@0 | 73 | label: { |
michael@0 | 74 | is: ["string"], |
michael@0 | 75 | ok: function (v) v.length > 0, |
michael@0 | 76 | msg: ERR_LABEL |
michael@0 | 77 | }, |
michael@0 | 78 | panel: { |
michael@0 | 79 | is: ["null", "undefined", "object"], |
michael@0 | 80 | ok: function(v) !v || v instanceof panels.Panel |
michael@0 | 81 | }, |
michael@0 | 82 | width: { |
michael@0 | 83 | is: ["null", "undefined", "number"], |
michael@0 | 84 | map: function (v) { |
michael@0 | 85 | if (null === v || undefined === v) v = 16; |
michael@0 | 86 | return v; |
michael@0 | 87 | }, |
michael@0 | 88 | defaultValue: 16 |
michael@0 | 89 | }, |
michael@0 | 90 | allow: { |
michael@0 | 91 | is: ["null", "undefined", "object"], |
michael@0 | 92 | map: function (v) { |
michael@0 | 93 | if (!v) v = { script: true }; |
michael@0 | 94 | return v; |
michael@0 | 95 | }, |
michael@0 | 96 | get defaultValue() ({ script: true }) |
michael@0 | 97 | }, |
michael@0 | 98 | }; |
michael@0 | 99 | |
michael@0 | 100 | // Widgets attributes definition |
michael@0 | 101 | let widgetAttributes = { |
michael@0 | 102 | label: valid.label, |
michael@0 | 103 | id: valid.id, |
michael@0 | 104 | tooltip: valid.string, |
michael@0 | 105 | width: valid.width, |
michael@0 | 106 | content: valid.string, |
michael@0 | 107 | panel: valid.panel, |
michael@0 | 108 | allow: valid.allow |
michael@0 | 109 | }; |
michael@0 | 110 | |
michael@0 | 111 | // Import data definitions from loader, but don't compose with it as Model |
michael@0 | 112 | // functions allow us to recreate easily all Loader code. |
michael@0 | 113 | let loaderAttributes = require("./content/loader").validationAttributes; |
michael@0 | 114 | for (let i in loaderAttributes) |
michael@0 | 115 | widgetAttributes[i] = loaderAttributes[i]; |
michael@0 | 116 | |
michael@0 | 117 | widgetAttributes.contentURL.optional = true; |
michael@0 | 118 | |
michael@0 | 119 | // Widgets public events list, that are automatically binded in options object |
michael@0 | 120 | const WIDGET_EVENTS = [ |
michael@0 | 121 | "click", |
michael@0 | 122 | "mouseover", |
michael@0 | 123 | "mouseout", |
michael@0 | 124 | "error", |
michael@0 | 125 | "message", |
michael@0 | 126 | "attach" |
michael@0 | 127 | ]; |
michael@0 | 128 | |
michael@0 | 129 | // `Model` utility functions that help creating these various Widgets objects |
michael@0 | 130 | let model = { |
michael@0 | 131 | |
michael@0 | 132 | // Validate one attribute using api-utils.js:validateOptions function |
michael@0 | 133 | _validate: function _validate(name, suspect, validation) { |
michael@0 | 134 | let $1 = {}; |
michael@0 | 135 | $1[name] = suspect; |
michael@0 | 136 | let $2 = {}; |
michael@0 | 137 | $2[name] = validation; |
michael@0 | 138 | return validateOptions($1, $2)[name]; |
michael@0 | 139 | }, |
michael@0 | 140 | |
michael@0 | 141 | /** |
michael@0 | 142 | * This method has two purposes: |
michael@0 | 143 | * 1/ Validate and define, on a given object, a set of attribute |
michael@0 | 144 | * 2/ Emit a "change" event on this object when an attribute is changed |
michael@0 | 145 | * |
michael@0 | 146 | * @params {Object} object |
michael@0 | 147 | * Object on which we can bind attributes on and watch for their changes. |
michael@0 | 148 | * This object must have an EventEmitter interface, or, at least `_emit` |
michael@0 | 149 | * method |
michael@0 | 150 | * @params {Object} attrs |
michael@0 | 151 | * Dictionary of attributes definition following api-utils:validateOptions |
michael@0 | 152 | * scheme |
michael@0 | 153 | * @params {Object} values |
michael@0 | 154 | * Dictionary of attributes default values |
michael@0 | 155 | */ |
michael@0 | 156 | setAttributes: function setAttributes(object, attrs, values) { |
michael@0 | 157 | let properties = {}; |
michael@0 | 158 | for (let name in attrs) { |
michael@0 | 159 | let value = values[name]; |
michael@0 | 160 | let req = attrs[name]; |
michael@0 | 161 | |
michael@0 | 162 | // Retrieve default value from typedef if the value is not defined |
michael@0 | 163 | if ((typeof value == "undefined" || value == null) && req.defaultValue) |
michael@0 | 164 | value = req.defaultValue; |
michael@0 | 165 | |
michael@0 | 166 | // Check for valid value if value is defined or mandatory |
michael@0 | 167 | if (!req.optional || typeof value != "undefined") |
michael@0 | 168 | value = model._validate(name, value, req); |
michael@0 | 169 | |
michael@0 | 170 | // In any case, define this property on `object` |
michael@0 | 171 | let property = null; |
michael@0 | 172 | if (req.readonly) { |
michael@0 | 173 | property = { |
michael@0 | 174 | value: value, |
michael@0 | 175 | writable: false, |
michael@0 | 176 | enumerable: true, |
michael@0 | 177 | configurable: false |
michael@0 | 178 | }; |
michael@0 | 179 | } |
michael@0 | 180 | else { |
michael@0 | 181 | property = model._createWritableProperty(name, value); |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | properties[name] = property; |
michael@0 | 185 | } |
michael@0 | 186 | Object.defineProperties(object, properties); |
michael@0 | 187 | }, |
michael@0 | 188 | |
michael@0 | 189 | // Generate ES5 property definition for a given attribute |
michael@0 | 190 | _createWritableProperty: function _createWritableProperty(name, value) { |
michael@0 | 191 | return { |
michael@0 | 192 | get: function () { |
michael@0 | 193 | return value; |
michael@0 | 194 | }, |
michael@0 | 195 | set: function (newValue) { |
michael@0 | 196 | value = newValue; |
michael@0 | 197 | // The main goal of all this Model stuff is here: |
michael@0 | 198 | // We want to forward all changes to some listeners |
michael@0 | 199 | this._emit("change", name, value); |
michael@0 | 200 | }, |
michael@0 | 201 | enumerable: true, |
michael@0 | 202 | configurable: false |
michael@0 | 203 | }; |
michael@0 | 204 | }, |
michael@0 | 205 | |
michael@0 | 206 | /** |
michael@0 | 207 | * Automagically register listeners in options dictionary |
michael@0 | 208 | * by detecting listener attributes with name starting with `on` |
michael@0 | 209 | * |
michael@0 | 210 | * @params {Object} object |
michael@0 | 211 | * Target object that need to follow EventEmitter interface, or, at least, |
michael@0 | 212 | * having `on` method. |
michael@0 | 213 | * @params {Array} events |
michael@0 | 214 | * List of events name to automatically bind. |
michael@0 | 215 | * @params {Object} listeners |
michael@0 | 216 | * Dictionary of event listener functions to register. |
michael@0 | 217 | */ |
michael@0 | 218 | setEvents: function setEvents(object, events, listeners) { |
michael@0 | 219 | for (let i = 0, l = events.length; i < l; i++) { |
michael@0 | 220 | let name = events[i]; |
michael@0 | 221 | let onName = "on" + name[0].toUpperCase() + name.substr(1); |
michael@0 | 222 | if (!listeners[onName]) |
michael@0 | 223 | continue; |
michael@0 | 224 | object.on(name, listeners[onName].bind(object)); |
michael@0 | 225 | } |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | }; |
michael@0 | 229 | |
michael@0 | 230 | function saveInserted(widgetId) { |
michael@0 | 231 | prefs.set(INSERTION_PREF_ROOT + widgetId, true); |
michael@0 | 232 | } |
michael@0 | 233 | |
michael@0 | 234 | function haveInserted(widgetId) { |
michael@0 | 235 | return prefs.has(INSERTION_PREF_ROOT + widgetId); |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | const isWide = node => node.classList.contains(AUSTRALIS_PANEL_WIDE_CLASSNAME); |
michael@0 | 239 | |
michael@0 | 240 | /** |
michael@0 | 241 | * Main Widget class: entry point of the widget API |
michael@0 | 242 | * |
michael@0 | 243 | * Allow to control all widget across all existing windows with a single object. |
michael@0 | 244 | * Widget.getView allow to retrieve a WidgetView instance to control a widget |
michael@0 | 245 | * specific to one window. |
michael@0 | 246 | */ |
michael@0 | 247 | const WidgetTrait = LightTrait.compose(EventEmitterTrait, LightTrait({ |
michael@0 | 248 | |
michael@0 | 249 | _initWidget: function _initWidget(options) { |
michael@0 | 250 | model.setAttributes(this, widgetAttributes, options); |
michael@0 | 251 | |
michael@0 | 252 | browserManager.validate(this); |
michael@0 | 253 | |
michael@0 | 254 | // We must have at least content or contentURL defined |
michael@0 | 255 | if (!(this.content || this.contentURL)) |
michael@0 | 256 | throw new Error(ERR_CONTENT); |
michael@0 | 257 | |
michael@0 | 258 | this._views = []; |
michael@0 | 259 | |
michael@0 | 260 | // Set tooltip to label value if we don't have tooltip defined |
michael@0 | 261 | if (!this.tooltip) |
michael@0 | 262 | this.tooltip = this.label; |
michael@0 | 263 | |
michael@0 | 264 | model.setEvents(this, WIDGET_EVENTS, options); |
michael@0 | 265 | |
michael@0 | 266 | this.on('change', this._onChange.bind(this)); |
michael@0 | 267 | |
michael@0 | 268 | let self = this; |
michael@0 | 269 | this._port = EventEmitterTrait.create({ |
michael@0 | 270 | emit: function () { |
michael@0 | 271 | let args = arguments; |
michael@0 | 272 | self._views.forEach(function(v) v.port.emit.apply(v.port, args)); |
michael@0 | 273 | } |
michael@0 | 274 | }); |
michael@0 | 275 | // expose wrapped port, that exposes only public properties. |
michael@0 | 276 | this._port._public = Cortex(this._port); |
michael@0 | 277 | |
michael@0 | 278 | // Register this widget to browser manager in order to create new widget on |
michael@0 | 279 | // all new windows |
michael@0 | 280 | browserManager.addItem(this); |
michael@0 | 281 | }, |
michael@0 | 282 | |
michael@0 | 283 | _onChange: function _onChange(name, value) { |
michael@0 | 284 | // Set tooltip to label value if we don't have tooltip defined |
michael@0 | 285 | if (name == 'tooltip' && !value) { |
michael@0 | 286 | // we need to change tooltip again in order to change the value of the |
michael@0 | 287 | // attribute itself |
michael@0 | 288 | this.tooltip = this.label; |
michael@0 | 289 | return; |
michael@0 | 290 | } |
michael@0 | 291 | |
michael@0 | 292 | // Forward attributes changes to WidgetViews |
michael@0 | 293 | if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) { |
michael@0 | 294 | this._views.forEach(function(v) v[name] = value); |
michael@0 | 295 | } |
michael@0 | 296 | }, |
michael@0 | 297 | |
michael@0 | 298 | _onEvent: function _onEvent(type, eventData) { |
michael@0 | 299 | this._emit(type, eventData); |
michael@0 | 300 | }, |
michael@0 | 301 | |
michael@0 | 302 | _createView: function _createView() { |
michael@0 | 303 | // Create a new WidgetView instance |
michael@0 | 304 | let view = WidgetView(this); |
michael@0 | 305 | |
michael@0 | 306 | // Keep a reference to it |
michael@0 | 307 | this._views.push(view); |
michael@0 | 308 | |
michael@0 | 309 | return view; |
michael@0 | 310 | }, |
michael@0 | 311 | |
michael@0 | 312 | // a WidgetView instance is destroyed |
michael@0 | 313 | _onViewDestroyed: function _onViewDestroyed(view) { |
michael@0 | 314 | let idx = this._views.indexOf(view); |
michael@0 | 315 | this._views.splice(idx, 1); |
michael@0 | 316 | }, |
michael@0 | 317 | |
michael@0 | 318 | /** |
michael@0 | 319 | * Called on browser window closed, to destroy related WidgetViews |
michael@0 | 320 | * @params {ChromeWindow} window |
michael@0 | 321 | * Window that has been closed |
michael@0 | 322 | */ |
michael@0 | 323 | _onWindowClosed: function _onWindowClosed(window) { |
michael@0 | 324 | for each (let view in this._views) { |
michael@0 | 325 | if (view._isInChromeWindow(window)) { |
michael@0 | 326 | view.destroy(); |
michael@0 | 327 | break; |
michael@0 | 328 | } |
michael@0 | 329 | } |
michael@0 | 330 | }, |
michael@0 | 331 | |
michael@0 | 332 | /** |
michael@0 | 333 | * Get the WidgetView instance related to a BrowserWindow instance |
michael@0 | 334 | * @params {BrowserWindow} window |
michael@0 | 335 | * BrowserWindow reference from "windows" module |
michael@0 | 336 | */ |
michael@0 | 337 | getView: function getView(window) { |
michael@0 | 338 | for each (let view in this._views) { |
michael@0 | 339 | if (view._isInWindow(window)) { |
michael@0 | 340 | return view._public; |
michael@0 | 341 | } |
michael@0 | 342 | } |
michael@0 | 343 | return null; |
michael@0 | 344 | }, |
michael@0 | 345 | |
michael@0 | 346 | get port() this._port._public, |
michael@0 | 347 | set port(v) {}, // Work around Cortex failure with getter without setter |
michael@0 | 348 | // See bug 653464 |
michael@0 | 349 | _port: null, |
michael@0 | 350 | |
michael@0 | 351 | postMessage: function postMessage(message) { |
michael@0 | 352 | this._views.forEach(function(v) v.postMessage(message)); |
michael@0 | 353 | }, |
michael@0 | 354 | |
michael@0 | 355 | destroy: function destroy() { |
michael@0 | 356 | if (this.panel) |
michael@0 | 357 | this.panel.destroy(); |
michael@0 | 358 | |
michael@0 | 359 | // Dispatch destroy calls to views |
michael@0 | 360 | // we need to go backward as we remove items from this array in |
michael@0 | 361 | // _onViewDestroyed |
michael@0 | 362 | for (let i = this._views.length - 1; i >= 0; i--) |
michael@0 | 363 | this._views[i].destroy(); |
michael@0 | 364 | |
michael@0 | 365 | // Unregister widget to stop creating it over new windows |
michael@0 | 366 | // and allow creation of new widget with same id |
michael@0 | 367 | browserManager.removeItem(this); |
michael@0 | 368 | } |
michael@0 | 369 | |
michael@0 | 370 | })); |
michael@0 | 371 | |
michael@0 | 372 | // Widget constructor |
michael@0 | 373 | const Widget = function Widget(options) { |
michael@0 | 374 | let w = WidgetTrait.create(Widget.prototype); |
michael@0 | 375 | w._initWidget(options); |
michael@0 | 376 | |
michael@0 | 377 | // Return a Cortex of widget in order to hide private attributes like _onEvent |
michael@0 | 378 | let _public = Cortex(w); |
michael@0 | 379 | unload.ensure(_public, "destroy"); |
michael@0 | 380 | return _public; |
michael@0 | 381 | } |
michael@0 | 382 | exports.Widget = Widget; |
michael@0 | 383 | |
michael@0 | 384 | |
michael@0 | 385 | /** |
michael@0 | 386 | * WidgetView is an instance of a widget for a specific window. |
michael@0 | 387 | * |
michael@0 | 388 | * This is an external API that can be retrieved by calling Widget.getView or |
michael@0 | 389 | * by watching `attach` event on Widget. |
michael@0 | 390 | */ |
michael@0 | 391 | const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({ |
michael@0 | 392 | |
michael@0 | 393 | // Reference to the matching WidgetChrome |
michael@0 | 394 | // set right after constructor call |
michael@0 | 395 | _chrome: null, |
michael@0 | 396 | |
michael@0 | 397 | // Public interface of the WidgetView, passed in `attach` event or in |
michael@0 | 398 | // Widget.getView |
michael@0 | 399 | _public: null, |
michael@0 | 400 | |
michael@0 | 401 | _initWidgetView: function WidgetView__initWidgetView(baseWidget) { |
michael@0 | 402 | this._baseWidget = baseWidget; |
michael@0 | 403 | |
michael@0 | 404 | model.setAttributes(this, widgetAttributes, baseWidget); |
michael@0 | 405 | |
michael@0 | 406 | this.on('change', this._onChange.bind(this)); |
michael@0 | 407 | |
michael@0 | 408 | let self = this; |
michael@0 | 409 | this._port = EventEmitterTrait.create({ |
michael@0 | 410 | emit: function () { |
michael@0 | 411 | if (!self._chrome) |
michael@0 | 412 | throw new Error(ERR_DESTROYED); |
michael@0 | 413 | self._chrome.update(self._baseWidget, "emit", arguments); |
michael@0 | 414 | } |
michael@0 | 415 | }); |
michael@0 | 416 | // expose wrapped port, that exposes only public properties. |
michael@0 | 417 | this._port._public = Cortex(this._port); |
michael@0 | 418 | |
michael@0 | 419 | this._public = Cortex(this); |
michael@0 | 420 | }, |
michael@0 | 421 | |
michael@0 | 422 | // Called by WidgetChrome, when the related Worker is applied to the document, |
michael@0 | 423 | // so that we can start sending events to it |
michael@0 | 424 | _onWorkerReady: function () { |
michael@0 | 425 | // Emit an `attach` event with a WidgetView instance without private attrs |
michael@0 | 426 | this._baseWidget._emit("attach", this._public); |
michael@0 | 427 | }, |
michael@0 | 428 | |
michael@0 | 429 | _onChange: function WidgetView__onChange(name, value) { |
michael@0 | 430 | if (name == 'tooltip' && !value) { |
michael@0 | 431 | this.tooltip = this.label; |
michael@0 | 432 | return; |
michael@0 | 433 | } |
michael@0 | 434 | |
michael@0 | 435 | // Forward attributes changes to WidgetChrome instance |
michael@0 | 436 | if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) { |
michael@0 | 437 | this._chrome.update(this._baseWidget, name, value); |
michael@0 | 438 | } |
michael@0 | 439 | }, |
michael@0 | 440 | |
michael@0 | 441 | _onEvent: function WidgetView__onEvent(type, eventData, domNode) { |
michael@0 | 442 | // Dispatch event in view |
michael@0 | 443 | this._emit(type, eventData); |
michael@0 | 444 | |
michael@0 | 445 | // And forward it to the main Widget object |
michael@0 | 446 | if ("click" == type || type.indexOf("mouse") == 0) |
michael@0 | 447 | this._baseWidget._onEvent(type, this._public); |
michael@0 | 448 | else |
michael@0 | 449 | this._baseWidget._onEvent(type, eventData); |
michael@0 | 450 | |
michael@0 | 451 | // Special case for click events: if the widget doesn't have a click |
michael@0 | 452 | // handler, but it does have a panel, display the panel. |
michael@0 | 453 | if ("click" == type && !this._listeners("click").length && this.panel) { |
michael@0 | 454 | // This kind of ugly workaround, instead we should implement |
michael@0 | 455 | // `getNodeView` for the `Widget` class itself, but that's kind of |
michael@0 | 456 | // hard without cleaning things up. |
michael@0 | 457 | this.panel.show(null, getNodeView.implement({}, () => domNode)); |
michael@0 | 458 | } |
michael@0 | 459 | }, |
michael@0 | 460 | |
michael@0 | 461 | _isInWindow: function WidgetView__isInWindow(window) { |
michael@0 | 462 | return windowsAPI.BrowserWindow({ |
michael@0 | 463 | window: this._chrome.window |
michael@0 | 464 | }) == window; |
michael@0 | 465 | }, |
michael@0 | 466 | |
michael@0 | 467 | _isInChromeWindow: function WidgetView__isInChromeWindow(window) { |
michael@0 | 468 | return this._chrome.window == window; |
michael@0 | 469 | }, |
michael@0 | 470 | |
michael@0 | 471 | _onPortEvent: function WidgetView__onPortEvent(args) { |
michael@0 | 472 | let port = this._port; |
michael@0 | 473 | port._emit.apply(port, args); |
michael@0 | 474 | let basePort = this._baseWidget._port; |
michael@0 | 475 | basePort._emit.apply(basePort, args); |
michael@0 | 476 | }, |
michael@0 | 477 | |
michael@0 | 478 | get port() this._port._public, |
michael@0 | 479 | set port(v) {}, // Work around Cortex failure with getter without setter |
michael@0 | 480 | // See bug 653464 |
michael@0 | 481 | _port: null, |
michael@0 | 482 | |
michael@0 | 483 | postMessage: function WidgetView_postMessage(message) { |
michael@0 | 484 | if (!this._chrome) |
michael@0 | 485 | throw new Error(ERR_DESTROYED); |
michael@0 | 486 | this._chrome.update(this._baseWidget, "postMessage", message); |
michael@0 | 487 | }, |
michael@0 | 488 | |
michael@0 | 489 | destroy: function WidgetView_destroy() { |
michael@0 | 490 | this._chrome.destroy(); |
michael@0 | 491 | delete this._chrome; |
michael@0 | 492 | this._baseWidget._onViewDestroyed(this); |
michael@0 | 493 | this._emit("detach"); |
michael@0 | 494 | } |
michael@0 | 495 | |
michael@0 | 496 | })); |
michael@0 | 497 | |
michael@0 | 498 | |
michael@0 | 499 | const WidgetView = function WidgetView(baseWidget) { |
michael@0 | 500 | let w = WidgetViewTrait.create(WidgetView.prototype); |
michael@0 | 501 | w._initWidgetView(baseWidget); |
michael@0 | 502 | return w; |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | |
michael@0 | 506 | /** |
michael@0 | 507 | * Keeps track of all browser windows. |
michael@0 | 508 | * Exposes methods for adding/removing widgets |
michael@0 | 509 | * across all open windows (and future ones). |
michael@0 | 510 | * Create a new instance of BrowserWindow per window. |
michael@0 | 511 | */ |
michael@0 | 512 | let browserManager = { |
michael@0 | 513 | items: [], |
michael@0 | 514 | windows: [], |
michael@0 | 515 | |
michael@0 | 516 | // Registers the manager to listen for window openings and closings. Note |
michael@0 | 517 | // that calling this method can cause onTrack to be called immediately if |
michael@0 | 518 | // there are open windows. |
michael@0 | 519 | init: function () { |
michael@0 | 520 | let windowTracker = new WindowTracker(this); |
michael@0 | 521 | unload.ensure(windowTracker); |
michael@0 | 522 | }, |
michael@0 | 523 | |
michael@0 | 524 | // Registers a window with the manager. This is a WindowTracker callback. |
michael@0 | 525 | onTrack: function browserManager_onTrack(window) { |
michael@0 | 526 | if (isBrowser(window)) { |
michael@0 | 527 | let win = new BrowserWindow(window); |
michael@0 | 528 | win.addItems(this.items); |
michael@0 | 529 | this.windows.push(win); |
michael@0 | 530 | } |
michael@0 | 531 | }, |
michael@0 | 532 | |
michael@0 | 533 | // Unregisters a window from the manager. It's told to undo all |
michael@0 | 534 | // modifications. This is a WindowTracker callback. Note that when |
michael@0 | 535 | // WindowTracker is unloaded, it calls onUntrack for every currently opened |
michael@0 | 536 | // window. The browserManager therefore doesn't need to specially handle |
michael@0 | 537 | // unload itself, since unloading the browserManager means untracking all |
michael@0 | 538 | // currently opened windows. |
michael@0 | 539 | onUntrack: function browserManager_onUntrack(window) { |
michael@0 | 540 | if (isBrowser(window)) { |
michael@0 | 541 | this.items.forEach(function(i) i._onWindowClosed(window)); |
michael@0 | 542 | for (let i = 0; i < this.windows.length; i++) { |
michael@0 | 543 | if (this.windows[i].window == window) { |
michael@0 | 544 | this.windows.splice(i, 1)[0]; |
michael@0 | 545 | return; |
michael@0 | 546 | } |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | } |
michael@0 | 550 | }, |
michael@0 | 551 | |
michael@0 | 552 | // Used to validate widget by browserManager before adding it, |
michael@0 | 553 | // in order to check input very early in widget constructor |
michael@0 | 554 | validate : function (item) { |
michael@0 | 555 | let idx = this.items.indexOf(item); |
michael@0 | 556 | if (idx > -1) |
michael@0 | 557 | throw new Error("The widget " + item + " has already been added."); |
michael@0 | 558 | if (item.id) { |
michael@0 | 559 | let sameId = this.items.filter(function(i) i.id == item.id); |
michael@0 | 560 | if (sameId.length > 0) |
michael@0 | 561 | throw new Error("This widget ID is already used: " + item.id); |
michael@0 | 562 | } else { |
michael@0 | 563 | item.id = this.items.length; |
michael@0 | 564 | } |
michael@0 | 565 | }, |
michael@0 | 566 | |
michael@0 | 567 | // Registers an item with the manager. It's added to all currently registered |
michael@0 | 568 | // windows, and when new windows are registered it will be added to them, too. |
michael@0 | 569 | addItem: function browserManager_addItem(item) { |
michael@0 | 570 | this.items.push(item); |
michael@0 | 571 | this.windows.forEach(function (w) w.addItems([item])); |
michael@0 | 572 | }, |
michael@0 | 573 | |
michael@0 | 574 | // Unregisters an item from the manager. It's removed from all windows that |
michael@0 | 575 | // are currently registered. |
michael@0 | 576 | removeItem: function browserManager_removeItem(item) { |
michael@0 | 577 | let idx = this.items.indexOf(item); |
michael@0 | 578 | if (idx > -1) |
michael@0 | 579 | this.items.splice(idx, 1); |
michael@0 | 580 | }, |
michael@0 | 581 | propagateCurrentset: function browserManager_propagateCurrentset(id, currentset) { |
michael@0 | 582 | this.windows.forEach(function (w) w.doc.getElementById(id).setAttribute("currentset", currentset)); |
michael@0 | 583 | } |
michael@0 | 584 | }; |
michael@0 | 585 | |
michael@0 | 586 | |
michael@0 | 587 | |
michael@0 | 588 | /** |
michael@0 | 589 | * Keeps track of a single browser window. |
michael@0 | 590 | * |
michael@0 | 591 | * This is where the core of how a widget's content is added to a window lives. |
michael@0 | 592 | */ |
michael@0 | 593 | function BrowserWindow(window) { |
michael@0 | 594 | this.window = window; |
michael@0 | 595 | this.doc = window.document; |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | BrowserWindow.prototype = { |
michael@0 | 599 | // Adds an array of items to the window. |
michael@0 | 600 | addItems: function BW_addItems(items) { |
michael@0 | 601 | items.forEach(this._addItemToWindow, this); |
michael@0 | 602 | }, |
michael@0 | 603 | |
michael@0 | 604 | _addItemToWindow: function BW__addItemToWindow(baseWidget) { |
michael@0 | 605 | // Create a WidgetView instance |
michael@0 | 606 | let widget = baseWidget._createView(); |
michael@0 | 607 | |
michael@0 | 608 | // Create a WidgetChrome instance |
michael@0 | 609 | let item = new WidgetChrome({ |
michael@0 | 610 | widget: widget, |
michael@0 | 611 | doc: this.doc, |
michael@0 | 612 | window: this.window |
michael@0 | 613 | }); |
michael@0 | 614 | |
michael@0 | 615 | widget._chrome = item; |
michael@0 | 616 | |
michael@0 | 617 | this._insertNodeInToolbar(item.node); |
michael@0 | 618 | |
michael@0 | 619 | // We need to insert Widget DOM Node before finishing widget view creation |
michael@0 | 620 | // (because fill creates an iframe and tries to access its docShell) |
michael@0 | 621 | item.fill(); |
michael@0 | 622 | }, |
michael@0 | 623 | |
michael@0 | 624 | _insertNodeInToolbar: function BW__insertNodeInToolbar(node) { |
michael@0 | 625 | // Add to the customization palette |
michael@0 | 626 | let toolbox = this.doc.getElementById("navigator-toolbox"); |
michael@0 | 627 | let palette = toolbox.palette; |
michael@0 | 628 | palette.appendChild(node); |
michael@0 | 629 | |
michael@0 | 630 | let { CustomizableUI } = this.window; |
michael@0 | 631 | let { id } = node; |
michael@0 | 632 | |
michael@0 | 633 | let placement = CustomizableUI.getPlacementOfWidget(id); |
michael@0 | 634 | |
michael@0 | 635 | if (!placement) { |
michael@0 | 636 | if (haveInserted(id) || isWide(node)) |
michael@0 | 637 | return; |
michael@0 | 638 | |
michael@0 | 639 | placement = {area: 'nav-bar', position: undefined}; |
michael@0 | 640 | saveInserted(id); |
michael@0 | 641 | } |
michael@0 | 642 | |
michael@0 | 643 | CustomizableUI.addWidgetToArea(id, placement.area, placement.position); |
michael@0 | 644 | CustomizableUI.ensureWidgetPlacedInWindow(id, this.window); |
michael@0 | 645 | } |
michael@0 | 646 | } |
michael@0 | 647 | |
michael@0 | 648 | |
michael@0 | 649 | /** |
michael@0 | 650 | * Final Widget class that handles chrome DOM Node: |
michael@0 | 651 | * - create initial DOM nodes |
michael@0 | 652 | * - receive instruction from WidgetView through update method and update DOM |
michael@0 | 653 | * - watch for DOM events and forward them to WidgetView |
michael@0 | 654 | */ |
michael@0 | 655 | function WidgetChrome(options) { |
michael@0 | 656 | this.window = options.window; |
michael@0 | 657 | this._doc = options.doc; |
michael@0 | 658 | this._widget = options.widget; |
michael@0 | 659 | this._symbiont = null; // set later |
michael@0 | 660 | this.node = null; // set later |
michael@0 | 661 | |
michael@0 | 662 | this._createNode(); |
michael@0 | 663 | } |
michael@0 | 664 | |
michael@0 | 665 | // Update a property of a widget. |
michael@0 | 666 | WidgetChrome.prototype.update = function WC_update(updatedItem, property, value) { |
michael@0 | 667 | switch(property) { |
michael@0 | 668 | case "contentURL": |
michael@0 | 669 | case "content": |
michael@0 | 670 | this.setContent(); |
michael@0 | 671 | break; |
michael@0 | 672 | case "width": |
michael@0 | 673 | this.node.style.minWidth = value + "px"; |
michael@0 | 674 | this.node.querySelector("iframe").style.width = value + "px"; |
michael@0 | 675 | break; |
michael@0 | 676 | case "tooltip": |
michael@0 | 677 | this.node.setAttribute("tooltiptext", value); |
michael@0 | 678 | break; |
michael@0 | 679 | case "postMessage": |
michael@0 | 680 | this._symbiont.postMessage(value); |
michael@0 | 681 | break; |
michael@0 | 682 | case "emit": |
michael@0 | 683 | let port = this._symbiont.port; |
michael@0 | 684 | port.emit.apply(port, value); |
michael@0 | 685 | break; |
michael@0 | 686 | } |
michael@0 | 687 | } |
michael@0 | 688 | |
michael@0 | 689 | // Add a widget to this window. |
michael@0 | 690 | WidgetChrome.prototype._createNode = function WC__createNode() { |
michael@0 | 691 | // XUL element container for widget |
michael@0 | 692 | let node = this._doc.createElement("toolbaritem"); |
michael@0 | 693 | |
michael@0 | 694 | // Temporary work around require("self") failing on unit-test execution ... |
michael@0 | 695 | let jetpackID = "testID"; |
michael@0 | 696 | try { |
michael@0 | 697 | jetpackID = require("./self").id; |
michael@0 | 698 | } catch(e) {} |
michael@0 | 699 | |
michael@0 | 700 | // Compute an unique and stable widget id with jetpack id and widget.id |
michael@0 | 701 | let id = "widget:" + jetpackID + "-" + this._widget.id; |
michael@0 | 702 | node.setAttribute("id", id); |
michael@0 | 703 | node.setAttribute("label", this._widget.label); |
michael@0 | 704 | node.setAttribute("tooltiptext", this._widget.tooltip); |
michael@0 | 705 | node.setAttribute("align", "center"); |
michael@0 | 706 | // Bug 626326: Prevent customize toolbar context menu to appear |
michael@0 | 707 | node.setAttribute("context", ""); |
michael@0 | 708 | |
michael@0 | 709 | // For use in styling by the browser |
michael@0 | 710 | node.setAttribute("sdkstylewidget", "true"); |
michael@0 | 711 | |
michael@0 | 712 | if (this._widget.width > AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF) { |
michael@0 | 713 | node.classList.add(AUSTRALIS_PANEL_WIDE_CLASSNAME); |
michael@0 | 714 | } |
michael@0 | 715 | |
michael@0 | 716 | // TODO move into a stylesheet, configurable by consumers. |
michael@0 | 717 | // Either widget.style, exposing the style object, or a URL |
michael@0 | 718 | // (eg, can load local stylesheet file). |
michael@0 | 719 | node.setAttribute("style", [ |
michael@0 | 720 | "overflow: hidden; margin: 1px 2px 1px 2px; padding: 0px;", |
michael@0 | 721 | "min-height: 16px;", |
michael@0 | 722 | ].join("")); |
michael@0 | 723 | |
michael@0 | 724 | node.style.minWidth = this._widget.width + "px"; |
michael@0 | 725 | |
michael@0 | 726 | this.node = node; |
michael@0 | 727 | } |
michael@0 | 728 | |
michael@0 | 729 | // Initial population of a widget's content. |
michael@0 | 730 | WidgetChrome.prototype.fill = function WC_fill() { |
michael@0 | 731 | let { node, _doc: document } = this; |
michael@0 | 732 | |
michael@0 | 733 | // Create element |
michael@0 | 734 | let iframe = document.createElement("iframe"); |
michael@0 | 735 | iframe.setAttribute("type", "content"); |
michael@0 | 736 | iframe.setAttribute("transparent", "transparent"); |
michael@0 | 737 | iframe.style.overflow = "hidden"; |
michael@0 | 738 | iframe.style.height = "16px"; |
michael@0 | 739 | iframe.style.maxHeight = "16px"; |
michael@0 | 740 | iframe.style.width = this._widget.width + "px"; |
michael@0 | 741 | iframe.setAttribute("flex", "1"); |
michael@0 | 742 | iframe.style.border = "none"; |
michael@0 | 743 | iframe.style.padding = "0px"; |
michael@0 | 744 | |
michael@0 | 745 | // Do this early, because things like contentWindow are null |
michael@0 | 746 | // until the node is attached to a document. |
michael@0 | 747 | node.appendChild(iframe); |
michael@0 | 748 | |
michael@0 | 749 | let label = document.createElement("label"); |
michael@0 | 750 | label.setAttribute("value", this._widget.label); |
michael@0 | 751 | label.className = "toolbarbutton-text"; |
michael@0 | 752 | label.setAttribute("crop", "right"); |
michael@0 | 753 | label.setAttribute("flex", "1"); |
michael@0 | 754 | node.appendChild(label); |
michael@0 | 755 | |
michael@0 | 756 | // This toolbarbutton is created to provide a more consistent user experience |
michael@0 | 757 | // during customization, see: |
michael@0 | 758 | // https://bugzilla.mozilla.org/show_bug.cgi?id=959640 |
michael@0 | 759 | let button = document.createElement("toolbarbutton"); |
michael@0 | 760 | button.setAttribute("label", this._widget.label); |
michael@0 | 761 | button.setAttribute("crop", "right"); |
michael@0 | 762 | button.className = "toolbarbutton-1 chromeclass-toolbar-additional"; |
michael@0 | 763 | node.appendChild(button); |
michael@0 | 764 | |
michael@0 | 765 | // add event handlers |
michael@0 | 766 | this.addEventHandlers(); |
michael@0 | 767 | |
michael@0 | 768 | // set content |
michael@0 | 769 | this.setContent(); |
michael@0 | 770 | } |
michael@0 | 771 | |
michael@0 | 772 | // Get widget content type. |
michael@0 | 773 | WidgetChrome.prototype.getContentType = function WC_getContentType() { |
michael@0 | 774 | if (this._widget.content) |
michael@0 | 775 | return CONTENT_TYPE_HTML; |
michael@0 | 776 | return (this._widget.contentURL && /\.(jpg|gif|png|ico|svg)$/i.test(this._widget.contentURL)) |
michael@0 | 777 | ? CONTENT_TYPE_IMAGE : CONTENT_TYPE_URI; |
michael@0 | 778 | } |
michael@0 | 779 | |
michael@0 | 780 | // Set widget content. |
michael@0 | 781 | WidgetChrome.prototype.setContent = function WC_setContent() { |
michael@0 | 782 | let type = this.getContentType(); |
michael@0 | 783 | let contentURL = null; |
michael@0 | 784 | |
michael@0 | 785 | switch (type) { |
michael@0 | 786 | case CONTENT_TYPE_HTML: |
michael@0 | 787 | contentURL = "data:text/html;charset=utf-8," + encodeURIComponent(this._widget.content); |
michael@0 | 788 | break; |
michael@0 | 789 | case CONTENT_TYPE_URI: |
michael@0 | 790 | contentURL = this._widget.contentURL; |
michael@0 | 791 | break; |
michael@0 | 792 | case CONTENT_TYPE_IMAGE: |
michael@0 | 793 | let imageURL = this._widget.contentURL; |
michael@0 | 794 | contentURL = "data:text/html;charset=utf-8,<html><body><img src='" + |
michael@0 | 795 | encodeURI(imageURL) + "'></body></html>"; |
michael@0 | 796 | break; |
michael@0 | 797 | default: |
michael@0 | 798 | throw new Error("The widget's type cannot be determined."); |
michael@0 | 799 | } |
michael@0 | 800 | |
michael@0 | 801 | let iframe = this.node.firstElementChild; |
michael@0 | 802 | |
michael@0 | 803 | let self = this; |
michael@0 | 804 | // Cleanup previously created symbiont (in case we are update content) |
michael@0 | 805 | if (this._symbiont) |
michael@0 | 806 | this._symbiont.destroy(); |
michael@0 | 807 | |
michael@0 | 808 | this._symbiont = Trait.compose(Symbiont.resolve({ |
michael@0 | 809 | _onContentScriptEvent: "_onContentScriptEvent-not-used", |
michael@0 | 810 | _onInit: "_initSymbiont" |
michael@0 | 811 | }), { |
michael@0 | 812 | // Overload `Symbiont._onInit` in order to know when the related worker |
michael@0 | 813 | // is ready. |
michael@0 | 814 | _onInit: function () { |
michael@0 | 815 | this._initSymbiont(); |
michael@0 | 816 | self._widget._onWorkerReady(); |
michael@0 | 817 | }, |
michael@0 | 818 | _onContentScriptEvent: function () { |
michael@0 | 819 | // Redirect events to WidgetView |
michael@0 | 820 | self._widget._onPortEvent(arguments); |
michael@0 | 821 | } |
michael@0 | 822 | })({ |
michael@0 | 823 | frame: iframe, |
michael@0 | 824 | contentURL: contentURL, |
michael@0 | 825 | contentScriptFile: this._widget.contentScriptFile, |
michael@0 | 826 | contentScript: this._widget.contentScript, |
michael@0 | 827 | contentScriptWhen: this._widget.contentScriptWhen, |
michael@0 | 828 | contentScriptOptions: this._widget.contentScriptOptions, |
michael@0 | 829 | allow: this._widget.allow, |
michael@0 | 830 | onMessage: function(message) { |
michael@0 | 831 | setTimeout(function() { |
michael@0 | 832 | self._widget._onEvent("message", message); |
michael@0 | 833 | }, 0); |
michael@0 | 834 | } |
michael@0 | 835 | }); |
michael@0 | 836 | } |
michael@0 | 837 | |
michael@0 | 838 | // Detect if document consists of a single image. |
michael@0 | 839 | WidgetChrome._isImageDoc = function WC__isImageDoc(doc) { |
michael@0 | 840 | return /*doc.body &&*/ doc.body.childNodes.length == 1 && |
michael@0 | 841 | doc.body.firstElementChild && |
michael@0 | 842 | doc.body.firstElementChild.tagName == "IMG"; |
michael@0 | 843 | } |
michael@0 | 844 | |
michael@0 | 845 | // Set up all supported events for a widget. |
michael@0 | 846 | WidgetChrome.prototype.addEventHandlers = function WC_addEventHandlers() { |
michael@0 | 847 | let contentType = this.getContentType(); |
michael@0 | 848 | |
michael@0 | 849 | let self = this; |
michael@0 | 850 | let listener = function(e) { |
michael@0 | 851 | // Ignore event firings that target the iframe. |
michael@0 | 852 | if (e.target == self.node.firstElementChild) |
michael@0 | 853 | return; |
michael@0 | 854 | |
michael@0 | 855 | // The widget only supports left-click for now, |
michael@0 | 856 | // so ignore all clicks (i.e. middle or right) except left ones. |
michael@0 | 857 | if (e.type == "click" && e.button !== 0) |
michael@0 | 858 | return; |
michael@0 | 859 | |
michael@0 | 860 | // Proxy event to the widget |
michael@0 | 861 | setTimeout(function() { |
michael@0 | 862 | self._widget._onEvent(EVENTS[e.type], null, self.node); |
michael@0 | 863 | }, 0); |
michael@0 | 864 | }; |
michael@0 | 865 | |
michael@0 | 866 | this.eventListeners = {}; |
michael@0 | 867 | let iframe = this.node.firstElementChild; |
michael@0 | 868 | for (let type in EVENTS) { |
michael@0 | 869 | iframe.addEventListener(type, listener, true, true); |
michael@0 | 870 | |
michael@0 | 871 | // Store listeners for later removal |
michael@0 | 872 | this.eventListeners[type] = listener; |
michael@0 | 873 | } |
michael@0 | 874 | |
michael@0 | 875 | // On document load, make modifications required for nice default |
michael@0 | 876 | // presentation. |
michael@0 | 877 | function loadListener(e) { |
michael@0 | 878 | let containerStyle = self.window.getComputedStyle(self.node.parentNode); |
michael@0 | 879 | // Ignore event firings that target the iframe |
michael@0 | 880 | if (e.target == iframe) |
michael@0 | 881 | return; |
michael@0 | 882 | // Ignore about:blank loads |
michael@0 | 883 | if (e.type == "load" && e.target.location == "about:blank") |
michael@0 | 884 | return; |
michael@0 | 885 | |
michael@0 | 886 | // We may have had an unload event before that cleaned up the symbiont |
michael@0 | 887 | if (!self._symbiont) |
michael@0 | 888 | self.setContent(); |
michael@0 | 889 | |
michael@0 | 890 | let doc = e.target; |
michael@0 | 891 | |
michael@0 | 892 | if (contentType == CONTENT_TYPE_IMAGE || WidgetChrome._isImageDoc(doc)) { |
michael@0 | 893 | // Force image content to size. |
michael@0 | 894 | // Add-on authors must size their images correctly. |
michael@0 | 895 | doc.body.firstElementChild.style.width = self._widget.width + "px"; |
michael@0 | 896 | doc.body.firstElementChild.style.height = "16px"; |
michael@0 | 897 | } |
michael@0 | 898 | |
michael@0 | 899 | // Extend the add-on bar's default text styles to the widget. |
michael@0 | 900 | doc.body.style.color = containerStyle.color; |
michael@0 | 901 | doc.body.style.fontFamily = containerStyle.fontFamily; |
michael@0 | 902 | doc.body.style.fontSize = containerStyle.fontSize; |
michael@0 | 903 | doc.body.style.fontWeight = containerStyle.fontWeight; |
michael@0 | 904 | doc.body.style.textShadow = containerStyle.textShadow; |
michael@0 | 905 | // Allow all content to fill the box by default. |
michael@0 | 906 | doc.body.style.margin = "0"; |
michael@0 | 907 | } |
michael@0 | 908 | |
michael@0 | 909 | iframe.addEventListener("load", loadListener, true); |
michael@0 | 910 | this.eventListeners["load"] = loadListener; |
michael@0 | 911 | |
michael@0 | 912 | // Register a listener to unload symbiont if the toolbaritem is moved |
michael@0 | 913 | // on user toolbars customization |
michael@0 | 914 | function unloadListener(e) { |
michael@0 | 915 | if (e.target.location == "about:blank") |
michael@0 | 916 | return; |
michael@0 | 917 | self._symbiont.destroy(); |
michael@0 | 918 | self._symbiont = null; |
michael@0 | 919 | // This may fail but not always, it depends on how the node is |
michael@0 | 920 | // moved or removed |
michael@0 | 921 | try { |
michael@0 | 922 | self.setContent(); |
michael@0 | 923 | } catch(e) {} |
michael@0 | 924 | |
michael@0 | 925 | } |
michael@0 | 926 | |
michael@0 | 927 | iframe.addEventListener("unload", unloadListener, true); |
michael@0 | 928 | this.eventListeners["unload"] = unloadListener; |
michael@0 | 929 | } |
michael@0 | 930 | |
michael@0 | 931 | // Remove and unregister the widget from everything |
michael@0 | 932 | WidgetChrome.prototype.destroy = function WC_destroy(removedItems) { |
michael@0 | 933 | // remove event listeners |
michael@0 | 934 | for (let type in this.eventListeners) { |
michael@0 | 935 | let listener = this.eventListeners[type]; |
michael@0 | 936 | this.node.firstElementChild.removeEventListener(type, listener, true); |
michael@0 | 937 | } |
michael@0 | 938 | // remove dom node |
michael@0 | 939 | this.node.parentNode.removeChild(this.node); |
michael@0 | 940 | // cleanup symbiont |
michael@0 | 941 | this._symbiont.destroy(); |
michael@0 | 942 | // cleanup itself |
michael@0 | 943 | this.eventListeners = null; |
michael@0 | 944 | this._widget = null; |
michael@0 | 945 | this._symbiont = null; |
michael@0 | 946 | } |
michael@0 | 947 | |
michael@0 | 948 | // Init the browserManager only after setting prototypes and such above, because |
michael@0 | 949 | // it will cause browserManager.onTrack to be called immediately if there are |
michael@0 | 950 | // open windows. |
michael@0 | 951 | browserManager.init(); |