From 09aed864d0a58807996cc9b0baf497ce701ede13 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Wed, 27 Nov 2013 16:43:04 -0800 Subject: [PATCH] Bug 941933: Uplift Add-on SDK to Firefox. r=me https://github.com/mozilla/addon-sdk/compare/e0ee67e...11344b2 --- .../tutorials/getting-started-with-cfx.md | 2 +- .../doc/module-source/sdk/window/utils.md | 2 +- .../media/screenshots/widget-jquery.png | Bin 16663 -> 17726 bytes .../media/screenshots/widget-mozilla.png | Bin 14393 -> 15298 bytes .../source/lib/sdk/content/content-worker.js | 43 ++++++ addon-sdk/source/lib/sdk/content/loader.js | 10 +- addon-sdk/source/lib/sdk/content/symbiont.js | 26 ++++ addon-sdk/source/lib/sdk/content/worker.js | 22 ++- addon-sdk/source/lib/sdk/event/core.js | 44 ++---- addon-sdk/source/lib/sdk/notifications.js | 27 +++- addon-sdk/source/lib/sdk/page-mod.js | 14 +- addon-sdk/source/lib/sdk/request.js | 4 + addon-sdk/source/lib/sdk/tabs/tab-fennec.js | 7 +- addon-sdk/source/lib/sdk/tabs/tab-firefox.js | 14 +- addon-sdk/source/lib/sdk/view/core.js | 11 +- addon-sdk/source/lib/sdk/widget.js | 79 ++++++----- addon-sdk/source/lib/sdk/window/browser.js | 8 +- addon-sdk/source/lib/sdk/windows/firefox.js | 8 +- addon-sdk/source/lib/sdk/worker/utils.js | 3 - .../test/fixtures/test-contentScriptFile.js | 5 + addon-sdk/source/test/test-content-events.js | 34 +++-- addon-sdk/source/test/test-content-loader.js | 23 +++ addon-sdk/source/test/test-content-worker.js | 131 +++++++++++++++++- addon-sdk/source/test/test-event-core.js | 31 +++-- addon-sdk/source/test/test-event-utils.js | 93 ++++++++++++- addon-sdk/source/test/test-notifications.js | 47 +++++++ addon-sdk/source/test/test-page-mod.js | 44 ++++++ addon-sdk/source/test/test-page-worker.js | 13 +- addon-sdk/source/test/test-request.js | 21 +++ addon-sdk/source/test/test-simple-prefs.js | 4 +- addon-sdk/source/test/test-tab.js | 15 ++ addon-sdk/source/test/test-widget.js | 111 +++++++++------ addon-sdk/source/test/test-windows-common.js | 22 +++ 33 files changed, 743 insertions(+), 175 deletions(-) create mode 100644 addon-sdk/source/test/fixtures/test-contentScriptFile.js diff --git a/addon-sdk/source/doc/dev-guide-source/tutorials/getting-started-with-cfx.md b/addon-sdk/source/doc/dev-guide-source/tutorials/getting-started-with-cfx.md index cb56d3b53c37..749b180a52fe 100644 --- a/addon-sdk/source/doc/dev-guide-source/tutorials/getting-started-with-cfx.md +++ b/addon-sdk/source/doc/dev-guide-source/tutorials/getting-started-with-cfx.md @@ -96,7 +96,7 @@ package.json modified: please re-run 'cfx run' alt="Mozilla icon widget" /> Run `cfx run` again, and it will run an instance of Firefox. In the -bottom-right corner of the browser you'll see an icon with the Firefox +bottom-right corner of the browser you'll see an icon with the Mozilla logo. Click the icon, and a new tab will open with [http://www.mozilla.org/](http://www.mozilla.org/) loaded into it. diff --git a/addon-sdk/source/doc/module-source/sdk/window/utils.md b/addon-sdk/source/doc/module-source/sdk/window/utils.md index 22135da28b5e..e8e1f394330e 100644 --- a/addon-sdk/source/doc/module-source/sdk/window/utils.md +++ b/addon-sdk/source/doc/module-source/sdk/window/utils.md @@ -89,7 +89,7 @@ to support private browsing, refer to the var utils = require('sdk/window/utils'); var browserWindow = utils.getMostRecentBrowserWindow(); var window = browserWindow.content; // `window` object for the current webpage - utils.getToplevelWindw(window) == browserWindow // => true + utils.getToplevelWindow(window) == browserWindow // => true @param window {nsIDOMWindow} @returns {nsIDOMWindow} diff --git a/addon-sdk/source/doc/static-files/media/screenshots/widget-jquery.png b/addon-sdk/source/doc/static-files/media/screenshots/widget-jquery.png index 4b0ad66bb7b178200f088ecfc7cb6e78725c2f54..ce1cbdcb67781b16e41d07822bcf65977f9c8911 100644 GIT binary patch literal 17726 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 M07*qoM6N<$g7`z$8~^|S literal 16663 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 A+W-In diff --git a/addon-sdk/source/doc/static-files/media/screenshots/widget-mozilla.png b/addon-sdk/source/doc/static-files/media/screenshots/widget-mozilla.png index 29a26a304a76838c3ef5eadd29bb46cbf4c7a23a..ed0555b64633bebb8adb46988b07f2e92d587b31 100644 GIT binary patch literal 15298 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 Jk0yrxd>D?JcDv3G(_q3iqwu9%4W_hcl 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 literal 14393 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 chromeAPI.sandbox.evaluate(exports, code); + } else if (typeof timer.fun != 'function') { + throw new Error('Unsupported callback type' + typeof timer.fun); + } + let id = registerMethod(onFire, timer.delay); function onFire() { try { @@ -145,12 +153,47 @@ const ContentWorker = Object.freeze({ timer.fun.apply(null, timer.args); } catch(e) { console.exception(e); + let wrapper = { + instanceOfError: instanceOf(e, Error), + value: e, + }; + if (wrapper.instanceOfError) { + wrapper.value = { + message: e.message, + fileName: e.fileName, + lineNumber: e.lineNumber, + stack: e.stack, + name: e.name, + }; + } + pipe.emit('error', wrapper); } } _timers[id] = timer; return id; } + // copied from sdk/lang/type.js since modules are not available here + function instanceOf(value, Type) { + var isConstructorNameSame; + var isConstructorSourceSame; + + // If `instanceof` returned `true` we know result right away. + var isInstanceOf = value instanceof Type; + + // If `instanceof` returned `false` we do ducktype check since `Type` may be + // from a different sandbox. If a constructor of the `value` or a constructor + // of the value's prototype has same name and source we assume that it's an + // instance of the Type. + if (!isInstanceOf && value) { + isConstructorNameSame = value.constructor.name === Type.name; + isConstructorSourceSame = String(value.constructor) == String(Type); + isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) || + instanceOf(Object.getPrototypeOf(value), Type); + } + return isInstanceOf; + } + function unregisterTimer(id) { if (!(id in _timers)) return; diff --git a/addon-sdk/source/lib/sdk/content/loader.js b/addon-sdk/source/lib/sdk/content/loader.js index 8d4b04be4b4c..77e4a697e372 100644 --- a/addon-sdk/source/lib/sdk/content/loader.js +++ b/addon-sdk/source/lib/sdk/content/loader.js @@ -13,6 +13,7 @@ const { validateOptions } = require('../deprecated/api-utils'); const { isValidURI, URL } = require('../url'); const file = require('../io/file'); const { contract } = require('../util/contract'); +const { isString, instanceOf } = require('../lang/type'); const LOCAL_URI_SCHEMES = ['resource', 'data']; @@ -32,7 +33,7 @@ const valid = { msg: 'The `contentURL` option must be a valid URL.' }, contentScriptFile: { - is: ['undefined', 'null', 'string', 'array'], + is: ['undefined', 'null', 'string', 'array', 'object'], map: ensureNull, ok: function(value) { if (value === null) @@ -40,8 +41,13 @@ const valid = { value = [].concat(value); - // Make sure every item is a local file URL. + // Make sure every item is a string or an + // URL instance, and also a local file URL. return value.every(function (item) { + + if (!isString(item) && !(item instanceof URL)) + return false; + try { return ~LOCAL_URI_SCHEMES.indexOf(URL(item).scheme); } diff --git a/addon-sdk/source/lib/sdk/content/symbiont.js b/addon-sdk/source/lib/sdk/content/symbiont.js index ec0eae0ebce4..3fc408cdb921 100644 --- a/addon-sdk/source/lib/sdk/content/symbiont.js +++ b/addon-sdk/source/lib/sdk/content/symbiont.js @@ -108,6 +108,27 @@ const Symbiont = Worker.resolve({ this._frame = frame; + if (getDocShell(frame)) { + this._reallyInitFrame(frame); + } + else { + if (this._waitForFrame) { + observers.remove('content-document-global-created', this._waitForFrame); + } + this._waitForFrame = this.__waitForFrame.bind(this, frame); + observers.add('content-document-global-created', this._waitForFrame); + } + }, + + __waitForFrame: function _waitForFrame(frame, win, topic) { + if (frame.contentWindow == win) { + observers.remove('content-document-global-created', this._waitForFrame); + delete this._waitForFrame; + this._reallyInitFrame(frame); + } + }, + + _reallyInitFrame: function _reallyInitFrame(frame) { getDocShell(frame).allowJavascript = this.allow.script; frame.setAttribute("src", this._contentURL); @@ -179,6 +200,11 @@ const Symbiont = Worker.resolve({ * This listener is registered in `Symbiont._initFrame`. */ _unregisterListener: function _unregisterListener() { + if (this._waitForFrame) { + observers.remove('content-document-global-created', this._waitForFrame); + delete this._waitForFrame; + } + if (!this._loadListener) return; if (this._loadEvent == "start") { diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js index cd37ea913aaf..3efcf90d2139 100644 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ b/addon-sdk/source/lib/sdk/content/worker.js @@ -202,8 +202,15 @@ const WorkerSandbox = EventEmitter.compose({ clearInterval: 'r' } }, + sandbox: { + evaluate: evaluate, + __exposedProps__: { + evaluate: 'r', + } + }, __exposedProps__: { - timers: 'r' + timers: 'r', + sandbox: 'r', } }; let onEvent = this._onContentEvent.bind(this); @@ -233,6 +240,19 @@ const WorkerSandbox = EventEmitter.compose({ self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments); }); + // unwrap, recreate and propagate async Errors thrown from content-script + this.on("error", function onError({instanceOfError, value}) { + if (self._addonWorker) { + let error = value; + if (instanceOfError) { + error = new Error(value.message, value.fileName, value.lineNumber); + error.stack = value.stack; + error.name = value.name; + } + self._addonWorker._emit('error', error); + } + }); + // Inject `addon` global into target document if document is trusted, // `addon` in document is equivalent to `self` in content script. if (worker._injectInDocument) { diff --git a/addon-sdk/source/lib/sdk/event/core.js b/addon-sdk/source/lib/sdk/event/core.js index 76ead4142ba0..b45428d7bbcb 100644 --- a/addon-sdk/source/lib/sdk/event/core.js +++ b/addon-sdk/source/lib/sdk/event/core.js @@ -36,7 +36,7 @@ const observers = function observers(target, type) { * The listener function that processes the event. */ function on(target, type, listener) { - if (typeof(listener) !== 'function') + if (typeof(listener) !== 'function') throw new Error(BAD_LISTENER); let listeners = observers(target, type); @@ -56,9 +56,9 @@ exports.on = on; * The listener function that processes the event. */ function once(target, type, listener) { - on(target, type, function observer() { + on(target, type, function observer(...args) { off(target, type, observer); - listener.apply(target, arguments); + listener.apply(target, args); }); } exports.once = once; @@ -74,40 +74,24 @@ exports.once = once; * Event target object. * @param {String} type * The type of event. - * @params {Object|Number|String|Boolean} message - * First argument that will be passed to listeners. - * @params {Object|Number|String|Boolean} ... - * More arguments that will be passed to listeners. + * @params {Object|Number|String|Boolean} args + * Arguments that will be passed to listeners. */ -function emit(target, type, message /*, ...*/) { - for each (let item in emit.lazy.apply(emit.lazy, arguments)) { - // We just iterate, iterator take care of emitting events. - } -} - -/** - * This is very experimental feature that you should not use unless absolutely - * need it. Also it may be removed at any point without any further notice. - * - * Creates lazy iterator of return values of listeners. You can think of it - * as lazy array of return values of listeners for the `emit` with the given - * arguments. - */ -emit.lazy = function lazy(target, type, message /*, ...*/) { - let args = Array.slice(arguments, 2); +function emit (target, type, ...args) { let state = observers(target, type); let listeners = state.slice(); - let index = 0; let count = listeners.length; + let index = 0; // If error event and there are no handlers then print error message // into a console. - if (count === 0 && type === 'error') console.exception(message); + if (count === 0 && type === 'error') console.exception(args[0]); while (index < count) { try { let listener = listeners[index]; // Dispatch only if listener is still registered. - if (~state.indexOf(listener)) yield listener.apply(target, args); + if (~state.indexOf(listener)) + listener.apply(target, args); } catch (error) { // If exception is not thrown by a error listener and error listener is @@ -115,8 +99,10 @@ emit.lazy = function lazy(target, type, message /*, ...*/) { if (type !== 'error') emit(target, 'error', error); else console.exception(error); } - index = index + 1; + index++; } + // Also emit on `"*"` so that one could listen for all events. + if (type !== '*') emit(target, '*', type, ...args); } exports.emit = emit; @@ -145,7 +131,7 @@ function off(target, type, listener) { } else if (length === 1) { let listeners = event(target); - Object.keys(listeners).forEach(function(type) delete listeners[type]); + Object.keys(listeners).forEach(type => delete listeners[type]); } } exports.off = off; @@ -171,7 +157,7 @@ exports.count = count; * Dictionary of listeners. */ function setListeners(target, listeners) { - Object.keys(listeners || {}).forEach(function onEach(key) { + Object.keys(listeners || {}).forEach(key => { let match = EVENT_TYPE_PATTERN.exec(key); let type = match && match[1].toLowerCase(); let listener = listeners[key]; diff --git a/addon-sdk/source/lib/sdk/notifications.js b/addon-sdk/source/lib/sdk/notifications.js index 0cf1885b74bd..362f2858617d 100644 --- a/addon-sdk/source/lib/sdk/notifications.js +++ b/addon-sdk/source/lib/sdk/notifications.js @@ -11,6 +11,10 @@ module.metadata = { const { Cc, Ci, Cr } = require("chrome"); const apiUtils = require("./deprecated/api-utils"); const errors = require("./deprecated/errors"); +const { isString, isUndefined, instanceOf } = require('./lang/type'); +const { URL } = require('./url'); + +const NOTIFICATION_DIRECTIONS = ["auto", "ltr", "rtl"]; try { let alertServ = Cc["@mozilla.org/alerts-service;1"]. @@ -36,7 +40,7 @@ exports.notify = function notifications_notify(options) { }; function notifyWithOpts(notifyFn) { notifyFn(valOpts.iconURL, valOpts.title, valOpts.text, !!clickObserver, - valOpts.data, clickObserver); + valOpts.data, clickObserver, valOpts.tag, valOpts.dir, valOpts.lang); } try { notifyWithOpts(notify); @@ -66,15 +70,32 @@ function validateOptions(options) { is: ["string", "undefined"] }, iconURL: { - is: ["string", "undefined"] + is: ["string", "undefined", "object"], + ok: function(value) { + return isUndefined(value) || isString(value) || (value instanceof URL); + }, + msg: "`iconURL` must be a string or an URL instance." }, onClick: { is: ["function", "undefined"] }, text: { - is: ["string", "undefined"] + is: ["string", "undefined", "number"] }, title: { + is: ["string", "undefined", "number"] + }, + tag: { + is: ["string", "undefined", "number"] + }, + dir: { + is: ["string", "undefined"], + ok: function(value) { + return isUndefined(value) || ~NOTIFICATION_DIRECTIONS.indexOf(value); + }, + msg: '`dir` option must be one of: "auto", "ltr" or "rtl".' + }, + lang: { is: ["string", "undefined"] } }); diff --git a/addon-sdk/source/lib/sdk/page-mod.js b/addon-sdk/source/lib/sdk/page-mod.js index e6fd4c185d91..dd445c1e82f7 100644 --- a/addon-sdk/source/lib/sdk/page-mod.js +++ b/addon-sdk/source/lib/sdk/page-mod.js @@ -136,18 +136,18 @@ const PageMod = Loader.compose(EventEmitter, { _applyOnExistingDocuments: function _applyOnExistingDocuments() { let mod = this; - // Returns true if the tab match one rule - let tabs = getAllTabs().filter(function (tab) { - return mod.include.matchesAny(getTabURI(tab)); - }); + let tabs = getAllTabs(); tabs.forEach(function (tab) { // Fake a newly created document let window = getTabContentWindow(tab); - if (has(mod.attachTo, "top")) + if (has(mod.attachTo, "top") && mod.include.matchesAny(getTabURI(tab))) mod._onContent(window); - if (has(mod.attachTo, "frame")) - getFrames(window).forEach(mod._onContent); + if (has(mod.attachTo, "frame")) { + getFrames(window). + filter((iframe) => mod.include.matchesAny(iframe.location.href)). + forEach(mod._onContent); + } }); }, diff --git a/addon-sdk/source/lib/sdk/request.js b/addon-sdk/source/lib/sdk/request.js index 932deed9d376..ccf9bfb0342f 100644 --- a/addon-sdk/source/lib/sdk/request.js +++ b/addon-sdk/source/lib/sdk/request.js @@ -130,6 +130,10 @@ const Request = Class({ request(this).contentType = validateSingleOption('contentType', value); }, get response() { return request(this).response; }, + delete: function() { + runRequest('DELETE', this); + return this; + }, get: function() { runRequest('GET', this); return this; diff --git a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js index 70a7659beb68..1beb9c8a86ef 100644 --- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js +++ b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js @@ -13,6 +13,7 @@ const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL, getTabConten const { emit } = require('../event/core'); const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils'); const { when: unload } = require('../system/unload'); +const { viewFor } = require('../event/core'); const { EVENTS } = require('./events'); const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec'; @@ -33,7 +34,7 @@ const Tab = Class({ // TabReady let onReady = tabInternals.onReady = onTabReady.bind(this); tab.browser.addEventListener(EVENTS.ready.dom, onReady, false); - + let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this); tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow, false); @@ -176,6 +177,10 @@ const Tab = Class({ }); exports.Tab = Tab; +// Implement `viewFor` polymorphic function for the Tab +// instances. +viewFor.define(Tab, x => tabNS(x).tab); + function cleanupTab(tab) { let tabInternals = tabNS(tab); if (!tabInternals.tab) diff --git a/addon-sdk/source/lib/sdk/tabs/tab-firefox.js b/addon-sdk/source/lib/sdk/tabs/tab-firefox.js index 17043df33c60..7f26f6551cef 100644 --- a/addon-sdk/source/lib/sdk/tabs/tab-firefox.js +++ b/addon-sdk/source/lib/sdk/tabs/tab-firefox.js @@ -13,9 +13,10 @@ const { getFaviconURIForLocation } = require("../io/data"); const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle, setTabTitle, getTabURL, setTabURL, getTabContentType, getTabId } = require('./utils'); const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils'); -const viewNS = require('sdk/core/namespace').ns(); -const { deprecateUsage } = require('sdk/util/deprecate'); -const { getURL } = require('sdk/url/utils'); +const viewNS = require('../core/namespace').ns(); +const { deprecateUsage } = require('../util/deprecate'); +const { getURL } = require('../url/utils'); +const { viewFor } = require('../view/core'); // Array of the inner instances of all the wrapped tabs. const TABS = []; @@ -64,6 +65,7 @@ const TabTrait = Trait.compose(EventEmitter, { viewNS(this._public).tab = this._tab; getPBOwnerWindow.implement(this._public, getChromeTab); + viewFor.implement(this._public, getTabView); // Add tabs to getURL method getURL.implement(this._public, (function (obj) this._public.url).bind(this)); @@ -97,7 +99,7 @@ const TabTrait = Trait.compose(EventEmitter, { if (event.target == this._contentDocument) this._emit(EVENTS.ready.name, this._public); }, - + /** * Internal listener that emits public event 'load' when the page of this * tab is loaded, for triggering on non-HTML content, bug #671305 @@ -272,6 +274,10 @@ function getChromeTab(tab) { return getOwnerWindow(viewNS(tab).tab); } +// Implement `viewFor` polymorphic function for the Tab +// instances. +const getTabView = tab => viewNS(tab).tab; + function Tab(options, existingOnly) { let chromeTab = options.tab; for each (let tab in TABS) { diff --git a/addon-sdk/source/lib/sdk/view/core.js b/addon-sdk/source/lib/sdk/view/core.js index 19a8c4a4c1fb..b321e8527827 100644 --- a/addon-sdk/source/lib/sdk/view/core.js +++ b/addon-sdk/source/lib/sdk/view/core.js @@ -16,13 +16,12 @@ var method = require("method/core"); // it returns `null`. You can implement this method for // this type to define what the result should be for it. let getNodeView = method("getNodeView"); -getNodeView.define(function(value) { - if (value instanceof Ci.nsIDOMNode) - return value; - return null; -}); - +getNodeView.define(x => + x instanceof Ci.nsIDOMNode ? x : + x instanceof Ci.nsIDOMWindow ? x : + null); exports.getNodeView = getNodeView; +exports.viewFor = getNodeView; let getActiveView = method("getActiveView"); exports.getActiveView = getActiveView; diff --git a/addon-sdk/source/lib/sdk/widget.js b/addon-sdk/source/lib/sdk/widget.js index da37620db285..51023336dff4 100644 --- a/addon-sdk/source/lib/sdk/widget.js +++ b/addon-sdk/source/lib/sdk/widget.js @@ -26,6 +26,8 @@ const ERR_CONTENT = "No content or contentURL property found. Widgets must " "position.", ERR_DESTROYED = "The widget has been destroyed and can no longer be used."; +const INSERTION_PREF_ROOT = "extensions.sdk-widget-inserted."; + // Supported events, mapping from DOM event names to our event names const EVENTS = { "click": "click", @@ -33,6 +35,11 @@ const EVENTS = { "mouseout": "mouseout", }; +// In the Australis menu panel, normally widgets should be treated like +// normal toolbarbuttons. If they're any wider than this margin, we'll +// treat them as wide widgets instead, which fill up the width of the panel: +const AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF = 70; + const { validateOptions } = require("./deprecated/api-utils"); const panels = require("./panel"); const { EventEmitter, EventEmitterTrait } = require("./deprecated/events"); @@ -45,8 +52,8 @@ const { WindowTracker } = require("./deprecated/window-utils"); const { isBrowser } = require("./window/utils"); const { setTimeout } = require("./timers"); const unload = require("./system/unload"); -const { uuid } = require("./util/uuid"); const { getNodeView } = require("./view/core"); +const prefs = require('./preferences/service'); // Data types definition const valid = { @@ -215,6 +222,13 @@ let model = { }; +function saveInserted(widgetId) { + prefs.set(INSERTION_PREF_ROOT + widgetId, true); +} + +function haveInserted(widgetId) { + return prefs.has(INSERTION_PREF_ROOT + widgetId); +} /** * Main Widget class: entry point of the widget API @@ -555,6 +569,9 @@ let browserManager = { 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)); } }; @@ -605,36 +622,14 @@ BrowserWindow.prototype = { 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); - - // Depending on when this gets called, we might be in the right place now. In that case, - // don't run the following code. - if (node.parentNode != palette) { - return; - } - // Otherwise, insert: - let container = this.doc.getElementById(placement.area); - if (container.customizationTarget) { - container = container.customizationTarget; - } - - if (placement.position !== undefined) { - // Find a position: - let items = this.window.CustomizableUI.getWidgetIdsInArea(placement.area); - let itemIndex = placement.position; - for (let l = items.length; itemIndex < l; itemIndex++) { - let realItems = container.getElementsByAttribute("id", items[itemIndex]); - if (realItems[0]) { - container.insertBefore(node, realItems[0]); - break; - } - } - } - if (node.parentNode != container) { - container.appendChild(node); - } + this.window.CustomizableUI.ensureWidgetPlacedInWindow(node.id, this.window); return; } @@ -650,10 +645,14 @@ BrowserWindow.prototype = { } // if widget isn't in any toolbar, add it to the addon-bar - // TODO: we may want some "first-launch" module to do this only on very - // first execution + let needToPropagateCurrentset = false; if (!container) { + if (haveInserted(node.id)) { + return; + } container = this.doc.getElementById("addon-bar"); + saveInserted(node.id); + needToPropagateCurrentset = true; // TODO: find a way to make the following code work when we use "cfx run": // http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#8586 // until then, force display of addon bar directly from sdk code @@ -684,9 +683,11 @@ BrowserWindow.prototype = { // Otherwise, this code will collide with other instance of Widget module // during Firefox startup. See bug 685929. if (ids.indexOf(id) == -1) { - container.setAttribute("currentset", container.currentSet); + 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); } } } @@ -736,7 +737,6 @@ WidgetChrome.prototype.update = function WC_update(updatedItem, property, value) WidgetChrome.prototype._createNode = function WC__createNode() { // XUL element container for widget let node = this._doc.createElement("toolbaritem"); - let guid = String(uuid()); // Temporary work around require("self") failing on unit-test execution ... let jetpackID = "testID"; @@ -753,6 +753,14 @@ WidgetChrome.prototype._createNode = function WC__createNode() { // Bug 626326: Prevent customize toolbar context menu to appear node.setAttribute("context", ""); + // For use in styling by the browser + node.setAttribute("sdkstylewidget", "true"); + // Mark wide widgets as such: + if (this.window.CustomizableUI && + this._widget.width > AUSTRALIS_PANEL_WIDE_WIDGET_CUTOFF) { + node.classList.add("panel-wide-item"); + } + // TODO move into a stylesheet, configurable by consumers. // Either widget.style, exposing the style object, or a URL // (eg, can load local stylesheet file). @@ -784,6 +792,13 @@ WidgetChrome.prototype.fill = function WC_fill() { // until the node is attached to a document. 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); + // add event handlers this.addEventHandlers(); diff --git a/addon-sdk/source/lib/sdk/window/browser.js b/addon-sdk/source/lib/sdk/window/browser.js index 515db0ca7525..3ceb9f124531 100644 --- a/addon-sdk/source/lib/sdk/window/browser.js +++ b/addon-sdk/source/lib/sdk/window/browser.js @@ -12,6 +12,7 @@ const unload = require('../system/unload'); const { isWindowPrivate } = require('../window/utils'); const { EventTarget } = require('../event/target'); const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils'); +const { viewFor } = require('../view/core'); const { deprecateUsage } = require('../util/deprecate'); const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead'; @@ -48,6 +49,7 @@ const BrowserWindow = Class({ }); exports.BrowserWindow = BrowserWindow; -getPBOwnerWindow.define(BrowserWindow, function(window) { - return windowNS(window).window; -}); +const getWindowView = window => windowNS(window).window; + +getPBOwnerWindow.define(BrowserWindow, getWindowView); +viewFor.define(BrowserWindow, getWindowView); diff --git a/addon-sdk/source/lib/sdk/windows/firefox.js b/addon-sdk/source/lib/sdk/windows/firefox.js index 27196fa85422..19f391f8e901 100644 --- a/addon-sdk/source/lib/sdk/windows/firefox.js +++ b/addon-sdk/source/lib/sdk/windows/firefox.js @@ -22,6 +22,7 @@ const { Cc, Ci, Cr } = require('chrome'), const { windowNS } = require('../window/namespace'); const { isPrivateBrowsingSupported } = require('../self'); const { ignoreWindow } = require('sdk/private-browsing/utils'); +const { viewFor } = require('../view/core'); /** * Window trait composes safe wrappers for browser window that are E10S @@ -76,6 +77,7 @@ const BrowserWindowTrait = Trait.compose( windowNS(this._public).window = this._window; getOwnerWindow.implement(this._public, getChromeWindow); + viewFor.implement(this._public, getChromeWindow); return this; }, @@ -84,6 +86,7 @@ const BrowserWindowTrait = Trait.compose( _onLoad: function() { try { this._initWindowTabTracker(); + this._loaded = true; } catch(e) { this._emit('error', e); @@ -94,9 +97,12 @@ const BrowserWindowTrait = Trait.compose( _onUnload: function() { if (!this._window) return; - this._destroyWindowTabTracker(); + if (this._loaded) + this._destroyWindowTabTracker(); + this._emitOnObject(browserWindows, 'close', this._public); this._window = null; + windowNS(this._public).window = null; // Removing reference from the windows array. windows.splice(windows.indexOf(this), 1); this._removeAllListeners(); diff --git a/addon-sdk/source/lib/sdk/worker/utils.js b/addon-sdk/source/lib/sdk/worker/utils.js index 6363abc8e87b..2b53d3529b21 100644 --- a/addon-sdk/source/lib/sdk/worker/utils.js +++ b/addon-sdk/source/lib/sdk/worker/utils.js @@ -76,9 +76,6 @@ function Worker(options) { ["pageshow", "pagehide", "detach", "message", "error"].forEach(function(key) { trait.on(key, function() { emit.apply(emit, [worker, key].concat(Array.slice(arguments))); - // Workaround lack of ability to listen on all events by emulating - // such ability. This will become obsolete once Bug 821065 is fixed. - emit.apply(emit, [worker, "*", key].concat(Array.slice(arguments))); }); }); traits.set(worker, trait); diff --git a/addon-sdk/source/test/fixtures/test-contentScriptFile.js b/addon-sdk/source/test/fixtures/test-contentScriptFile.js new file mode 100644 index 000000000000..7dc0e3f24257 --- /dev/null +++ b/addon-sdk/source/test/fixtures/test-contentScriptFile.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +self.postMessage("msg from contentScriptFile"); diff --git a/addon-sdk/source/test/test-content-events.js b/addon-sdk/source/test/test-content-events.js index 9ec694f2530c..dd0bbf91f662 100644 --- a/addon-sdk/source/test/test-content-events.js +++ b/addon-sdk/source/test/test-content-events.js @@ -44,16 +44,9 @@ exports["test multiple tabs"] = function(assert, done) { on(events, "data", handler); function handler ({type, target, timeStamp}) { - // ignore about:blank pages and *-document-global-created - // events that are not very consistent. - // ignore http:// requests, as Fennec's `about:home` page - // displays add-ons a user could install - if (target.URL !== "about:blank" && - target.URL !== "about:home" && - !target.URL.match(/^https?:\/\//i) && - type !== "chrome-document-global-created" && - type !== "content-document-global-created") + eventFilter(type, target, () => { actual.push(type + " -> " + target.URL) + }); } let window = getMostRecentBrowserWindow(); @@ -92,12 +85,9 @@ exports["test nested frames"] = function(assert, done) { let actual = []; on(events, "data", handler); function handler ({type, target, timeStamp}) { - // ignore about:blank pages and *-global-created - // events that are not very consistent. - if (target.URL !== "about:blank" && - type !== "chrome-document-global-created" && - type !== "content-document-global-created") + eventFilter(type, target, () => { actual.push(type + " -> " + target.URL) + }); } let window = getMostRecentBrowserWindow(); @@ -126,4 +116,20 @@ exports["test nested frames"] = function(assert, done) { }); }; +// ignore about:blank pages and *-document-global-created +// events that are not very consistent. +// ignore http:// requests, as Fennec's `about:home` page +// displays add-ons a user could install +// ignore local `searchplugins` files loaded +// Calls callback if passes filter +function eventFilter (type, target, callback) { + if (target.URL !== "about:blank" && + target.URL !== "about:home" && + !target.URL.match(/^https?:\/\//i) && + !target.URL.match(/searchplugins/) && + type !== "chrome-document-global-created" && + type !== "content-document-global-created") + + callback(); +} require("test").run(exports); diff --git a/addon-sdk/source/test/test-content-loader.js b/addon-sdk/source/test/test-content-loader.js index 5690369fee2d..7bd455b97769 100644 --- a/addon-sdk/source/test/test-content-loader.js +++ b/addon-sdk/source/test/test-content-loader.js @@ -6,6 +6,7 @@ const { Loader } = require('sdk/content/loader'); const self = require("sdk/self"); const fixtures = require("./fixtures"); +const { URL } = require('sdk/url'); exports['test:contentURL'] = function(assert) { let loader = Loader(), @@ -204,6 +205,28 @@ exports['test:contentScriptFile'] = function(assert) { ); } + let data = 'data:text/html,test'; + try { + loader.contentScriptFile = [ { toString: () => data } ]; + test.fail('must throw when non-URL object is set'); + } catch(e) { + assert.equal( + 'The `contentScriptFile` option must be a local URL or an array of URLs.', + e.message + ); + } + + loader.contentScriptFile = new URL(data); + assert.ok( + loader.contentScriptFile instanceof URL, + 'must be able to set `contentScriptFile` to an instance of URL' + ); + assert.equal( + data, + loader.contentScriptFile.toString(), + 'setting `contentScriptFile` to an instance of URL should preserve the url' + ); + loader.contentScriptFile = undefined; assert.equal( null, diff --git a/addon-sdk/source/test/test-content-worker.js b/addon-sdk/source/test/test-content-worker.js index 2fa8d9621f9c..152c5803da9f 100644 --- a/addon-sdk/source/test/test-content-worker.js +++ b/addon-sdk/source/test/test-content-worker.js @@ -17,6 +17,10 @@ const { LoaderWithHookedConsole } = require("sdk/test/loader"); const { Worker } = require("sdk/content/worker"); const { close } = require("sdk/window/helpers"); const { set: setPref } = require("sdk/preferences/service"); +const { isArray } = require("sdk/lang/type"); +const { URL } = require('sdk/url'); +const fixtures = require("./fixtures"); + const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo"; @@ -396,8 +400,116 @@ exports["test:ensure console.xxx works in cs"] = WorkerTest( } ); +exports["test:setTimeout works with string argument"] = WorkerTest( + "data:text/html;charset=utf-8,", + function(assert, browser, done) { + let worker = Worker({ + window: browser.contentWindow, + contentScript: "new " + function ContentScriptScope() { + // must use "window.scVal" instead of "var csVal" + // since we are inside ContentScriptScope function. + // i'm NOT putting code-in-string inside code-in-string + window.csVal = 13; + setTimeout("self.postMessage([" + + "csVal, " + + "window.docVal, " + + "'ContentWorker' in window, " + + "'UNWRAP_ACCESS_KEY' in window, " + + "'getProxyForObject' in window, " + + "])", 1); + }, + contentScriptWhen: "ready", + onMessage: function([csVal, docVal, chrome1, chrome2, chrome3]) { + // test timer code is executed in the correct context + assert.equal(csVal, 13, "accessing content-script values"); + assert.notEqual(docVal, 5, "can't access document values (directly)"); + assert.ok(!chrome1 && !chrome2 && !chrome3, "nothing is leaked from chrome"); + done(); + } + }); + } +); -exports["test:setTimeout can\"t be cancelled by content"] = WorkerTest( +exports["test:setInterval works with string argument"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(assert, browser, done) { + let count = 0; + let worker = Worker({ + window: browser.contentWindow, + contentScript: "setInterval('self.postMessage(1)', 50)", + contentScriptWhen: "ready", + onMessage: function(one) { + count++; + assert.equal(one, 1, "got " + count + " message(s) from setInterval"); + if (count >= 3) done(); + } + }); + } +); + +exports["test:setInterval async Errors passed to .onError"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(assert, browser, done) { + let count = 0; + let worker = Worker({ + window: browser.contentWindow, + contentScript: "setInterval(() => { throw Error('ubik') }, 50)", + contentScriptWhen: "ready", + onError: function(err) { + count++; + assert.equal(err.message, "ubik", + "error (corectly) propagated " + count + " time(s)"); + if (count >= 3) done(); + } + }); + } +); + +exports["test:setTimeout throws array, passed to .onError"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(assert, browser, done) { + let worker = Worker({ + window: browser.contentWindow, + contentScript: "setTimeout(function() { throw ['array', 42] }, 1)", + contentScriptWhen: "ready", + onError: function(arr) { + assert.ok(isArray(arr), + "the type of thrown/propagated object is array"); + assert.ok(arr.length==2, + "the propagated thrown array is the right length"); + assert.equal(arr[1], 42, + "element inside the thrown array correctly propagated"); + done(); + } + }); + } +); + +exports["test:setTimeout string arg with SyntaxError to .onError"] = WorkerTest( + DEFAULT_CONTENT_URL, + function(assert, browser, done) { + let worker = Worker({ + window: browser.contentWindow, + contentScript: "setTimeout('syntax 123 error', 1)", + contentScriptWhen: "ready", + onError: function(err) { + assert.equal(err.name, "SyntaxError", + "received SyntaxError thrown from bad code in string argument to setTimeout"); + assert.ok('fileName' in err, + "propagated SyntaxError contains a fileName property"); + assert.ok('stack' in err, + "propagated SyntaxError contains a stack property"); + assert.equal(err.message, "missing ; before statement", + "propagated SyntaxError has the correct (helpful) message"); + assert.equal(err.lineNumber, 1, + "propagated SyntaxError was thrown on the right lineNumber"); + done(); + } + }); + } +); + +exports["test:setTimeout can't be cancelled by content"] = WorkerTest( "data:text/html;charset=utf-8,", function(assert, browser, done) { @@ -700,4 +812,21 @@ exports["test:global postMessage"] = WorkerTest( } ); +exports['test:conentScriptFile as URL instance'] = WorkerTest( + DEFAULT_CONTENT_URL, + function(assert, browser, done) { + + let url = new URL(fixtures.url("test-contentScriptFile.js")); + let worker = Worker({ + window: browser.contentWindow, + contentScriptFile: url, + onMessage: function(msg) { + assert.equal(msg, "msg from contentScriptFile", + "received a wrong message from contentScriptFile"); + done(); + } + }); + } +); + require("test").run(exports); diff --git a/addon-sdk/source/test/test-event-core.js b/addon-sdk/source/test/test-event-core.js index 9102dec11b19..3e28147718e9 100644 --- a/addon-sdk/source/test/test-event-core.js +++ b/addon-sdk/source/test/test-event-core.js @@ -222,23 +222,24 @@ exports['test count'] = function(assert) { assert.equal(count(target, 'foo'), 0, 'listeners unregistered'); }; -exports['test emit.lazy'] = function(assert) { - let target = {}, boom = Error('boom!'), errors = [], actual = [] +exports['test listen to all events'] = function(assert) { + let actual = []; + let target = {}; - on(target, 'error', function error(e) errors.push(e)) + on(target, 'foo', message => actual.push(message)); + on(target, '*', (type, ...message) => { + actual.push([type].concat(message)); + }); + + emit(target, 'foo', 'hello'); + assert.equal(actual[0], 'hello', + 'non-wildcard listeners still work'); + assert.deepEqual(actual[1], ['foo', 'hello'], + 'wildcard listener called'); - on(target, 'a', function() 1); - on(target, 'a', function() {}); - on(target, 'a', function() 2); - on(target, 'a', function() { throw boom }); - on(target, 'a', function() 3); - - for each (let value in emit.lazy(target, 'a')) - actual.push(value); - - assert.deepEqual(actual, [ 1, undefined, 2, 3 ], - 'all results were collected'); - assert.deepEqual(errors, [ boom ], 'errors reporetd'); + emit(target, 'bar', 'goodbye'); + assert.deepEqual(actual[2], ['bar', 'goodbye'], + 'wildcard listener called for unbound event name'); }; require('test').run(exports); diff --git a/addon-sdk/source/test/test-event-utils.js b/addon-sdk/source/test/test-event-utils.js index b0163600a293..1e3604aa7300 100644 --- a/addon-sdk/source/test/test-event-utils.js +++ b/addon-sdk/source/test/test-event-utils.js @@ -5,7 +5,7 @@ 'use strict'; const { on, emit } = require("sdk/event/core"); -const { filter, map, merge, expand } = require("sdk/event/utils"); +const { filter, map, merge, expand, pipe } = require("sdk/event/utils"); const $ = require("./event/helpers"); function isEven(x) !(x % 2) @@ -163,7 +163,96 @@ exports["test expand"] = function(assert) { assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"], "all inputs data merged into one"); -} +}; +exports["test pipe"] = function (assert, done) { + let src = {}; + let dest = {}; + + let aneventCount = 0, multiargsCount = 0; + let wildcardCount = {}; + + on(dest, 'an-event', arg => { + assert.equal(arg, 'my-arg', 'piped argument to event'); + ++aneventCount; + check(); + }); + on(dest, 'multiargs', (...data) => { + assert.equal(data[0], 'a', 'multiple arguments passed via pipe'); + assert.equal(data[1], 'b', 'multiple arguments passed via pipe'); + assert.equal(data[2], 'c', 'multiple arguments passed via pipe'); + ++multiargsCount; + check(); + }); + + on(dest, '*', (name, ...data) => { + wildcardCount[name] = (wildcardCount[name] || 0) + 1; + if (name === 'multiargs') { + assert.equal(data[0], 'a', 'multiple arguments passed via pipe, wildcard'); + assert.equal(data[1], 'b', 'multiple arguments passed via pipe, wildcard'); + assert.equal(data[2], 'c', 'multiple arguments passed via pipe, wildcard'); + } + if (name === 'an-event') + assert.equal(data[0], 'my-arg', 'argument passed via pipe, wildcard'); + check(); + }); + + pipe(src, dest); + + for (let i = 0; i < 3; i++) + emit(src, 'an-event', 'my-arg'); + + emit(src, 'multiargs', 'a', 'b', 'c'); + + function check () { + if (aneventCount === 3 && multiargsCount === 1 && + wildcardCount['an-event'] === 3 && + wildcardCount['multiargs'] === 1) + done(); + } +}; + +exports["test pipe multiple targets"] = function (assert) { + let src1 = {}; + let src2 = {}; + let middle = {}; + let dest = {}; + + pipe(src1, middle); + pipe(src2, middle); + pipe(middle, dest); + + let middleFired = 0; + let destFired = 0; + let src1Fired = 0; + let src2Fired = 0; + + on(src1, '*', () => src1Fired++); + on(src2, '*', () => src2Fired++); + on(middle, '*', () => middleFired++); + on(dest, '*', () => destFired++); + + emit(src1, 'ev'); + assert.equal(src1Fired, 1, 'event triggers in source in pipe chain'); + assert.equal(middleFired, 1, 'event passes through the middle of pipe chain'); + assert.equal(destFired, 1, 'event propagates to end of pipe chain'); + assert.equal(src2Fired, 0, 'event does not fire on alternative chain routes'); + + emit(src2, 'ev'); + assert.equal(src2Fired, 1, 'event triggers in source in pipe chain'); + assert.equal(middleFired, 2, + 'event passes through the middle of pipe chain from different src'); + assert.equal(destFired, 2, + 'event propagates to end of pipe chain from different src'); + assert.equal(src1Fired, 1, 'event does not fire on alternative chain routes'); + + emit(middle, 'ev'); + assert.equal(middleFired, 3, + 'event triggers in source of pipe chain'); + assert.equal(destFired, 3, + 'event propagates to end of pipe chain from middle src'); + assert.equal(src1Fired, 1, 'event does not fire on alternative chain routes'); + assert.equal(src2Fired, 1, 'event does not fire on alternative chain routes'); +}; require('test').run(exports); diff --git a/addon-sdk/source/test/test-notifications.js b/addon-sdk/source/test/test-notifications.js index 4d92d57f16db..d154e3907406 100644 --- a/addon-sdk/source/test/test-notifications.js +++ b/addon-sdk/source/test/test-notifications.js @@ -24,6 +24,53 @@ exports.testOnClick = function (assert) { loader.unload(); }; +exports['test:numbers and URLs in options'] = function(assert) { + let [loader] = makeLoader(module); + let notifs = loader.require('sdk/notifications'); + let opts = { + title: 123, + text: 45678, + // must use in-loader `sdk/url` module for the validation type check to work + iconURL: loader.require('sdk/url').URL('data:image/png,blah') + }; + try { + notifs.notify(opts); + assert.pass('using numbers and URLs in options works'); + } catch (e) { + assert.fail('using numbers and URLs in options must not throw'); + } + loader.unload(); +} + +exports['test:new tag, dir and lang options'] = function(assert) { + let [loader] = makeLoader(module); + let notifs = loader.require('sdk/notifications'); + let opts = { + title: 'best', + tag: 'tagging', + lang: 'en' + }; + + try { + opts.dir = 'ttb'; + notifs.notify(opts); + assert.fail('`dir` option must not accept TopToBottom direction.'); + } catch (e) { + assert.equal(e.message, + '`dir` option must be one of: "auto", "ltr" or "rtl".'); + } + + try { + opts.dir = 'rtl'; + notifs.notify(opts); + assert.pass('`dir` option accepts "rtl" direction.'); + } catch (e) { + assert.fail('`dir` option must accept "rtl" direction.'); + } + + loader.unload(); +} + // Returns [loader, mockAlertService]. function makeLoader(module) { let loader = Loader(module); diff --git a/addon-sdk/source/test/test-page-mod.js b/addon-sdk/source/test/test-page-mod.js index b17ffe8034b7..6e494bcbd382 100644 --- a/addon-sdk/source/test/test-page-mod.js +++ b/addon-sdk/source/test/test-page-mod.js @@ -430,6 +430,50 @@ exports.testWorksWithExistingTabs = function(assert, done) { }); }; +exports.testExistingFrameDoesntMatchInclude = function(assert, done) { + let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-42'; + let iframe = '