☠☠ backed out by 34941dd46be6 ☠ ☠ | |
author | Erik Vold <evold@mozilla.com> |
Tue, 11 Mar 2014 20:43:42 -0700 | |
changeset 173150 | bd0463063293e9e2fdfcea2dce80bc849316af0f |
parent 173149 | 9fe5610363b3f8e35f248bb2ce726c8e2665a8a1 |
child 173151 | 31f9a148b33cb6f6eb509df29a339b5de4f65254 |
push id | 26391 |
push user | cbook@mozilla.com |
push date | Wed, 12 Mar 2014 11:20:34 +0000 |
treeherder | mozilla-central@a56837cfc67c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 982379 |
milestone | 30.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100644 --- /dev/null +++ b/addon-sdk/source/lib/sdk/input/customizable-ui.js @@ -0,0 +1,40 @@ +/* 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/. */ +"use strict"; + +const { Cu } = require("chrome"); + +// Because Firefox Holly, we still need to check if `CustomizableUI` is +// available. Once Australis will officially land, we can safely remove it. +// See Bug 959142 +try { + Cu.import("resource:///modules/CustomizableUI.jsm", {}); +} +catch (e) { + throw Error("Unsupported Application: The module" + module.id + + " does not support this application."); +} + +const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); +const { receive } = require("../event/utils"); +const { InputPort } = require("./system"); +const { object} = require("../util/sequence"); +const { getOuterId } = require("../window/utils"); + +const Input = function() {}; +Input.prototype = Object.create(InputPort.prototype); + +Input.prototype.onCustomizeStart = function (window) { + receive(this, object([getOuterId(window), true])); +} + +Input.prototype.onCustomizeEnd = function (window) { + receive(this, object([getOuterId(window), null])); +} + +Input.prototype.addListener = input => CustomizableUI.addListener(input); + +Input.prototype.removeListener = input => CustomizableUI.removeListener(input); + +exports.CustomizationInput = Input;
--- a/addon-sdk/source/lib/sdk/input/system.js +++ b/addon-sdk/source/lib/sdk/input/system.js @@ -43,33 +43,39 @@ const InputPort = function InputPort({id // InputPort type implements `Input` signal interface. InputPort.prototype = new Input(); InputPort.prototype.constructor = InputPort; // When port is started (which is when it's subgraph get's // first subscriber) actual observer is registered. InputPort.start = input => { - addObserver(input, input.topic, false); + input.addListener(input); // Also register add-on unload observer to end this signal // when that happens. addObserver(input, addonUnloadTopic, false); }; InputPort.prototype[start] = InputPort.start; +InputPort.addListener = input => addObserver(input, input.topic, false); +InputPort.prototype.addListener = InputPort.addListener; + // When port is stopped (which is when it's subgraph has no // no subcribers left) an actual observer unregistered. // Note that port stopped once it ends as well (which is when // add-on is unloaded). InputPort.stop = input => { - removeObserver(input, input.topic); + input.removeListener(input); removeObserver(input, addonUnloadTopic); }; InputPort.prototype[stop] = InputPort.stop; +InputPort.removeListener = input => removeObserver(input, input.topic); +InputPort.prototype.removeListener = InputPort.removeListener; + // `InputPort` also implements `nsIObserver` interface and // `nsISupportsWeakReference` interfaces as it's going to be used as such. InputPort.prototype.QueryInterface = function(iid) { if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference)) throw Cr.NS_ERROR_NO_INTERFACE; return this; };
--- a/addon-sdk/source/lib/sdk/panel.js +++ b/addon-sdk/source/lib/sdk/panel.js @@ -8,17 +8,16 @@ module.metadata = { "stability": "stable", "engines": { "Firefox": "*" } }; const { Ci } = require("chrome"); -const { validateOptions: valid } = require('./deprecated/api-utils'); const { setTimeout } = require('./timers'); const { isPrivateBrowsingSupported } = require('./self'); const { isWindowPBSupported } = require('./private-browsing/utils'); const { Class } = require("./core/heritage"); const { merge } = require("./util/object"); const { WorkerHost, detach, attach, destroy } = require("./content/utils"); const { Worker } = require("./content/worker"); const { Disposable } = require("./core/disposable"); @@ -26,39 +25,46 @@ const { contract: loaderContract } = req const { contract } = require("./util/contract"); const { on, off, emit, setListeners } = require("./event/core"); const { EventTarget } = require("./event/target"); const domPanel = require("./panel/utils"); const { events } = require("./panel/events"); const systemEvents = require("./system/events"); const { filter, pipe, stripListeners } = require("./event/utils"); const { getNodeView, getActiveView } = require("./view/core"); -const { isNil, isObject } = require("./lang/type"); +const { isNil, isObject, isNumber } = require("./lang/type"); const { getAttachEventType } = require("./content/utils"); +const { number, boolean, object } = require('./deprecated/api-utils'); -let number = { is: ['number', 'undefined', 'null'] }; -let boolean = { is: ['boolean', 'undefined', 'null'] }; +let isRect = ({top, right, bottom, left}) => [top, right, bottom, left]. + some(value => isNumber(value) && !isNaN(value)); + +let isSDKObj = obj => obj instanceof Class; let rectContract = contract({ top: number, right: number, bottom: number, left: number }); -let rect = { - is: ['object', 'undefined', 'null'], - map: function(v) isNil(v) || !isObject(v) ? v : rectContract(v) +let position = { + is: object, + map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v), + ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)), + msg: 'The option "position" must be a SDK object registered as anchor; ' + + 'or an object with one or more of the following keys set to numeric ' + + 'values: top, right, bottom, left.' } let displayContract = contract({ width: number, height: number, focus: boolean, - position: rect + position: position }); let panelContract = contract(merge({}, displayContract.rules, loaderContract.rules)); function isDisposed(panel) !views.has(panel); let panels = new WeakMap(); @@ -171,32 +177,32 @@ const Panel = Class({ // reatached once panel content is ready. detach(workerFor(this)); }, /* Public API: Panel.isShowing */ get isShowing() !isDisposed(this) && domPanel.isOpen(viewFor(this)), /* Public API: Panel.show */ - show: function show(options, anchor) { + show: function show(options={}, anchor) { if (options instanceof Ci.nsIDOMElement) { [anchor, options] = [options, null]; } if (anchor instanceof Ci.nsIDOMElement) { console.warn( "Passing a DOM node to Panel.show() method is an unsupported " + "feature that will be soon replaced. " + "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877" ); } let model = modelFor(this); let view = viewFor(this); - let anchorView = getNodeView(anchor); + let anchorView = getNodeView(anchor || options.position); options = merge({ position: model.position, width: model.width, height: model.height, defaultWidth: model.defaultWidth, defaultHeight: model.defaultHeight, focus: model.focus @@ -234,31 +240,32 @@ const Panel = Class({ } }); exports.Panel = Panel; // Note must be defined only after value to `Panel` is assigned. getActiveView.define(Panel, viewFor); // Filter panel events to only panels that are create by this module. -let panelEvents = filter(events, function({target}) panelFor(target)); +let panelEvents = filter(events, ({target}) => panelFor(target)); // Panel events emitted after panel has being shown. -let shows = filter(panelEvents, function({type}) type === "popupshown"); +let shows = filter(panelEvents, ({type}) => type === "popupshown"); // Panel events emitted after panel became hidden. -let hides = filter(panelEvents, function({type}) type === "popuphidden"); +let hides = filter(panelEvents, ({type}) => type === "popuphidden"); // Panel events emitted after content inside panel is ready. For different // panels ready may mean different state based on `contentScriptWhen` attribute. // Weather given event represents readyness is detected by `getAttachEventType` // helper function. -let ready = filter(panelEvents, function({type, target}) +let ready = filter(panelEvents, ({type, target}) => getAttachEventType(modelFor(panelFor(target))) === type); // Forward panel show / hide events to panel's own event listeners. -on(shows, "data", function({target}) emit(panelFor(target), "show")); -on(hides, "data", function({target}) emit(panelFor(target), "hide")); +on(shows, "data", ({target}) => emit(panelFor(target), "show")); + +on(hides, "data", ({target}) => emit(panelFor(target), "hide")); on(ready, "data", function({target}) { let worker = workerFor(panelFor(target)); attach(worker, domPanel.getContentDocument(target).defaultView); });
--- a/addon-sdk/source/lib/sdk/panel/utils.js +++ b/addon-sdk/source/lib/sdk/panel/utils.js @@ -18,16 +18,18 @@ const { create: createFrame, swapFrameLo const { window: addonWindow } = require("../addon/window"); const { isNil } = require("../lang/type"); const events = require("../system/events"); const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) { + position = position || {}; + let x, y; let hasTop = !isNil(position.top); let hasRight = !isNil(position.right); let hasBottom = !isNil(position.bottom); let hasLeft = !isNil(position.left); let hasWidth = !isNil(width); let hasHeight = !isNil(height); @@ -122,24 +124,42 @@ function display(panel, options, anchor) // in order to, be positioned properly panel.style.margin = "0"; let viewportRect = document.defaultView.gBrowser.getBoundingClientRect(); ({x, y, width, height}) = calculateRegion(options, viewportRect); } else { + // The XUL Panel has an arrow, so the margin needs to be reset + // to the default value. + panel.style.margin = ""; + let { CustomizableUI, window } = anchor.ownerDocument.defaultView; + + // In Australis, widgets may be positioned in an overflow panel or the + // menu panel. + // In such cases clicking this widget will hide the overflow/menu panel, + // and the widget's panel will show instead. + if (CustomizableUI) { + let node = anchor; + ({anchor}) = CustomizableUI.getWidget(anchor.id).forWindow(window); + + // if `node` is not the `anchor` itself, it means the widget is + // positioned in a panel, therefore we have to hide it before show + // the widget's panel in the same anchor + if (node !== anchor) + CustomizableUI.hidePanelForNode(anchor); + } + width = width || defaultWidth; height = height || defaultHeight; // Open the popup by the anchor. let rect = anchor.getBoundingClientRect(); - let window = anchor.ownerDocument.defaultView; - let zoom = getScreenPixelsPerCSSPixel(window); let screenX = rect.left + window.mozInnerScreenX * zoom; let screenY = rect.top + window.mozInnerScreenY * zoom; // Set up the vertical position of the popup relative to the anchor // (always display the arrow on anchor center) let horizontal, vertical; if (screenY > window.screen.availHeight / 2 + height)
--- a/addon-sdk/source/lib/sdk/system.js +++ b/addon-sdk/source/lib/sdk/system.js @@ -7,17 +7,16 @@ module.metadata = { "stability": "unstable" }; const { Cc, Ci, CC } = require('chrome'); const options = require('@loader/options'); const file = require('./io/file'); const runtime = require("./system/runtime"); -var cfxArgs = require("@test/options"); const appStartup = Cc['@mozilla.org/toolkit/app-startup;1']. getService(Ci.nsIAppStartup); const appInfo = Cc["@mozilla.org/xre/app-info;1"]. getService(Ci.nsIXULAppInfo); const directoryService = Cc['@mozilla.org/file/directory_service;1']. getService(Ci.nsIProperties); @@ -65,23 +64,22 @@ exports.exit = function exit(code) { // This is used by 'cfx' to find out exit code. if ('resultFile' in options && options.resultFile) { let mode = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; let stream = openFile(options.resultFile, mode); let status = code ? 'FAIL' : 'OK'; stream.write(status, status.length); stream.flush(); stream.close(); - if (cfxArgs.parseable) { - console.log('wrote to resultFile'); - } } - forcedExit = true; - appStartup.quit(E_FORCE); + if (code == 0) { + forcedExit = true; + } + appStartup.quit(code ? E_ATTEMPT : E_FORCE); }; // Adapter for nodejs's stdout & stderr: // http://nodejs.org/api/process.html#process_process_stdout let stdout = Object.freeze({ write: dump, end: dump }); exports.stdout = stdout; exports.stderr = stdout;
--- a/addon-sdk/source/lib/sdk/test/runner.js +++ b/addon-sdk/source/lib/sdk/test/runner.js @@ -2,39 +2,31 @@ * 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/. */ "use strict"; module.metadata = { "stability": "experimental" }; -var { setTimeout } = require("../timers"); var { exit, stdout } = require("../system"); var cfxArgs = require("@test/options"); function runTests(findAndRunTests) { var harness = require("./harness"); function onDone(tests) { stdout.write("\n"); var total = tests.passed + tests.failed; stdout.write(tests.passed + " of " + total + " tests passed.\n"); if (tests.failed == 0) { - if (tests.passed === 0) { + if (tests.passed === 0) stdout.write("No tests were run\n"); - } - - setTimeout(function() { - if (cfxArgs.parseable) { - console.log('calling exit(0)'); - } - exit(0); - }, 0); + exit(0); } else { if (cfxArgs.verbose || cfxArgs.parseable) printFailedTests(tests, stdout.write); exit(1); } }; // We may have to run test on next cycle, otherwise XPCOM components
--- a/addon-sdk/source/lib/sdk/ui/button/action.js +++ b/addon-sdk/source/lib/sdk/ui/button/action.js @@ -21,16 +21,17 @@ catch (e) { ' does not support this application.'); } const { Class } = require('../../core/heritage'); const { merge } = require('../../util/object'); const { Disposable } = require('../../core/disposable'); const { on, off, emit, setListeners } = require('../../event/core'); const { EventTarget } = require('../../event/target'); +const { getNodeView } = require('../../view/core'); const view = require('./view'); const { buttonContract, stateContract } = require('./contract'); const { properties, render, state, register, unregister, getDerivedStateFor } = require('../state'); const { events: stateEvents } = require('../state/events'); const { events: viewEvents } = require('./view/events'); const events = require('../../event/utils'); @@ -84,16 +85,20 @@ const ActionButton = Class({ get id() this.state().id, click: function click() { view.click(toWidgetId(this.id)) } }); exports.ActionButton = ActionButton; identify.define(ActionButton, ({id}) => toWidgetId(id)); +getNodeView.define(ActionButton, button => + view.nodeFor(toWidgetId(button.id)) +); + let actionButtonStateEvents = events.filter(stateEvents, e => e.target instanceof ActionButton); let actionButtonViewEvents = events.filter(viewEvents, e => buttons.has(e.target)); let clickEvents = events.filter(actionButtonViewEvents, e => e.type === 'click'); let updateEvents = events.filter(actionButtonViewEvents, e => e.type === 'update');
--- a/addon-sdk/source/lib/sdk/ui/button/toggle.js +++ b/addon-sdk/source/lib/sdk/ui/button/toggle.js @@ -21,16 +21,17 @@ catch (e) { ' does not support this application.'); } const { Class } = require('../../core/heritage'); const { merge } = require('../../util/object'); const { Disposable } = require('../../core/disposable'); const { on, off, emit, setListeners } = require('../../event/core'); const { EventTarget } = require('../../event/target'); +const { getNodeView } = require('../../view/core'); const view = require('./view'); const { toggleButtonContract, toggleStateContract } = require('./contract'); const { properties, render, state, register, unregister, setStateFor, getStateFor, getDerivedStateFor } = require('../state'); const { events: stateEvents } = require('../state/events'); const { events: viewEvents } = require('./view/events'); const events = require('../../event/utils'); @@ -85,16 +86,20 @@ const ToggleButton = Class({ get id() this.state().id, click: function click() view.click(toWidgetId(this.id)) }); exports.ToggleButton = ToggleButton; identify.define(ToggleButton, ({id}) => toWidgetId(id)); +getNodeView.define(ToggleButton, button => + view.nodeFor(toWidgetId(button.id)) +); + let toggleButtonStateEvents = events.filter(stateEvents, e => e.target instanceof ToggleButton); let toggleButtonViewEvents = events.filter(viewEvents, e => buttons.has(e.target)); let clickEvents = events.filter(toggleButtonViewEvents, e => e.type === 'click'); let updateEvents = events.filter(toggleButtonViewEvents, e => e.type === 'update');
--- a/addon-sdk/source/lib/sdk/ui/button/view.js +++ b/addon-sdk/source/lib/sdk/ui/button/view.js @@ -103,16 +103,21 @@ function getImage(icon, isInToolbar, pix } if (image.indexOf('./') === 0) return data.url(image.substr(2)); return image; } +function nodeFor(id, window=getMostRecentBrowserWindow()) { + return customizedWindows.has(window) ? null : getNode(id, window); +}; +exports.nodeFor = nodeFor; + function create(options) { let { id, label, icon, type } = options; if (views.has(id)) throw new Error('The ID "' + id + '" seems already used.'); CustomizableUI.createWidget({ id: id, @@ -178,41 +183,40 @@ function setIcon(id, window, icon) { let image = getImage(icon, isInToolbar(id), window.devicePixelRatio); node.setAttribute('image', image); } } exports.setIcon = setIcon; function setLabel(id, window, label) { - let node = customizedWindows.has(window) ? null : getNode(id, window); + let node = nodeFor(id, window); if (node) { node.setAttribute('label', label); node.setAttribute('tooltiptext', label); } } exports.setLabel = setLabel; function setDisabled(id, window, disabled) { - let node = customizedWindows.has(window) ? null : getNode(id, window); + let node = nodeFor(id, window); if (node) node.disabled = disabled; } exports.setDisabled = setDisabled; function setChecked(id, window, checked) { - let node = customizedWindows.has(window) ? null : getNode(id, window); + let node = nodeFor(id, window); if (node) node.checked = checked; } exports.setChecked = setChecked; function click(id) { - let window = getMostRecentBrowserWindow(); - let node = customizedWindows.has(window) ? null : getNode(id, window); + let node = nodeFor(id); if (node) node.click(); } exports.click = click;
--- a/addon-sdk/source/lib/sdk/ui/frame/model.js +++ b/addon-sdk/source/lib/sdk/ui/frame/model.js @@ -27,27 +27,27 @@ const { id: addonID, data: { url: resolv const { Frames } = require("../../input/frame"); const output = new OutputPort({ id: "frame-change" }); const mailbox = new OutputPort({ id: "frame-mailbox" }); const input = Frames; -const urlToId = url => +const makeID = url => ("frame-" + addonID + "-" + url). split("/").join("-"). split(".").join("-"). replace(/[^A-Za-z0-9_\-]/g, ""); const validate = contract({ - id: { + name: { is: ["string", "undefined"], ok: x => /^[a-z][a-z0-9-_]+$/i.test(x), - msg: "The `option.id` must be a valid alphanumeric string (hyphens and " + + msg: "The `option.name` must be a valid alphanumeric string (hyphens and " + "underscores are allowed) starting with letter." }, url: { map: x => x.toString(), is: ["string"], ok: x => isLocalURL(x), msg: "The `options.url` must be a valid local URI." } @@ -83,17 +83,17 @@ const Message = function({type, data, so const frames = new Map(); const sources = new Map(); const Frame = Class({ extends: EventTarget, implements: [Disposable, Source], initialize: function(params={}) { const options = validate(params); - const id = options.id || urlToId(options.url); + const id = makeID(options.name || options.url); if (frames.has(id)) throw Error("Frame with this id already exists: " + id); const initial = { id: id, url: resolve(options.url) }; this.id = id; setListeners(this, params);
--- a/addon-sdk/source/lib/sdk/ui/state.js +++ b/addon-sdk/source/lib/sdk/ui/state.js @@ -14,17 +14,17 @@ module.metadata = { const { Ci } = require('chrome'); const events = require('../event/utils'); const { events: browserEvents } = require('../browser/events'); const { events: tabEvents } = require('../tab/events'); const { events: stateEvents } = require('./state/events'); -const { windows, isInteractive, getMostRecentBrowserWindow } = require('../window/utils'); +const { windows, isInteractive, getFocusedBrowser } = require('../window/utils'); const { getActiveTab, getOwnerWindow } = require('../tabs/utils'); const { ignoreWindow } = require('../private-browsing/utils'); const { freeze } = Object; const { merge } = require('../util/object'); const { on, off, emit } = require('../event/core'); @@ -42,17 +42,17 @@ const ERR_INVALID_TARGET = 'The state ca 'Only window, tab and registered component are valid targets.'; const isWindow = thing => thing instanceof Ci.nsIDOMWindow; const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab'; const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing)); const isEnumerable = window => !ignoreWindow(window); const browsers = _ => windows('navigator:browser', { includePrivate: true }).filter(isInteractive); -const getMostRecentTab = _ => getActiveTab(getMostRecentBrowserWindow()); +const getMostRecentTab = _ => getActiveTab(getFocusedBrowser()); function getStateFor(component, target) { if (!isRegistered(component)) throw new Error(ERR_UNREGISTERED); if (!components.has(component)) return null; @@ -167,17 +167,17 @@ function properties(contract) { return Object.create(Object.prototype, descriptor); } exports.properties = properties; function state(contract) { return { state: function state(target, state) { - let nativeTarget = target === 'window' ? getMostRecentBrowserWindow() + let nativeTarget = target === 'window' ? getFocusedBrowser() : target === 'tab' ? getMostRecentTab() : viewFor(target); if (!nativeTarget && target !== this && !isNil(target)) throw new Error('target not allowed.'); target = nativeTarget || target;
--- a/addon-sdk/source/lib/sdk/ui/toolbar/view.js +++ b/addon-sdk/source/lib/sdk/ui/toolbar/view.js @@ -11,21 +11,23 @@ module.metadata = { }; const { Cu } = require("chrome"); const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {}); const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/utils"); const { InputPort } = require("../../input/system"); const { OutputPort } = require("../../output/system"); const { Interactive } = require("../../input/browser"); +const { CustomizationInput } = require("../../input/customizable-ui"); const { pairs, map, isEmpty, object, each, keys, values } = require("../../util/sequence"); const { curry, flip } = require("../../lang/functional"); const { patch, diff } = require("diffpatcher/index"); const prefs = require("../../preferences/service"); +const { getByOuterId } = require("../../window/utils"); const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const PREF_ROOT = "extensions.sdk-toolbar-collapsed."; // There are two output ports one for publishing changes that occured // and the other for change requests. Later is synchronous and is only // consumed here. Note: it needs to be synchronous to avoid race conditions @@ -33,18 +35,19 @@ const PREF_ROOT = "extensions.sdk-toolba // toolbar is destroyed between the ticks. const output = new OutputPort({ id: "toolbar-changed" }); const syncoutput = new OutputPort({ id: "toolbar-change", sync: true }); // Merge disptached changes and recevied changes from models to keep state up to // date. const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }), new InputPort({ id: "toolbar-change" })])); -const State = lift((toolbars, windows) => ({windows: windows, toolbars: toolbars}), - Toolbars, Interactive); +const State = lift((toolbars, windows, customizable) => + ({windows: windows, toolbars: toolbars, customizable: customizable}), + Toolbars, Interactive, new CustomizationInput()); // Shared event handler that makes `event.target.parent` collapsed. // Used as toolbar's close buttons click handler. const collapseToolbar = event => { const toolbar = event.target.parentNode; toolbar.collapsed = true; }; @@ -83,77 +86,111 @@ const attributesChanged = mutations => { // it back. In addition it set's up a listener and observer to communicate // state changes. const addView = curry((options, {document}) => { let view = document.createElementNS(XUL_NS, "toolbar"); view.setAttribute("id", options.id); view.setAttribute("collapsed", options.collapsed); view.setAttribute("toolbarname", options.title); view.setAttribute("pack", "end"); - view.setAttribute("defaultset", options.items.join(",")); - view.setAttribute("customizable", true); - view.setAttribute("style", "max-height: 40px;"); + view.setAttribute("customizable", "false"); + view.setAttribute("style", "padding: 2px 0; max-height: 40px;"); view.setAttribute("mode", "icons"); view.setAttribute("iconsize", "small"); view.setAttribute("context", "toolbar-context-menu"); + view.setAttribute("class", "toolbar-primary chromeclass-toolbar"); + + let label = document.createElementNS(XUL_NS, "label"); + label.setAttribute("value", options.title); + label.setAttribute("collapsed", "true"); + view.appendChild(label); let closeButton = document.createElementNS(XUL_NS, "toolbarbutton"); closeButton.setAttribute("id", "close-" + options.id); closeButton.setAttribute("class", "close-icon"); closeButton.setAttribute("customizable", false); closeButton.addEventListener("command", collapseToolbar); view.appendChild(closeButton); + // In order to have a close button not costumizable, aligned on the right, + // leaving the customizable capabilities of Australis, we need to create + // a toolbar inside a toolbar. + // This is should be a temporary hack, we should have a proper XBL for toolbar + // instead. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=982005 + let toolbar = document.createElementNS(XUL_NS, "toolbar"); + toolbar.setAttribute("id", "inner-" + options.id); + toolbar.setAttribute("defaultset", options.items.join(",")); + toolbar.setAttribute("customizable", "true"); + toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden"); + toolbar.setAttribute("mode", "icons"); + toolbar.setAttribute("iconsize", "small"); + toolbar.setAttribute("context", "toolbar-context-menu"); + toolbar.setAttribute("flex", "1"); + + view.insertBefore(toolbar, closeButton); + const observer = new document.defaultView.MutationObserver(attributesChanged); observer.observe(view, { attributes: true, attributeFilter: ["collapsed", "toolbarname"] }); const toolbox = document.getElementById("navigator-toolbox"); toolbox.appendChild(view); }); const viewAdd = curry(flip(addView)); const removeView = curry((id, {document}) => { const view = document.getElementById(id); if (view) view.remove(); }); -const updateView = curry((id, {title, collapsed}, {document}) => { +const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => { const view = document.getElementById(id); - if (view && title) + + if (!view) + return; + + if (title) view.setAttribute("toolbarname", title); - if (view && collapsed !== void(0)) + + if (collapsed !== void(0)) view.setAttribute("collapsed", Boolean(collapsed)); + + if (isCustomizing !== void(0)) { + view.querySelector("label").collapsed = !isCustomizing; + view.querySelector("toolbar").style.visibility = isCustomizing + ? "hidden" : "visible"; + } }); - +const viewUpdate = curry(flip(updateView)); // Utility function used to register toolbar into CustomizableUI. const registerToolbar = state => { // If it's first additon register toolbar as customizableUI component. - CustomizableUI.registerArea(state.id, { + CustomizableUI.registerArea("inner-" + state.id, { type: CustomizableUI.TYPE_TOOLBAR, legacy: true, - defaultPlacements: [...state.items, "close-" + state.id] + defaultPlacements: [...state.items] }); }; // Utility function used to unregister toolbar from the CustomizableUI. const unregisterToolbar = CustomizableUI.unregisterArea; const reactor = new Reactor({ onStep: (present, past) => { const delta = diff(past, present); each(([id, update]) => { // If update is `null` toolbar is removed, in such case // we unregister toolbar and remove it from each window // it was added to. if (update === null) { - unregisterToolbar(id); + unregisterToolbar("inner-" + id); each(removeView(id), values(past.windows)); send(output, object([id, null])); } else if (past.toolbars[id]) { // If `collapsed` state for toolbar was updated, persist // it for a future sessions. if (update.collapsed !== void(0)) @@ -185,17 +222,23 @@ const reactor = new Reactor({ } }, pairs(delta.toolbars)); // Add views to every window that was added. each(window => { if (window) each(viewAdd(window), values(past.toolbars)); }, values(delta.windows)); + + each(([id, isCustomizing]) => { + each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}), + keys(present.toolbars)); + + }, pairs(delta.customizable)) }, onEnd: state => { each(id => { - unregisterToolbar(id); + unregisterToolbar("inner-" + id); each(removeView(id), values(state.windows)); }, keys(state.toolbars)); } }); reactor.run(State);
--- a/addon-sdk/source/lib/sdk/widget.js +++ b/addon-sdk/source/lib/sdk/widget.js @@ -439,38 +439,20 @@ const WidgetViewTrait = LightTrait.compo if ("click" == type || type.indexOf("mouse") == 0) this._baseWidget._onEvent(type, this._public); else this._baseWidget._onEvent(type, eventData); // Special case for click events: if the widget doesn't have a click // handler, but it does have a panel, display the panel. if ("click" == type && !this._listeners("click").length && this.panel) { - // In Australis, widgets may be positioned in an overflow panel or the - // menu panel. - // In such cases clicking this widget will hide the overflow/menu panel, - // and the widget's panel will show instead. - - let anchor = domNode; - let { CustomizableUI, window } = domNode.ownerDocument.defaultView; - - if (CustomizableUI) { - ({anchor}) = CustomizableUI.getWidget(domNode.id).forWindow(window); - - // if `anchor` is not the `domNode` itself, it means the widget is - // positioned in a panel, therefore we have to hide it before show - // the widget's panel in the same anchor - if (anchor !== domNode) - CustomizableUI.hidePanelForNode(domNode); - } - // This kind of ugly workaround, instead we should implement // `getNodeView` for the `Widget` class itself, but that's kind of // hard without cleaning things up. - this.panel.show(null, getNodeView.implement({}, () => anchor)); + this.panel.show(null, getNodeView.implement({}, () => domNode)); } }, _isInWindow: function WidgetView__isInWindow(window) { return windowsAPI.BrowserWindow({ window: this._chrome.window }) == window; }, @@ -790,38 +772,49 @@ WidgetChrome.prototype._createNode = fun node.style.minWidth = this._widget.width + "px"; this.node = node; } // Initial population of a widget's content. WidgetChrome.prototype.fill = function WC_fill() { + let { node, _doc: document } = this; + // Create element - var iframe = this._doc.createElement("iframe"); + let iframe = document.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"; // Do this early, because things like contentWindow are null // until the node is attached to a document. - this.node.appendChild(iframe); + node.appendChild(iframe); - var label = this._doc.createElement("label"); + let label = document.createElement("label"); label.setAttribute("value", this._widget.label); label.className = "toolbarbutton-text"; label.setAttribute("crop", "right"); label.setAttribute("flex", "1"); - this.node.appendChild(label); + node.appendChild(label); + + // This toolbarbutton is created to provide a more consistent user experience + // during customization, see: + // https://bugzilla.mozilla.org/show_bug.cgi?id=959640 + let button = document.createElement("toolbarbutton"); + button.setAttribute("label", this._widget.label); + button.setAttribute("crop", "right"); + button.className = "toolbarbutton-1 chromeclass-toolbar-additional"; + node.appendChild(button); // add event handlers this.addEventHandlers(); // set content this.setContent(); }
--- a/addon-sdk/source/lib/sdk/window/utils.js +++ b/addon-sdk/source/lib/sdk/window/utils.js @@ -14,16 +14,18 @@ const { defer } = require('sdk/core/prom const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. getService(Ci.nsIWindowWatcher); const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. getService(Ci.nsIAppShellService); const WM = Cc['@mozilla.org/appshell/window-mediator;1']. getService(Ci.nsIWindowMediator); const io = Cc['@mozilla.org/network/io-service;1']. getService(Ci.nsIIOService); +const FM = Cc["@mozilla.org/focus-manager;1"]. + getService(Ci.nsIFocusManager); const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; const BROWSER = 'navigator:browser', URI_BROWSER = 'chrome://browser/content/browser.xul', NAME = '_blank', FEATURES = 'chrome,all,dialog=no,non-private'; @@ -352,16 +354,28 @@ exports.isXULBrowser = isXULBrowser; function getFocusedWindow() { let window = WM.getMostRecentWindow(BROWSER); return window ? window.document.commandDispatcher.focusedWindow : null; } exports.getFocusedWindow = getFocusedWindow; /** + * Returns the focused browser window if any, or the most recent one. + * Opening new window, updates most recent window, but focus window + * changes later; so most recent window and focused window are not always + * the same. + */ +function getFocusedBrowser() { + let window = FM.activeWindow; + return isBrowser(window) ? window : getMostRecentBrowserWindow() +} +exports.getFocusedBrowser = getFocusedBrowser; + +/** * Returns the focused element in the most recent focused window */ function getFocusedElement() { let window = WM.getMostRecentWindow(BROWSER); return window ? window.document.commandDispatcher.focusedElement : null; } exports.getFocusedElement = getFocusedElement;
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py @@ -538,33 +538,29 @@ def initializer(env_root, args, out=sys. print >>out, '*', args[1], 'package directory created' except OSError: print >>out, '*', args[1], 'already exists, testing if directory is empty' # avoid clobbering existing files, but we tolerate things like .git existing = [fn for fn in os.listdir(path) if not fn.startswith(".")] if existing: print >>err, 'This command must be run in an empty directory.' return {"result":1} - for d in ['lib','data','test','doc']: + for d in ['lib','data','test']: os.mkdir(os.path.join(path,d)) print >>out, '*', d, 'directory created' - open(os.path.join(path,'README.md'),'w').write('') - print >>out, '* README.md written' jid = create_jid() print >>out, '* generated jID automatically:', jid open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(), 'title':addon, 'id':jid }) print >>out, '* package.json written' open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS) print >>out, '* test/test-main.js written' open(os.path.join(path,'lib','main.js'),'w').write('') print >>out, '* lib/main.js written' - open(os.path.join(path,'doc','main.md'),'w').write('') - print >>out, '* doc/main.md written' if len(args) == 1: print >>out, '\nYour sample add-on is now ready.' print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' else: print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.' print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' return {"result":0, "jid":jid}
--- a/addon-sdk/source/python-lib/cuddlefish/runner.py +++ b/addon-sdk/source/python-lib/cuddlefish/runner.py @@ -731,17 +731,16 @@ def run_app(harness_root_dir, manifest_r sys.stderr.flush() if is_running_tests and parseable: match = PARSEABLE_TEST_NAME.search(new_chars) if match: test_name = match.group(1) if os.path.exists(resultfile): result = open(resultfile).read() if result: - sys.stderr.write("resultfile contained " + "'" + result + "'\n") if result in ['OK', 'FAIL']: done = True else: sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result))) sys.stderr.write("'"+result+"'\n") if enforce_timeouts: if time.time() - last_output_time > OUTPUT_TIMEOUT: raise Timeout("Test output exceeded timeout (%ds)." % @@ -750,19 +749,17 @@ def run_app(harness_root_dir, manifest_r raise Timeout("Test run exceeded timeout (%ds)." % RUN_TIMEOUT, test_name, parseable) except: runner.stop() raise else: runner.wait(10) finally: - sys.stderr.write("Done.\n") outf.close() - sys.stderr.write("Clean the profile.\n") if profile: profile.cleanup() print >>sys.stderr, "Total time: %f seconds" % (time.time() - starttime) if result == 'OK': print >>sys.stderr, "Program terminated successfully." return 0
--- a/addon-sdk/source/test/addons/addon-page/main.js +++ b/addon-sdk/source/test/addons/addon-page/main.js @@ -1,25 +1,19 @@ /* 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/. */ 'use strict'; -module.metadata = { - engines: { - 'Firefox': '*' - } -}; - const { isTabOpen, activateTab, openTab, closeTab, getTabURL, getWindowHoldingTab } = require('sdk/tabs/utils'); const windows = require('sdk/deprecated/window-utils'); const { LoaderWithHookedConsole } = require('sdk/test/loader'); const { setTimeout } = require('sdk/timers'); -const { is } = require('sdk/system/xul-app'); +const app = require("sdk/system/xul-app"); const tabs = require('sdk/tabs'); const isAustralis = "gCustomizeMode" in windows.activeBrowserWindow; const { set: setPref } = require("sdk/preferences/service"); const { defer } = require('sdk/core/promise'); const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; let uri = require('sdk/self').data.url('index.html'); @@ -39,16 +33,20 @@ function closeTabPromise(tab) { return promise; } function isChromeVisible(window) { let x = window.document.documentElement.getAttribute('disablechrome') return x !== 'true'; } +// Once Bug 903018 is resolved, just move the application testing to +// module.metadata.engines +if (app.is('Firefox')) { + exports['test add-on page deprecation message'] = function(assert) { let { loader, messages } = LoaderWithHookedConsole(module); loader.require('sdk/addon-page'); setPref(DEPRECATE_PREF, true); assert.equal(messages.length, 1, "only one error is dispatched"); assert.equal(messages[0].type, "error", "the console message is an error"); @@ -69,17 +67,17 @@ exports['test that add-on page has no ch assert.ok(isChromeVisible(window), 'chrome is visible for non addon page'); // need to do this in another turn to make sure event listener // that sets property has time to do that. setTimeout(function() { activateTab(tab); - assert.equal(isChromeVisible(window), is('Fennec') || isAustralis, + assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis, 'chrome is not visible for addon page'); closeTabPromise(tab).then(function() { assert.ok(isChromeVisible(window), 'chrome is visible again'); loader.unload(); assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload'); done(); }).then(null, assert.fail); @@ -95,17 +93,17 @@ exports['test that add-on page with hash assert.ok(isChromeVisible(window), 'chrome is visible for non addon page'); // need to do this in another turn to make sure event listener // that sets property has time to do that. setTimeout(function() { activateTab(tab); - assert.equal(isChromeVisible(window), is('Fennec') || isAustralis, + assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis, 'chrome is not visible for addon page'); closeTabPromise(tab).then(function() { assert.ok(isChromeVisible(window), 'chrome is visible again'); loader.unload(); assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload'); done(); }).then(null, assert.fail); @@ -121,17 +119,17 @@ exports['test that add-on page with quer assert.ok(isChromeVisible(window), 'chrome is visible for non addon page'); // need to do this in another turn to make sure event listener // that sets property has time to do that. setTimeout(function() { activateTab(tab); - assert.equal(isChromeVisible(window), is('Fennec') || isAustralis, + assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis, 'chrome is not visible for addon page'); closeTabPromise(tab).then(function() { assert.ok(isChromeVisible(window), 'chrome is visible again'); loader.unload(); assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload'); done(); }).then(null, assert.fail); @@ -147,17 +145,17 @@ exports['test that add-on page with hash assert.ok(isChromeVisible(window), 'chrome is visible for non addon page'); // need to do this in another turn to make sure event listener // that sets property has time to do that. setTimeout(function() { activateTab(tab); - assert.equal(isChromeVisible(window), is('Fennec') || isAustralis, + assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis, 'chrome is not visible for addon page'); closeTabPromise(tab).then(function() { assert.ok(isChromeVisible(window), 'chrome is visible again'); loader.unload(); assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload'); done(); }).then(null, assert.fail); @@ -180,9 +178,13 @@ exports['test that malformed uri is not closeTabPromise(tab).then(function() { loader.unload(); done(); }).then(null, assert.fail); }); }; +} else { + exports['test unsupported'] = (assert) => assert.pass('This application is unsupported.'); +} + require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/child_process/index.js +++ b/addon-sdk/source/test/addons/child_process/index.js @@ -1,32 +1,39 @@ /* 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/. */ "use strict"; -const { exec } = require("sdk/system/child_process"); -const { platform, pathFor } = require("sdk/system"); -const PROFILE_DIR = pathFor("ProfD"); -const isWindows = platform.toLowerCase().indexOf("win") === 0; - /** * Ensures using child_process and underlying subprocess.jsm * works within an addon */ -exports["test child_process in an addon"] = (assert, done) => { - exec(isWindows ? "DIR /A-D" : "ls -al", { - cwd: PROFILE_DIR - }, (err, stdout, stderr) => { - assert.ok(!err, "no errors"); - assert.equal(stderr, "", "stderr is empty"); - assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files"); + +const { exec } = require("sdk/system/child_process"); +const { platform, pathFor } = require("sdk/system"); +const PROFILE_DIR = pathFor("ProfD"); +const isWindows = platform.toLowerCase().indexOf("win") === 0; +const app = require("sdk/system/xul-app"); - if (isWindows) - assert.ok(!/<DIR>/.test(stdout), "passing args works"); - else - assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works"); - done(); - }); -}; +// Once Bug 903018 is resolved, just move the application testing to +// module.metadata.engines +if (app.is("Firefox")) { + exports["test child_process in an addon"] = (assert, done) => { + exec(isWindows ? "DIR /A-D" : "ls -al", { + cwd: PROFILE_DIR + }, (err, stdout, stderr) => { + assert.ok(!err, "no errors"); + assert.equal(stderr, "", "stderr is empty"); + assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files"); + if (isWindows) + assert.ok(!/<DIR>/.test(stdout), "passing args works"); + else + assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works"); + done(); + }); + }; +} else { + exports["test unsupported"] = (assert) => assert.pass("This application is unsupported."); +} require("sdk/test/runner").runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/places/main.js +++ b/addon-sdk/source/test/addons/places/main.js @@ -1,24 +1,27 @@ /* 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/. */ 'use strict'; -module.metadata = { - 'engines': { - 'Firefox': '*' - } -}; - const { safeMerge: merge } = require('sdk/util/object'); +const app = require("sdk/system/xul-app"); -merge(module.exports, - require('./tests/test-places-bookmarks'), - require('./tests/test-places-events'), - require('./tests/test-places-favicon'), - require('./tests/test-places-history'), - require('./tests/test-places-host'), - require('./tests/test-places-utils') -); +// Once Bug 903018 is resolved, just move the application testing to +// module.metadata.engines +if (app.is('Firefox')) { + merge(module.exports, + require('./tests/test-places-bookmarks'), + require('./tests/test-places-events'), + require('./tests/test-places-favicon'), + require('./tests/test-places-history'), + require('./tests/test-places-host'), + require('./tests/test-places-utils') + ); +} else { + exports['test unsupported'] = (assert) => { + assert.pass('This application is unsupported.'); + }; +} require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/simple-prefs-l10n/main.js +++ b/addon-sdk/source/test/addons/simple-prefs-l10n/main.js @@ -7,51 +7,59 @@ const { Cu } = require('chrome'); const sp = require('sdk/simple-prefs'); const app = require('sdk/system/xul-app'); const self = require('sdk/self'); const tabs = require('sdk/tabs'); const { preferencesBranch } = require('sdk/self'); const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {}); -exports.testAOMLocalization = function(assert, done) { - tabs.open({ - url: 'about:addons', - onReady: function(tab) { - tab.attach({ - contentScriptWhen: 'end', - contentScript: 'function onLoad() {\n' + - 'unsafeWindow.removeEventListener("load", onLoad, false);\n' + - 'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' + - 'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' + - 'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' + - 'setTimeout(function() {\n' + // TODO: figure out why this is necessary.. - 'self.postMessage({\n' + - 'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[data-jetpack-id=\'' + self.id + '\']"))\n' + - '});\n' + - '}, 250);\n' + - '}, false);\n' + - 'unsafeWindow.gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);\n' + - '});\n' + - 'function getAttributes(ele) {\n' + - 'if (!ele) return {};\n' + - 'return {\n' + - 'title: ele.getAttribute("title")\n' + +// Once Bug 903018 is resolved, just move the application testing to +// module.metadata.engines +// +// This should work in Fennec, needs to be refactored to work, via bug 979645 +if (app.is('Firefox')) { + exports.testAOMLocalization = function(assert, done) { + tabs.open({ + url: 'about:addons', + onReady: function(tab) { + tab.attach({ + contentScriptWhen: 'end', + contentScript: 'function onLoad() {\n' + + 'unsafeWindow.removeEventListener("load", onLoad, false);\n' + + 'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' + + 'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' + + 'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' + + 'setTimeout(function() {\n' + // TODO: figure out why this is necessary.. + 'self.postMessage({\n' + + 'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[data-jetpack-id=\'' + self.id + '\']"))\n' + + '});\n' + + '}, 250);\n' + + '}, false);\n' + + 'unsafeWindow.gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);\n' + + '});\n' + + 'function getAttributes(ele) {\n' + + 'if (!ele) return {};\n' + + 'return {\n' + + 'title: ele.getAttribute("title")\n' + + '}\n' + '}\n' + '}\n' + - '}\n' + - // Wait for the load event ? - 'if (document.readyState == "complete") {\n' + - 'onLoad()\n' + - '} else {\n' + - 'unsafeWindow.addEventListener("load", onLoad, false);\n' + - '}\n', - onMessage: function(msg) { - // test somePreference - assert.equal(msg.somePreference.title, 'A', 'somePreference title is correct'); - tab.close(done); - } - }); - } - }); + // Wait for the load event ? + 'if (document.readyState == "complete") {\n' + + 'onLoad()\n' + + '} else {\n' + + 'unsafeWindow.addEventListener("load", onLoad, false);\n' + + '}\n', + onMessage: function(msg) { + // test somePreference + assert.equal(msg.somePreference.title, 'A', 'somePreference title is correct'); + tab.close(done); + } + }); + } + }); + } +} else { + exports['test unsupported'] = (assert) => assert.pass('This test is unsupported.'); } require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/test-functional.js +++ b/addon-sdk/source/test/test-functional.js @@ -433,30 +433,38 @@ exports["test debounce"] = (assert, done setTimeout(() => { assert.equal(counter, 2, "function able to be called again after wait"); done(); }, 150); }, 200); }; exports["test throttle"] = (assert, done) => { - let called = 0; - let attempt = 0; - let throttledFn = throttle(() => called++, 100); - let fn = () => ++attempt && throttledFn(); + let counter = 0; + let incr = function(){ counter++; }; + let throttledIncr = throttle(incr, 32); + throttledIncr(); + throttledIncr(); + throttledIncr(); + throttledIncr(); - new Array(11).join(0).split("").forEach((_, i) => { - setTimeout(fn, 20 * (i+1)); - }); - + assert.equal(counter, 1, 'incr was called immediately'); setTimeout(() => { - assert.equal(called, 1, "function called atleast once during first throttle period"); - assert.ok(attempt >= 2, "function attempted to be called several times during first period"); - }, 50); + assert.equal(counter, 2, 'incr was throttled'); + done(); + }, 64); +}; +exports["test throttle args"] = (assert, done) => { + let value = 0; + let update = function(val){ value = val; }; + let throttledUpdate = throttle(update, 32); + throttledUpdate(1); + throttledUpdate(2); + setTimeout(() => throttledUpdate(3), 64); + assert.equal(value, 1, 'updated to latest value'); setTimeout(() => { - assert.equal(called, 3, "function called again during second throttle period"); - assert.equal(attempt, 10, "function attempted to be called several times during second period"); + assert.equal(value, 3, 'updated to latest value'); done(); - }, 300); + }, 96); }; require('test').run(exports);
--- a/addon-sdk/source/test/test-ui-frame.js +++ b/addon-sdk/source/test/test-ui-frame.js @@ -41,36 +41,36 @@ exports["test frame API"] = function*(as assert.throws(() => new Frame(), /The `options.url`/, "must provide url"); assert.throws(() => new Frame({ url: "http://mozilla.org/" }), /The `options.url`/, "options.url must be local url"); - assert.throws(() => new Frame({url: url, id: "4you" }), - /The `option.id` must be a valid/, - "can only take valid ID's"); + assert.throws(() => new Frame({url: url, name: "4you" }), + /The `option.name` must be a valid/, + "can only take valid names"); const f1 = new Frame({ url: url }); assert.ok(f1.id, "frame has an id"); assert.equal(f1.url, void(0), "frame has no url until it's loaded"); assert.equal(typeof(f1.postMessage), "function", "frames can postMessages"); const p1 = wait(f1, "register"); assert.throws(() => new Frame({ url: url }), /Frame with this id already exists/, "can't have two identical frames"); - const f2 = new Frame({ id: "frame-2", url: url }); - assert.pass("can create frame with same url but diff id"); + const f2 = new Frame({ name: "frame-2", url: url }); + assert.pass("can create frame with same url but diff name"); const p2 = wait(f2, "register"); yield p1; assert.pass("frame#1 was registered"); assert.equal(f1.url, url, "once registered it get's url"); yield p2; assert.pass("frame#2 was registered"); @@ -140,17 +140,17 @@ exports["test frame in toolbar"] = funct exports["test host to content messaging"] = function*(assert) { const url = "data:text/html,<script>new " + function() { window.addEventListener("message", (event) => { if (event.data === "ping!") event.source.postMessage("pong!", event.origin); }); } + "</script>"; - const f1 = new Frame({ id: "mailbox", url: url }); + const f1 = new Frame({ name: "mailbox", url: url }); const t1 = new Toolbar({ title: "mailbox", items: [f1] }); const e1 = yield wait(f1, "ready"); e1.source.postMessage("ping!", e1.origin); const pong = yield wait(f1, "message"); assert.equal(pong.data, "pong!", "received ping back"); t1.destroy(); @@ -164,17 +164,17 @@ exports["test content to host messaging" window.addEventListener("message", (event) => { if (event.data === "pong!") event.source.postMessage("end", event.origin); }); window.parent.postMessage("ping!", "*"); } + "</script>"; - const f1 = new Frame({ id: "inbox", url: url }); + const f1 = new Frame({ name: "inbox", url: url }); const t1 = new Toolbar({ title: "inbox", items: [f1] }); const e1 = yield wait(f1, "message"); assert.equal(e1.data, "ping!", "received ping from content"); e1.source.postMessage("pong!", e1.origin); const e2 = yield wait(f1, "message"); @@ -192,17 +192,17 @@ exports["test direct messaging"] = funct if (event.data === "inc") n = n + 1; if (event.data === "print") event.source.postMessage({ n: n }, event.origin); }); } + "</script>"; const w1 = getMostRecentBrowserWindow(); - const f1 = new Frame({ url: url, id: "mail-cluster" }); + const f1 = new Frame({ url: url, name: "mail-cluster" }); const t1 = new Toolbar({ title: "claster", items: [f1] }); yield wait(f1, "ready"); assert.pass("document loaded in window#1"); const w2 = open(); yield wait(f1, "ready");
--- a/addon-sdk/source/test/test-ui-toggle-button.js +++ b/addon-sdk/source/test/test-ui-toggle-button.js @@ -1015,16 +1015,54 @@ exports['test button click do not messin button.state(mainWindow, null); assert.equal(button.state(activeTab).icon, './icon.png', 'icon property for tab state, properly derived from window state'); loader.unload(); } +exports['test buttons can have anchored panels'] = function(assert, done) { + let loader = Loader(module); + let { ToggleButton } = loader.require('sdk/ui'); + let { Panel } = loader.require('sdk/panel'); + let { identify } = loader.require('sdk/ui/id'); + let { getActiveView } = loader.require('sdk/view/core'); + + let button = ToggleButton({ + id: 'my-button-22', + label: 'my button', + icon: './icon.png', + onChange: ({checked}) => checked && panel.show({position: button}) + }); + + let panel = Panel(); + + panel.once('show', () => { + let { document } = getMostRecentBrowserWindow(); + let buttonNode = document.getElementById(identify(button)); + let panelNode = getActiveView(panel); + + assert.ok(button.state('window').checked, + 'button is checked'); + + assert.equal(panelNode.getAttribute('type'), 'arrow', + 'the panel is a arrow type'); + + assert.strictEqual(buttonNode, panelNode.anchorNode, + 'the panel is anchored properly to the button'); + + loader.unload(); + + done(); + }); + + button.click(); +} + // If the module doesn't support the app we're being run in, require() will // throw. In that case, remove all tests above from exports, and add one dummy // test that passes. try { require('sdk/ui/button/toggle'); } catch (err) { if (!/^Unsupported Application/.test(err.message))
--- a/addon-sdk/source/test/test-ui-toolbar.js +++ b/addon-sdk/source/test/test-ui-toolbar.js @@ -7,21 +7,22 @@ module.metadata = { "engines": { "Firefox": "*" } }; const { Toolbar } = require("sdk/ui/toolbar"); const { Loader } = require("sdk/test/loader"); const { identify } = require("sdk/ui/id"); -const { getMostRecentBrowserWindow, open } = require("sdk/window/utils"); +const { getMostRecentBrowserWindow, open, getOuterId } = require("sdk/window/utils"); const { ready, close } = require("sdk/window/helpers"); const { defer } = require("sdk/core/promise"); -const { send } = require("sdk/event/utils"); +const { send, stop, Reactor } = require("sdk/event/utils"); const { object } = require("sdk/util/sequence"); +const { CustomizationInput } = require("sdk/input/customizable-ui"); const { OutputPort } = require("sdk/output/system"); const output = new OutputPort({ id: "toolbar-change" }); const wait = (toolbar, event) => { let { promise, resolve } = defer(); toolbar.once(event, resolve); return promise; }; @@ -352,9 +353,125 @@ exports["test title change"] = function* assert.equal(readTitle(t1, w2), "second title", "title updated in second window"); t1.destroy(); yield wait(t1, "detach"); yield close(w2); }; +exports["test toolbar is not customizable"] = function*(assert, done) { + const { window, document, gCustomizeMode } = getMostRecentBrowserWindow(); + const outerId = getOuterId(window); + const input = new CustomizationInput(); + const customized = defer(); + const customizedEnd = defer(); + + new Reactor({ onStep: value => { + if (value[outerId] === true) + customized.resolve(); + if (value[outerId] === null) + customizedEnd.resolve(); + }}).run(input); + + const toolbar = new Toolbar({ title: "foo" }); + + yield wait(toolbar, "attach"); + + let view = document.getElementById(toolbar.id); + let label = view.querySelector("label"); + let inner = view.querySelector("toolbar"); + + assert.equal(view.getAttribute("customizable"), "false", + "The outer toolbar is not customizable."); + + assert.ok(label.collapsed, + "The label is not displayed.") + + assert.equal(inner.getAttribute("customizable"), "true", + "The inner toolbar is customizable."); + + assert.equal(window.getComputedStyle(inner).visibility, "visible", + "The inner toolbar is visible."); + + // Enter in customization mode + gCustomizeMode.toggle(); + + yield customized.promise; + + assert.equal(view.getAttribute("customizable"), "false", + "The outer toolbar is not customizable."); + + assert.equal(label.collapsed, false, + "The label is displayed.") + + assert.equal(inner.getAttribute("customizable"), "true", + "The inner toolbar is customizable."); + + assert.equal(window.getComputedStyle(inner).visibility, "hidden", + "The inner toolbar is hidden."); + + // Exit from customization mode + gCustomizeMode.toggle(); + + yield customizedEnd.promise; + + assert.equal(view.getAttribute("customizable"), "false", + "The outer toolbar is not customizable."); + + assert.ok(label.collapsed, + "The label is not displayed.") + + assert.equal(inner.getAttribute("customizable"), "true", + "The inner toolbar is customizable."); + + assert.equal(window.getComputedStyle(inner).visibility, "visible", + "The inner toolbar is visible."); + + toolbar.destroy(); +}; + +exports["test button are attached to toolbar"] = function*(assert) { + const { document } = getMostRecentBrowserWindow(); + const { ActionButton, ToggleButton } = require("sdk/ui"); + const { identify } = require("sdk/ui/id"); + + let action = ActionButton({ + id: "btn-1", + label: "action", + icon: "./placeholder.png" + }); + + let toggle = ToggleButton({ + id: "btn-2", + label: "toggle", + icon: "./placeholder.png" + }); + + const toolbar = new Toolbar({ + title: "foo", + items: [action, toggle] + }); + + yield wait(toolbar, "attach"); + + let actionNode = document.getElementById(identify(action)); + let toggleNode = document.getElementById(identify(toggle)); + + assert.notEqual(actionNode, null, + "action button exists in the document"); + + assert.notEqual(actionNode, null, + "action button exists in the document"); + + assert.notEqual(toggleNode, null, + "toggle button exists in the document"); + + assert.equal(actionNode.nextElementSibling, toggleNode, + "action button is placed before toggle button"); + + assert.equal(actionNode.parentNode.parentNode.id, toolbar.id, + "buttons are placed in the correct toolbar"); + + toolbar.destroy(); +}; + require("sdk/test").run(exports);