12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007 |
- "use strict";
- module.metadata = {
- "stability": "stable",
- "engines": {
- "Firefox": "*"
- }
- };
- const CONTENT_TYPE_URI = 1;
- const CONTENT_TYPE_HTML = 2;
- const CONTENT_TYPE_IMAGE = 3;
- const ERR_CONTENT = "No content or contentURL property found. Widgets must "
- + "have one or the other.",
- ERR_LABEL = "The widget must have a non-empty label property.",
- ERR_ID = "You have to specify a unique value for the id property of " +
- "your widget in order for the application to remember its " +
- "position.",
- ERR_DESTROYED = "The widget has been destroyed and can no longer be used.";
- const INSERTION_PREF_ROOT = "extensions.sdk-widget-inserted.";
- const EVENTS = {
- "click": "click",
- "mouseover": "mouseover",
- "mouseout": "mouseout",
- };
- const AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF = 70;
- const { validateOptions } = require("./deprecated/api-utils");
- const panels = require("./panel");
- const { EventEmitter, EventEmitterTrait } = require("./deprecated/events");
- const { Trait } = require("./deprecated/traits");
- const LightTrait = require('./deprecated/light-traits').Trait;
- const { Loader, Symbiont } = require("./content/content");
- const { Cortex } = require('./deprecated/cortex');
- const windowsAPI = require("./windows");
- const { WindowTracker } = require("./deprecated/window-utils");
- const { isBrowser } = require("./window/utils");
- const { setTimeout } = require("./timers");
- const unload = require("./system/unload");
- const { getNodeView } = require("./view/core");
- const prefs = require('./preferences/service');
- const valid = {
- number: { is: ["null", "undefined", "number"] },
- string: { is: ["null", "undefined", "string"] },
- id: {
- is: ["string"],
- ok: function (v) v.length > 0,
- msg: ERR_ID,
- readonly: true
- },
- label: {
- is: ["string"],
- ok: function (v) v.length > 0,
- msg: ERR_LABEL
- },
- panel: {
- is: ["null", "undefined", "object"],
- ok: function(v) !v || v instanceof panels.Panel
- },
- width: {
- is: ["null", "undefined", "number"],
- map: function (v) {
- if (null === v || undefined === v) v = 16;
- return v;
- },
- defaultValue: 16
- },
- allow: {
- is: ["null", "undefined", "object"],
- map: function (v) {
- if (!v) v = { script: true };
- return v;
- },
- get defaultValue() ({ script: true })
- },
- };
- let widgetAttributes = {
- label: valid.label,
- id: valid.id,
- tooltip: valid.string,
- width: valid.width,
- content: valid.string,
- panel: valid.panel,
- allow: valid.allow
- };
- let loaderAttributes = require("./content/loader").validationAttributes;
- for (let i in loaderAttributes)
- widgetAttributes[i] = loaderAttributes[i];
- widgetAttributes.contentURL.optional = true;
- const WIDGET_EVENTS = [
- "click",
- "mouseover",
- "mouseout",
- "error",
- "message",
- "attach"
- ];
- let model = {
-
- _validate: function _validate(name, suspect, validation) {
- let $1 = {};
- $1[name] = suspect;
- let $2 = {};
- $2[name] = validation;
- return validateOptions($1, $2)[name];
- },
-
- setAttributes: function setAttributes(object, attrs, values) {
- let properties = {};
- for (let name in attrs) {
- let value = values[name];
- let req = attrs[name];
-
- if ((typeof value == "undefined" || value == null) && req.defaultValue)
- value = req.defaultValue;
-
- if (!req.optional || typeof value != "undefined")
- value = model._validate(name, value, req);
-
- let property = null;
- if (req.readonly) {
- property = {
- value: value,
- writable: false,
- enumerable: true,
- configurable: false
- };
- }
- else {
- property = model._createWritableProperty(name, value);
- }
- properties[name] = property;
- }
- Object.defineProperties(object, properties);
- },
-
- _createWritableProperty: function _createWritableProperty(name, value) {
- return {
- get: function () {
- return value;
- },
- set: function (newValue) {
- value = newValue;
-
-
- this._emit("change", name, value);
- },
- enumerable: true,
- configurable: false
- };
- },
-
- setEvents: function setEvents(object, events, listeners) {
- for (let i = 0, l = events.length; i < l; i++) {
- let name = events[i];
- let onName = "on" + name[0].toUpperCase() + name.substr(1);
- if (!listeners[onName])
- continue;
- object.on(name, listeners[onName].bind(object));
- }
- }
- };
- function saveInserted(widgetId) {
- prefs.set(INSERTION_PREF_ROOT + widgetId, true);
- }
- function haveInserted(widgetId) {
- return prefs.has(INSERTION_PREF_ROOT + widgetId);
- }
- const WidgetTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
- _initWidget: function _initWidget(options) {
- model.setAttributes(this, widgetAttributes, options);
- browserManager.validate(this);
-
- if (!(this.content || this.contentURL))
- throw new Error(ERR_CONTENT);
- this._views = [];
-
- if (!this.tooltip)
- this.tooltip = this.label;
- model.setEvents(this, WIDGET_EVENTS, options);
- this.on('change', this._onChange.bind(this));
- let self = this;
- this._port = EventEmitterTrait.create({
- emit: function () {
- let args = arguments;
- self._views.forEach(function(v) v.port.emit.apply(v.port, args));
- }
- });
- // expose wrapped port, that exposes only public properties.
- this._port._public = Cortex(this._port);
- // Register this widget to browser manager in order to create new widget on
- // all new windows
- browserManager.addItem(this);
- },
- _onChange: function _onChange(name, value) {
-
- if (name == 'tooltip' && !value) {
-
-
- this.tooltip = this.label;
- return;
- }
-
- if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
- this._views.forEach(function(v) v[name] = value);
- }
- },
- _onEvent: function _onEvent(type, eventData) {
- this._emit(type, eventData);
- },
- _createView: function _createView() {
-
- let view = WidgetView(this);
-
- this._views.push(view);
- return view;
- },
-
- _onViewDestroyed: function _onViewDestroyed(view) {
- let idx = this._views.indexOf(view);
- this._views.splice(idx, 1);
- },
-
- _onWindowClosed: function _onWindowClosed(window) {
- for each (let view in this._views) {
- if (view._isInChromeWindow(window)) {
- view.destroy();
- break;
- }
- }
- },
-
- getView: function getView(window) {
- for each (let view in this._views) {
- if (view._isInWindow(window)) {
- return view._public;
- }
- }
- return null;
- },
- get port() this._port._public,
- set port(v) {},
-
- _port: null,
- postMessage: function postMessage(message) {
- this._views.forEach(function(v) v.postMessage(message));
- },
- destroy: function destroy() {
- if (this.panel)
- this.panel.destroy();
-
-
-
- for (let i = this._views.length - 1; i >= 0; i--)
- this._views[i].destroy();
-
-
- browserManager.removeItem(this);
- }
- }));
- const Widget = function Widget(options) {
- let w = WidgetTrait.create(Widget.prototype);
- w._initWidget(options);
-
- let _public = Cortex(w);
- unload.ensure(_public, "destroy");
- return _public;
- }
- exports.Widget = Widget;
- const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
-
-
- _chrome: null,
-
-
- _public: null,
- _initWidgetView: function WidgetView__initWidgetView(baseWidget) {
- this._baseWidget = baseWidget;
- model.setAttributes(this, widgetAttributes, baseWidget);
- this.on('change', this._onChange.bind(this));
- let self = this;
- this._port = EventEmitterTrait.create({
- emit: function () {
- if (!self._chrome)
- throw new Error(ERR_DESTROYED);
- self._chrome.update(self._baseWidget, "emit", arguments);
- }
- });
-
- this._port._public = Cortex(this._port);
- this._public = Cortex(this);
- },
-
-
- _onWorkerReady: function () {
-
- this._baseWidget._emit("attach", this._public);
- },
- _onChange: function WidgetView__onChange(name, value) {
- if (name == 'tooltip' && !value) {
- this.tooltip = this.label;
- return;
- }
-
- if (['width', 'tooltip', 'content', 'contentURL'].indexOf(name) != -1) {
- this._chrome.update(this._baseWidget, name, value);
- }
- },
- _onEvent: function WidgetView__onEvent(type, eventData, domNode) {
-
- this._emit(type, eventData);
-
- if ("click" == type || type.indexOf("mouse") == 0)
- this._baseWidget._onEvent(type, this._public);
- else
- this._baseWidget._onEvent(type, eventData);
-
-
- if ("click" == type && !this._listeners("click").length && this.panel) {
-
-
-
-
- let anchor = domNode;
- let { CustomizableUI, window } = domNode.ownerDocument.defaultView;
- if (CustomizableUI) {
- ({anchor}) = CustomizableUI.getWidget(domNode.id).forWindow(window);
-
-
-
- if (anchor !== domNode)
- CustomizableUI.hidePanelForNode(domNode);
- }
-
-
-
- this.panel.show(null, getNodeView.implement({}, () => anchor));
- }
- },
- _isInWindow: function WidgetView__isInWindow(window) {
- return windowsAPI.BrowserWindow({
- window: this._chrome.window
- }) == window;
- },
- _isInChromeWindow: function WidgetView__isInChromeWindow(window) {
- return this._chrome.window == window;
- },
- _onPortEvent: function WidgetView__onPortEvent(args) {
- let port = this._port;
- port._emit.apply(port, args);
- let basePort = this._baseWidget._port;
- basePort._emit.apply(basePort, args);
- },
- get port() this._port._public,
- set port(v) {},
-
- _port: null,
- postMessage: function WidgetView_postMessage(message) {
- if (!this._chrome)
- throw new Error(ERR_DESTROYED);
- this._chrome.update(this._baseWidget, "postMessage", message);
- },
- destroy: function WidgetView_destroy() {
- this._chrome.destroy();
- delete this._chrome;
- this._baseWidget._onViewDestroyed(this);
- this._emit("detach");
- }
- }));
- const WidgetView = function WidgetView(baseWidget) {
- let w = WidgetViewTrait.create(WidgetView.prototype);
- w._initWidgetView(baseWidget);
- return w;
- }
- let browserManager = {
- items: [],
- windows: [],
-
-
-
- init: function () {
- let windowTracker = new WindowTracker(this);
- unload.ensure(windowTracker);
- },
-
- onTrack: function browserManager_onTrack(window) {
- if (isBrowser(window)) {
- let win = new BrowserWindow(window);
- win.addItems(this.items);
- this.windows.push(win);
- }
- },
-
-
-
-
-
-
- onUntrack: function browserManager_onUntrack(window) {
- if (isBrowser(window)) {
- this.items.forEach(function(i) i._onWindowClosed(window));
- for (let i = 0; i < this.windows.length; i++) {
- if (this.windows[i].window == window) {
- this.windows.splice(i, 1)[0];
- return;
- }
- }
- }
- },
-
-
- validate : function (item) {
- let idx = this.items.indexOf(item);
- if (idx > -1)
- throw new Error("The widget " + item + " has already been added.");
- if (item.id) {
- let sameId = this.items.filter(function(i) i.id == item.id);
- if (sameId.length > 0)
- throw new Error("This widget ID is already used: " + item.id);
- } else {
- item.id = this.items.length;
- }
- },
-
-
- addItem: function browserManager_addItem(item) {
- this.items.push(item);
- this.windows.forEach(function (w) w.addItems([item]));
- },
- // Unregisters an item from the manager. It's removed from all windows that
- // are currently registered.
- removeItem: function browserManager_removeItem(item) {
- let idx = this.items.indexOf(item);
- if (idx > -1)
- this.items.splice(idx, 1);
- },
- propagateCurrentset: function browserManager_propagateCurrentset(id, currentset) {
- this.windows.forEach(function (w) w.doc.getElementById(id).setAttribute("currentset", currentset));
- }
- };
- /**
- * Keeps track of a single browser window.
- *
- * This is where the core of how a widget's content is added to a window lives.
- */
- function BrowserWindow(window) {
- this.window = window;
- this.doc = window.document;
- }
- BrowserWindow.prototype = {
-
- addItems: function BW_addItems(items) {
- items.forEach(this._addItemToWindow, this);
- },
- _addItemToWindow: function BW__addItemToWindow(baseWidget) {
-
- let widget = baseWidget._createView();
-
- let item = new WidgetChrome({
- widget: widget,
- doc: this.doc,
- window: this.window
- });
- widget._chrome = item;
- this._insertNodeInToolbar(item.node);
-
-
- item.fill();
- },
- _insertNodeInToolbar: function BW__insertNodeInToolbar(node) {
-
- let toolbox = this.doc.getElementById("navigator-toolbox");
- let palette = toolbox.palette;
- palette.appendChild(node);
- if (this.window.CustomizableUI) {
- let placement = this.window.CustomizableUI.getPlacementOfWidget(node.id);
- if (!placement) {
- if (haveInserted(node.id)) {
- return;
- }
- placement = {area: 'nav-bar', position: undefined};
- saveInserted(node.id);
- }
- this.window.CustomizableUI.addWidgetToArea(node.id, placement.area, placement.position);
- this.window.CustomizableUI.ensureWidgetPlacedInWindow(node.id, this.window);
- return;
- }
-
- let container = null;
- let toolbars = this.doc.getElementsByTagName("toolbar");
- let id = node.getAttribute("id");
- for (let i = 0, l = toolbars.length; i < l; i++) {
- let toolbar = toolbars[i];
- if (toolbar.getAttribute("currentset").indexOf(id) == -1)
- continue;
- container = toolbar;
- }
-
- let needToPropagateCurrentset = false;
- if (!container) {
- if (haveInserted(node.id)) {
- return;
- }
- container = this.doc.getElementById("addon-bar");
- saveInserted(node.id);
- needToPropagateCurrentset = true;
-
-
-
-
- if (container.collapsed)
- this.window.toggleAddonBar();
- }
-
-
- let nextNode = null;
- let currentSet = container.getAttribute("currentset");
- let ids = (currentSet == "__empty") ? [] : currentSet.split(",");
- let idx = ids.indexOf(id);
- if (idx != -1) {
- for (let i = idx; i < ids.length; i++) {
- nextNode = this.doc.getElementById(ids[i]);
- if (nextNode)
- break;
- }
- }
-
- container.insertItem(id, nextNode, null, false);
-
-
-
-
- if (ids.indexOf(id) == -1) {
- let set = container.currentSet;
- container.setAttribute("currentset", set);
- // Save DOM attribute in order to save position on new window opened
- this.window.document.persist(container.id, "currentset");
- browserManager.propagateCurrentset(container.id, set);
- }
- }
- }
- /**
- * Final Widget class that handles chrome DOM Node:
- * - create initial DOM nodes
- * - receive instruction from WidgetView through update method and update DOM
- * - watch for DOM events and forward them to WidgetView
- */
- function WidgetChrome(options) {
- this.window = options.window;
- this._doc = options.doc;
- this._widget = options.widget;
- this._symbiont = null;
- this.node = null;
- this._createNode();
- }
- WidgetChrome.prototype.update = function WC_update(updatedItem, property, value) {
- switch(property) {
- case "contentURL":
- case "content":
- this.setContent();
- break;
- case "width":
- this.node.style.minWidth = value + "px";
- this.node.querySelector("iframe").style.width = value + "px";
- break;
- case "tooltip":
- this.node.setAttribute("tooltiptext", value);
- break;
- case "postMessage":
- this._symbiont.postMessage(value);
- break;
- case "emit":
- let port = this._symbiont.port;
- port.emit.apply(port, value);
- break;
- }
- }
- WidgetChrome.prototype._createNode = function WC__createNode() {
-
- let node = this._doc.createElement("toolbaritem");
-
- let jetpackID = "testID";
- try {
- jetpackID = require("./self").id;
- } catch(e) {}
-
- let id = "widget:" + jetpackID + "-" + this._widget.id;
- node.setAttribute("id", id);
- node.setAttribute("label", this._widget.label);
- node.setAttribute("tooltiptext", this._widget.tooltip);
- node.setAttribute("align", "center");
-
- node.setAttribute("context", "");
-
- node.setAttribute("sdkstylewidget", "true");
-
- if (this.window.CustomizableUI &&
- this._widget.width > AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF) {
- node.classList.add("panel-wide-item");
- }
-
-
-
- node.setAttribute("style", [
- "overflow: hidden; margin: 1px 2px 1px 2px; padding: 0px;",
- "min-height: 16px;",
- ].join(""));
- node.style.minWidth = this._widget.width + "px";
- this.node = node;
- }
- WidgetChrome.prototype.fill = function WC_fill() {
-
- var iframe = this._doc.createElement("iframe");
- iframe.setAttribute("type", "content");
- iframe.setAttribute("transparent", "transparent");
- iframe.style.overflow = "hidden";
- iframe.style.height = "16px";
- iframe.style.maxHeight = "16px";
- iframe.style.width = this._widget.width + "px";
- iframe.setAttribute("flex", "1");
- iframe.style.border = "none";
- iframe.style.padding = "0px";
-
-
- this.node.appendChild(iframe);
- var label = this._doc.createElement("label");
- label.setAttribute("value", this._widget.label);
- label.className = "toolbarbutton-text";
- label.setAttribute("crop", "right");
- label.setAttribute("flex", "1");
- this.node.appendChild(label);
-
- this.addEventHandlers();
-
- this.setContent();
- }
- WidgetChrome.prototype.getContentType = function WC_getContentType() {
- if (this._widget.content)
- return CONTENT_TYPE_HTML;
- return (this._widget.contentURL && /\.(jpg|gif|png|ico|svg)$/i.test(this._widget.contentURL))
- ? CONTENT_TYPE_IMAGE : CONTENT_TYPE_URI;
- }
- WidgetChrome.prototype.setContent = function WC_setContent() {
- let type = this.getContentType();
- let contentURL = null;
- switch (type) {
- case CONTENT_TYPE_HTML:
- contentURL = "data:text/html;charset=utf-8," + encodeURIComponent(this._widget.content);
- break;
- case CONTENT_TYPE_URI:
- contentURL = this._widget.contentURL;
- break;
- case CONTENT_TYPE_IMAGE:
- let imageURL = this._widget.contentURL;
- contentURL = "data:text/html;charset=utf-8,<html><body><img src='" +
- encodeURI(imageURL) + "'></body></html>";
- break;
- default:
- throw new Error("The widget's type cannot be determined.");
- }
- let iframe = this.node.firstElementChild;
- let self = this;
-
- if (this._symbiont)
- this._symbiont.destroy();
- this._symbiont = Trait.compose(Symbiont.resolve({
- _onContentScriptEvent: "_onContentScriptEvent-not-used",
- _onInit: "_initSymbiont"
- }), {
-
-
- _onInit: function () {
- this._initSymbiont();
- self._widget._onWorkerReady();
- },
- _onContentScriptEvent: function () {
-
- self._widget._onPortEvent(arguments);
- }
- })({
- frame: iframe,
- contentURL: contentURL,
- contentScriptFile: this._widget.contentScriptFile,
- contentScript: this._widget.contentScript,
- contentScriptWhen: this._widget.contentScriptWhen,
- contentScriptOptions: this._widget.contentScriptOptions,
- allow: this._widget.allow,
- onMessage: function(message) {
- setTimeout(function() {
- self._widget._onEvent("message", message);
- }, 0);
- }
- });
- }
- WidgetChrome._isImageDoc = function WC__isImageDoc(doc) {
- return doc.body.childNodes.length == 1 &&
- doc.body.firstElementChild &&
- doc.body.firstElementChild.tagName == "IMG";
- }
- WidgetChrome.prototype.addEventHandlers = function WC_addEventHandlers() {
- let contentType = this.getContentType();
- let self = this;
- let listener = function(e) {
-
- if (e.target == self.node.firstElementChild)
- return;
-
-
- if (e.type == "click" && e.button !== 0)
- return;
-
- setTimeout(function() {
- self._widget._onEvent(EVENTS[e.type], null, self.node);
- }, 0);
- };
- this.eventListeners = {};
- let iframe = this.node.firstElementChild;
- for (let type in EVENTS) {
- iframe.addEventListener(type, listener, true, true);
-
- this.eventListeners[type] = listener;
- }
-
-
- function loadListener(e) {
- let containerStyle = self.window.getComputedStyle(self.node.parentNode);
-
- if (e.target == iframe)
- return;
-
- if (e.type == "load" && e.target.location == "about:blank")
- return;
-
- if (!self._symbiont)
- self.setContent();
- let doc = e.target;
- if (contentType == CONTENT_TYPE_IMAGE || WidgetChrome._isImageDoc(doc)) {
-
-
- doc.body.firstElementChild.style.width = self._widget.width + "px";
- doc.body.firstElementChild.style.height = "16px";
- }
-
- doc.body.style.color = containerStyle.color;
- doc.body.style.fontFamily = containerStyle.fontFamily;
- doc.body.style.fontSize = containerStyle.fontSize;
- doc.body.style.fontWeight = containerStyle.fontWeight;
- doc.body.style.textShadow = containerStyle.textShadow;
-
- doc.body.style.margin = "0";
- }
- iframe.addEventListener("load", loadListener, true);
- this.eventListeners["load"] = loadListener;
-
-
- function unloadListener(e) {
- if (e.target.location == "about:blank")
- return;
- self._symbiont.destroy();
- self._symbiont = null;
-
-
- try {
- self.setContent();
- } catch(e) {}
- }
- iframe.addEventListener("unload", unloadListener, true);
- this.eventListeners["unload"] = unloadListener;
- }
- WidgetChrome.prototype.destroy = function WC_destroy(removedItems) {
-
- for (let type in this.eventListeners) {
- let listener = this.eventListeners[type];
- this.node.firstElementChild.removeEventListener(type, listener, true);
- }
-
- this.node.parentNode.removeChild(this.node);
-
- this._symbiont.destroy();
-
- this.eventListeners = null;
- this._widget = null;
- this._symbiont = null;
- }
- browserManager.init();
|