Bug 906173 - Uplift addon-sdk to Firefox r=me

This commit is contained in:
Wes Kocher 2013-08-16 13:57:21 -07:00
parent d0ff61182b
commit 5c4a4d62bf
53 changed files with 5199 additions and 440 deletions

View File

@ -222,6 +222,8 @@ function startup(data, reasonCode) {
resultFile: options.resultFile,
// Arguments passed as --static-args
staticArgs: options.staticArgs,
// Add-on preferences branch name
preferencesBranch: options.preferencesBranch,
// Arguments related to test runner.
modules: {

View File

@ -0,0 +1,10 @@
<script>
addon.port.on('X', function(msg) {
// last message
addon.port.emit('X', msg + '3');
});
// start messaging chain
addon.port.emit('Y', '1');
</script>
SIDEBAR TEST

View File

@ -70,6 +70,7 @@ We'd like to thank our many Jetpack project contributors! They include:
### J ###
* Tomislav Jovanovic
* Eric H. Jung
### K ###
@ -100,6 +101,7 @@ We'd like to thank our many Jetpack project contributors! They include:
* Joe R. Nassimian ([placidrage](https://github.com/placidrage))
* Dương H. Nguyễn
* Nick Nguyen
* nodeless
### O ###

View File

@ -46,7 +46,7 @@ Then open "lib/main.js" and add the following code:
onItemVisited: function(aItemId, aVisitID, time) {
console.log("visited ", bookmarkService.getBookmarkURI(aItemId).spec);
},
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsINavBookmarkObserver])
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
};
exports.main = function() {

View File

@ -0,0 +1,214 @@
<!-- 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/. -->
<!-- contributed by Erik Vold [evold@mozilla.com] -->
This module exports a two constructor functions `Button` which constructs a
new toolbar button, and `Sidebar` which constructs a sidebar (with a button).
Sidebars are displayed on the left side of your browser. Its content is specified as
local HTML, so the appearance and behaviour of the sidebar
is limited only by what you can do using HTML, CSS and JavaScript.
The screenshot below shows a sidebar whose content is built from tweets:
<!-- add screen shot here -->
Sidebars are useful for presenting temporary interfaces to users in a way that is
easier for users to ignore and dismiss than a modal dialog
and easier for users to keep around than a Panel, since sidebars are
displayed at the side of the browser until the user decides to close it.
A sidebar's content is loaded anew as soon as it is opened, and unloads
when the user closes the sidebar.
Your add-on can receive notifications when a sidebar is shown or hidden by
listening to its `show` and `hide` events.
Opening a sidebar in a window will close an already opened sidebar in that window.
## Sidebar Content ##
The sidebar's content is specified as HTML, which is loaded from the URL
supplied in the `url` option to the sidebar's constructor.
You can load remote HTML into the sidebar:
var sidebar = require("sdk/ui").Sidebar({
id: 'a-new-sidebar',
title: 'A New Sidebar',
icon: './icon.png',
url: './index.html'
});
sidebar.show();
This will load HTML that's been packaged with your add-on, and this is
most probably how you will create sidebars. To do this, save
the `index.html` HTML file in your add-on's `data` directory.
## Sidebar Positioning ##
By default the sidebars appears on the left side of the currently active browser window.
## Updating Sidebar Content ##
You can update the sidebar's content simply by setting the sidebar's `url`
property. Note this will change the sidebar's url for all windows.
## Scripting Sidebar Content ##
You can't directly access your sidebar's content from your main add-on code.
To access the sidebar's content, you need to add a `<script>` into the sidebar.
The sidebar's scripts will have access to a `addon` global, with you can
communicate with your main add-on code, like so:
`lib/main.js`:
let sidebar = Sidebar({
id: 'a-new-sidebar',
title: 'A New Sidebar',
icon: './icon.png',
url: './index.html',
onAttach: function (worker) {
worker.port.on('message', function() { // part 2
// do things...
worker.port.emit('message', 'I have information for you!'); // part 3
});
}
});
`data/index.html`
<pre class="brush: html">
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
...
&lt;script&gt;
addon.port.on('message', function(msg) { // part 4
// msg will == 'I have information for you!'
});
// starting communication here..
addon.port.emit('message'); // part 1
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<api name="Sidebar">
@class
The Sidebar object.
Once a sidebar object has been created it can be shown and hidden,
in the active window, using its
`show()` and `hide()` methods. Once a sidebar is no longer needed it can be
removed/destructed using `destroy()`.
<api name="Sidebar">
@constructor
Creates a sidebar.
@param options {object}
Options for the sidebar, with the following keys:
@prop id {string}
The `id` of the sidebar.
@prop title {string}
A title for the sidebar.
@prop icon {string, object}
The icon used for the sidebar's button.
@prop url {string, URL}
The URL of the content to load in the sidebar.
@prop [onAttach] {function}
Include this to listen to the sidebar's `attach` event.
@prop [onShow] {function}
Include this to listen to the sidebar's `show` event.
@prop [onHide] {function}
Include this to listen to the sidebar's `hide` event.
</api>
<api name="id">
@property {string}
The `id` for the sidebar.
</api>
<api name="title">
@property {string}
The `title` of the sidebar.
</api>
<api name="icon">
@property {string, object}
The global icon for the sidebar.
</api>
<api name="url">
@property {string}
The URL of content loaded into the sidebar.
</api>
<api name="destroy">
@method
Destroys the sidebar, once destroyed, the sidebar can no longer be used.
</api>
<api name="show">
@method
Displays the sidebar in the active window.
</api>
<api name="hide">
@method
Hides the sidebar in the active window.
</api>
<api name="on">
@method
Registers an event listener with the sidebar.
@param type {string}
The type of event to listen for.
@param listener {function}
The listener function that handles the event.
</api>
<api name="once">
@method
Registers an event listener with the sidebar.
The difference between `on` and `once` is that
`on` will continue listening until it is
removed, whereas `once` is removed automatically
upon the first event it catches.
@param type {string}
The type of event to listen for.
@param listener {function}
The listener function that handles the event.
</api>
<api name="removeListener">
@method
Unregisters/removes an event listener from the sidebar.
@param type {string}
The type of event for which `listener` was registered.
@param listener {function}
The listener function that was registered.
</api>
<api name="attach">
@event
This event is emitted when the sidebar's window
is created and the `addon` global was added.
</api>
<api name="show">
@event
This event is emitted when the sidebar is shown.
</api>
<api name="hide">
@event
This event is emitted when the sidebar is hidden.
</api>
</api>

View File

@ -48,7 +48,6 @@ const { override, load } = loaderModule;
* @returns {Error}
*/
function incompatibility(module) {
let { metadata, id } = module;
// if metadata or engines are not specified we assume compatibility is not

View File

@ -11,9 +11,11 @@ const { emit, off } = require("./event/core");
const { when: unload } = require("./system/unload");
const { PrefsTarget } = require("./preferences/event-target");
const { id } = require("./self");
const { preferencesBranch } = require('@loader/options');
const observers = require("./deprecated/observer-service");
const ADDON_BRANCH = "extensions." + id + ".";
const ADDON_BRANCH = "extensions." + preferencesBranch + ".";
const BUTTON_PRESSED = id + "-cmdPressed";
const target = PrefsTarget({ branchName: ADDON_BRANCH });

View File

@ -0,0 +1,14 @@
/* 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 = {
'stability': 'experimental',
'engines': {
'Firefox': '> 24'
}
};
exports.Button = require('./ui/button').Button;
exports.Sidebar = require('./ui/sidebar').Sidebar;

View File

@ -0,0 +1,159 @@
/* 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 = {
'stability': 'experimental',
'engines': {
'Firefox': '> 24'
}
};
try {
require('chrome').Cu.import('resource:///modules/CustomizableUI.jsm', {});
}
catch (e) {
throw Error('Unsupported Application: The module ' + module.id + ' does not support this application.');
}
const { Class } = require('../core/heritage');
const { merge } = require('../util/object');
const { properties, render, state, register, unregister } = require("./state");
const { Disposable } = require('../core/disposable');
const { contract } = require('../util/contract');
const { on, off, emit, setListeners } = require('../event/core');
const { EventTarget } = require('../event/target');
const { isNil, isObject, isString } = require('../lang/type');
const { required, either, string, number, boolean, object } = require('../deprecated/api-utils');
const { isLocalURL } = require('../url');
const { add, remove, has, clear, iterator } = require("../lang/weak-set");
const tabs = require("../tabs");
const { browserWindows } = require("sdk/windows");
const view = require("./button/view");
const { data } = require("../self");
function isIconSet(icons) {
return Object.keys(icons).
every(size => String(size >>> 0) === size && isLocalURL(icons[size]))
}
let iconSet = {
is: either(object, string),
ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
msg: 'The option "icon" must be a local URL or an object with ' +
'numeric keys / local URL values pair.'
}
let buttonId = {
is: string,
ok: v => /^[a-z0-9-_]+$/i.test(v),
msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
'underscores are allowed).'
};
let buttonType = {
is: string,
ok: v => ~['button', 'checkbox'].indexOf(v),
msg: 'The option "type" must be one of the following string values: ' +
'"button", "checkbox".'
}
let size = {
is: string,
ok: v => ~['small', 'medium', 'large'].indexOf(v),
msg: 'The option "size" must be one of the following string values: ' +
'"small", "medium", "large".'
};
let label = {
is: string,
ok: v => isNil(v) || v.trim().length > 0,
msg: 'The option "label" must be a non empty string'
}
let stateContract = contract({
label: label,
icon: iconSet,
disabled: boolean,
checked: boolean
});
let buttonContract = contract(merge({}, stateContract.rules, {
id: required(buttonId),
label: required(label),
icon: required(iconSet),
type: buttonType,
size: size
}));
const Button = Class({
extends: EventTarget,
implements: [
properties(stateContract),
state(stateContract),
Disposable
],
setup: function setup(options) {
let state = merge({
type: 'button',
disabled: false,
checked: false,
size: 'small',
}, buttonContract(options));
// Setup listeners.
setListeners(this, options);
// TODO: improve
let viewEvents = view.create(state);
on(viewEvents, 'click', onViewClick.bind(this));
on(viewEvents, 'moved', () => render(this));
register(this, state);
},
dispose: function dispose() {
off(this);
view.dispose(this);
unregister(this);
},
get id() this.state().id,
get size() this.state().size,
get type() this.state().type,
click: function click() view.click(this)
});
exports.Button = Button;
function onViewClick() {
let state = this.state(tabs.activeTab);
if (this.type === 'checkbox') {
state = merge({}, state, { checked: !state.checked });
this.state(browserWindows.activeWindow, state);
emit(this, 'click', state);
emit(this, 'change', state);
}
else
emit(this, 'click', state);
}
on(Button, 'render', function(button, window, state) {
view.setIcon(button, window, state.icon);
view.setLabel(button, window, state.label);
view.setDisabled(button, window, state.disabled);
view.setChecked(button, window, state.checked);
});

View File

@ -0,0 +1,201 @@
/* 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 = {
'stability': 'experimental',
'engines': {
'Firefox': '> 24'
}
};
const { Cu } = require('chrome');
const { on, off, emit } = require('../../event/core');
const { id: addonID, data } = require('sdk/self');
const buttonPrefix = 'button--' + addonID.replace(/@/g, '-at-');
const { isObject } = require('../../lang/type');
const { getMostRecentBrowserWindow } = require('../../window/utils');
const { ignoreWindow } = require('../../private-browsing/utils');
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI;
const SIZE = {
'small': 16,
'medium': 32,
'large': 64
}
const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const toWidgetID = function(id) buttonPrefix + '-' + id;
const toButtonID = function(id) id.substr(buttonPrefix.length + 1);
const views = {};
const buttonListener = {
onWidgetAdded: function(widgetId, area, position) {
let id = toButtonID(widgetId);
if (id in views && views[id].area !== area) {
views[id].area = area;
emit(views[id].events, 'moved', area === AREA_PANEL);
}
}
};
CustomizableUI.addListener(buttonListener);
require('../../system/unload').when(function(){
CustomizableUI.removeListener(buttonListener);
});
function getNode(id, window) {
return !(id in views) || ignoreWindow(window)
? null
: CustomizableUI.getWidget(toWidgetID(id)).forWindow(window).node
};
function isInPanel(id) views[id] && views[id].area === AREA_PANEL;
function getImage(icon, areaIsPanel, pixelRatio) {
let targetSize = (areaIsPanel ? 32 : 18) * pixelRatio;
let bestSize = 0;
let image = icon;
if (isObject(icon)) {
for (let size of Object.keys(icon)) {
size = +size;
let offset = targetSize - size;
if (offset === 0) {
bestSize = size;
break;
}
let delta = Math.abs(offset) - Math.abs(targetSize - bestSize);
if (delta < 0)
bestSize = size;
}
image = icon[bestSize];
}
if (image.indexOf('./') === 0)
return data.url(image.substr(2));
return image;
}
function create(options) {
let { id, label, image, size, type } = options;
let bus = {};
if (id in views)
throw new Error('The ID "' + id + '" seems already used.');
CustomizableUI.createWidget({
id: toWidgetID(id),
type: 'custom',
removable: true,
defaultArea: AREA_NAVBAR,
allowedAreas: [ AREA_PANEL, AREA_NAVBAR ],
onBuild: function(document) {
let window = document.defaultView;
let node = document.createElementNS(XUL_NS, 'toolbarbutton');
if (ignoreWindow(window))
node.style.display = 'none';
node.setAttribute('id', this.id);
node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional');
node.setAttribute('width', SIZE[size] || 16);
node.setAttribute('type', type);
views[id] = {
events: bus,
area: this.currentArea
};
node.addEventListener('command', function(event) {
if (views[id])
emit(views[id].events, 'click', event);
});
return node;
}
});
return bus;
};
exports.create = create;
function dispose({id}) {
if (!views[id]) return;
off(views[id].events);
delete views[id];
CustomizableUI.destroyWidget(toWidgetID(id));
}
exports.dispose = dispose;
function setIcon({id}, window, icon) {
let node = getNode(id, window);
if (node) {
let image = getImage(icon, isInPanel(id), window.devicePixelRatio);
node.setAttribute('image', image);
}
}
exports.setIcon = setIcon;
function setLabel({id}, window, label) {
let node = getNode(id, window);
if (node) {
node.setAttribute('label', label);
node.setAttribute('tooltiptext', label);
}
}
exports.setLabel = setLabel;
function setDisabled({id}, window, disabled) {
let node = getNode(id, window);
if (node) {
if (disabled)
node.setAttribute('disabled', disabled);
else
node.removeAttribute('disabled');
}
}
exports.setDisabled = setDisabled;
function setChecked({id}, window, checked) {
let node = getNode(id, window);
if (node) {
if (checked)
node.setAttribute('checked', checked);
else
node.removeAttribute('checked');
}
}
exports.setChecked = setChecked;
function click({id}) {
let window = getMostRecentBrowserWindow();
let node = getNode(id, window);
if (node)
node.click();
}
exports.click = click;

View File

@ -0,0 +1,330 @@
/* 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 = {
'stability': 'experimental',
'engines': {
'Firefox': '> 24'
}
};
try {
require('chrome').Cu.import('resource:///modules/CustomizableUI.jsm', {});
}
catch (e) {
throw Error('Unsupported Application: The module ' + module.id + ' does not support this application.');
}
const { Class } = require('../core/heritage');
const { merge } = require('../util/object');
const { Disposable } = require('../core/disposable');
const { off, emit, setListeners } = require('../event/core');
const { EventTarget } = require('../event/target');
const { URL } = require('../url');
const { add, remove, has, clear, iterator } = require('../lang/weak-set');
const { WindowTracker } = require('../deprecated/window-utils');
const { isShowing } = require('./sidebar/utils');
const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../window/utils');
const { ns } = require('../core/namespace');
const { remove: removeFromArray } = require('../util/array');
const { show, hide, toggle } = require('./sidebar/actions');
const { Worker: WorkerTrait } = require('../content/worker');
const { contract: sidebarContract } = require('./sidebar/contract');
const { Button } = require('./button');
const { setStateFor, getStateFor } = require('./state');
const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
const { defer } = require('../core/promise');
const { models, buttons, views, viewsFor, modelFor } = require('./sidebar/namespace');
const { isLocalURL } = require('../url');
const { ensure } = require('../system/unload');
const Worker = WorkerTrait.resolve({
_injectInDocument: '__injectInDocument'
}).compose({
get _injectInDocument() true
});
const sidebarNS = ns();
const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
let sidebars = {};
const Sidebar = Class({
implements: [ Disposable ],
extends: EventTarget,
setup: function(options) {
let model = sidebarContract(options);
models.set(this, model);
validateTitleAndURLCombo({}, this.title, this.url);
// NOTE: delegating icon validation to the Button.
// IMPORTANT: Make the button first since it has it's own
// validation which we make use of.. (even if the sidebar
// id is not a duplicate the button id could be..)
let button = Button({
id: model.id,
icon: model.icon,
label: model.title,
type: 'checkbox',
onChange: update.bind(null, 'button')
});
buttons.set(this, button);
const self = this;
const internals = sidebarNS(self);
const windowNS = internals.windowNS = ns();
// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=886148
ensure(this, 'destroy');
setListeners(this, options);
function update(source, state) {
let wins = windows('navigator:browser', { includePrivate: true });
for (let window of wins) {
let isShowing = isSidebarShowing(window, self);
let isChecked = (source == 'button') ? getStateFor(button, window).checked : isShowing;
// update sidebar?
if (isShowing != isChecked) {
if (isChecked) {
showSidebar(window, self);
}
else {
hideSidebar(window, self);
}
}
// update the button
setStateFor(button, window, { checked: isChecked });
}
}
let bars = [];
internals.tracker = WindowTracker({
onTrack: function(window) {
if (!isBrowser(window))
return;
let sidebar = window.document.getElementById('sidebar');
let sidebarBox = window.document.getElementById('sidebar-box');
let bar = create(window, {
id: self.id,
title: self.title,
sidebarurl: self.url
});
bars.push(bar);
windowNS(window).bar = bar;
bar.addEventListener('command', function() {
if (isSidebarShowing(window, self)) {
hideSidebar(window, self);
return;
}
showSidebar(window, self);
}, false);
function onSidebarLoad() {
// check if the sidebar is ready
let isReady = sidebar.docShell && sidebar.contentDocument;
if (!isReady)
return;
// check if it is a web panel
let panelBrowser = sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
if (!panelBrowser) {
bar.removeAttribute('checked');
return;
}
let sbTitle = window.document.getElementById('sidebar-title');
function onWebPanelSidebarCreated() {
if (panelBrowser.contentWindow.location != model.url ||
sbTitle.value != model.title) {
return;
}
let worker = windowNS(window).worker = Worker({
window: panelBrowser.contentWindow
});
function onWebPanelSidebarUnload() {
windowNS(window).onWebPanelSidebarUnload = null;
// uncheck the associated menuitem
bar.setAttribute('checked', 'false');
setStateFor(button, window, { checked: false });
emit(self, 'hide', {});
emit(self, 'detach', worker);
}
windowNS(window).onWebPanelSidebarUnload = onWebPanelSidebarUnload;
panelBrowser.contentWindow.addEventListener('unload', onWebPanelSidebarUnload, true);
// check the associated menuitem
bar.setAttribute('checked', 'true');
function onWebPanelSidebarLoad() {
panelBrowser.contentWindow.removeEventListener('load', onWebPanelSidebarLoad, true);
windowNS(window).onWebPanelSidebarLoad = null;
update();
// TODO: decide if returning worker is acceptable..
//emit(self, 'show', { worker: worker });
emit(self, 'show', {});
}
windowNS(window).onWebPanelSidebarLoad = onWebPanelSidebarLoad;
panelBrowser.contentWindow.addEventListener('load', onWebPanelSidebarLoad, true);
emit(self, 'attach', worker);
}
windowNS(window).onWebPanelSidebarCreated = onWebPanelSidebarCreated;
panelBrowser.addEventListener('DOMWindowCreated', onWebPanelSidebarCreated, true);
}
windowNS(window).onSidebarLoad = onSidebarLoad;
sidebar.addEventListener('load', onSidebarLoad, true); // removed properly
},
onUntrack: function(window) {
if (!isBrowser(window))
return;
// hide the sidebar if it is showing
hideSidebar(window, self);
// kill the menu item
let { bar } = windowNS(window);
if (bar) {
removeFromArray(viewsFor(self), bar);
dispose(bar);
}
// kill listeners
let sidebar = window.document.getElementById('sidebar');
if (windowNS(window).onSidebarLoad) {
sidebar && sidebar.removeEventListener('load', windowNS(window).onSidebarLoad, true)
windowNS(window).onSidebarLoad = null;
}
let panelBrowser = sidebar && sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
if (windowNS(window).onWebPanelSidebarCreated) {
panelBrowser && panelBrowser.removeEventListener('DOMWindowCreated', windowNS(window).onWebPanelSidebarCreated, true);
windowNS(window).onWebPanelSidebarCreated = null;
}
if (windowNS(window).onWebPanelSidebarLoad) {
panelBrowser && panelBrowser.contentWindow.removeEventListener('load', windowNS(window).onWebPanelSidebarLoad, true);
windowNS(window).onWebPanelSidebarLoad = null;
}
if (windowNS(window).onWebPanelSidebarUnload) {
panelBrowser && panelBrowser.contentWindow.removeEventListener('unload', windowNS(window).onWebPanelSidebarUnload, true);
windowNS(window).onWebPanelSidebarUnload = null;
}
}
});
views.set(this, bars);
add(sidebars, this);
},
get id() (modelFor(this) || {}).id,
get title() (modelFor(this) || {}).title,
set title(v) {
// destroyed?
if (!modelFor(this))
return;
// validation
if (typeof v != 'string')
throw Error('title must be a string');
validateTitleAndURLCombo(this, v, this.url);
// do update
updateTitle(this, v);
return modelFor(this).title = v;
},
get url() (modelFor(this) || {}).url,
set url(v) {
// destroyed?
if (!modelFor(this))
return;
// validation
if (!isLocalURL(v))
throw Error('the url must be a valid local url');
validateTitleAndURLCombo(this, this.title, v);
// do update
updateURL(this, v);
modelFor(this).url = v;
},
get icon() (buttons.get(this) || {}).icon,
set icon(v) {
let button = buttons.get(this);
if (!button)
return;
button.icon = v;
},
show: function() {
return showSidebar(null, this);
},
hide: function() {
return hideSidebar(null, this);
},
dispose: function() {
const internals = sidebarNS(this);
off(this);
remove(sidebars, this);
// stop tracking windows
internals.tracker.unload();
internals.tracker = null;
internals.windowNS = null;
views.delete(this);
models.delete(this);
// kill the button
let button = buttons.get(this);
if (button)
button.destroy();
}
});
exports.Sidebar = Sidebar;
function validateTitleAndURLCombo(sidebar, title, url) {
if (sidebar.title == title && sidebar.url == url) {
return false;
}
for (let window of windows(null, { includePrivate: true })) {
let sidebar = window.document.querySelector('menuitem[sidebarurl="' + url + '"][label="' + title + '"]');
if (sidebar) {
throw Error('The provided title and url combination is invalid (already used).');
}
}
return false;
}
isShowing.define(Sidebar, isSidebarShowing.bind(null, null));
show.define(Sidebar, showSidebar.bind(null, null));
hide.define(Sidebar, hideSidebar.bind(null, null));
function toggleSidebar(window, sidebar) {
// TODO: make sure this is not private
window = window || getMostRecentBrowserWindow();
if (isSidebarShowing(window, sidebar)) {
return hideSidebar(window, sidebar);
}
return showSidebar(window, sidebar);
}
toggle.define(Sidebar, toggleSidebar.bind(null, null));

View File

@ -0,0 +1,10 @@
/* 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 method = require('method/core');
exports.show = method('show');
exports.hide = method('hide');
exports.toggle = method('toggle');

View File

@ -0,0 +1,38 @@
/* 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 { contract } = require('../../util/contract');
const { isValidURI, URL, isLocalURL } = require('../../url');
const { isNil, isObject, isString } = require('../../lang/type');
function isIconSet(icons) {
return Object.keys(icons).
every(size => String(size >>> 0) === size && isLocalURL(icons[size]))
}
exports.contract = contract({
id: {
is: [ 'string' ],
ok: v => /^[a-z0-9-_]+$/i.test(v),
msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
'underscores are allowed).'
},
title: {
is: [ 'string' ],
ok: v => v.length
},
icon: {
is: ['string', 'object'],
ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
msg: 'The option "icon" must be a local URL or an object with ' +
'numeric keys / local URL values pair.'
},
url: {
is: [ 'string' ],
ok: v => isLocalURL(v),
map: function(v) v.toString(),
msg: 'The option "url" must be a valid URI.'
}
});

View File

@ -0,0 +1,11 @@
/* 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 models = exports.models = new WeakMap();
const views = exports.views = new WeakMap();
exports.buttons = new WeakMap();
exports.viewsFor = function viewsFor(sidebar) views.get(sidebar);
exports.modelFor = function modelFor(sidebar) models.get(sidebar);

View File

@ -0,0 +1,8 @@
/* 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 method = require('method/core');
exports.isShowing = method('isShowing');

View File

@ -0,0 +1,193 @@
/* 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 = {
'stability': 'unstable',
'engines': {
'Firefox': '> 24'
}
};
const { models, buttons, views, viewsFor, modelFor } = require('./namespace');
const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../../window/utils');
const { setStateFor } = require('../state');
const { defer } = require('../../core/promise');
const { isPrivateBrowsingSupported } = require('../../self');
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
function create(window, details) {
let { document } = window;
let menuitem = document.createElementNS(XUL_NS, 'menuitem');
menuitem.setAttribute('id', makeID(details.id));
menuitem.setAttribute('label', details.title);
menuitem.setAttribute('sidebarurl', details.sidebarurl);
menuitem.setAttribute('checked', 'false');
menuitem.setAttribute('type', 'checkbox');
menuitem.setAttribute('group', 'sidebar');
menuitem.setAttribute('autoCheck', 'false');
document.getElementById('viewSidebarMenu').appendChild(menuitem);
return menuitem;
}
exports.create = create;
function dispose(menuitem) {
menuitem.parentNode.removeChild(menuitem);
}
exports.dispose = dispose;
function updateTitle(sidebar, title) {
let button = buttons.get(sidebar);
for (let window of windows(null, { includePrivate: true })) {
let { document } = window;
// update the button
if (button) {
setStateFor(button, window, { label: title });
}
// update the menuitem
let mi = document.getElementById(makeID(sidebar.id));
if (mi) {
mi.setAttribute('label', title)
}
// update sidebar, if showing
if (isSidebarShowing(window, sidebar)) {
document.getElementById('sidebar-title').setAttribute('value', title);
}
}
}
exports.updateTitle = updateTitle;
function updateURL(sidebar, url) {
for (let window of windows(null, { includePrivate: true })) {
// update sidebar, if showing
if (isSidebarShowing(window, sidebar)) {
showSidebar(window, sidebar, url);
}
}
}
exports.updateURL = updateURL;
function isSidebarShowing(window, sidebar) {
let win = window || getMostRecentBrowserWindow();
// make sure there is a window
if (!win) {
return false;
}
// make sure there is a sidebar for the window
let sb = win.document.getElementById('sidebar');
let sidebarTitle = win.document.getElementById('sidebar-title');
if (!(sb && sidebarTitle)) {
return false;
}
// checks if the sidebar box is hidden
let sbb = win.document.getElementById('sidebar-box');
if (!sbb || sbb.hidden) {
return false;
}
if (sidebarTitle.value == modelFor(sidebar).title) {
// checks if the sidebar is loading
if (win.gWebPanelURI == modelFor(sidebar).url) {
return true;
}
// checks if the sidebar loaded already
let ele = sb.contentDocument && sb.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
if (!ele) {
return false;
}
if (ele.getAttribute('cachedurl') == modelFor(sidebar).url) {
return true;
}
if (ele && ele.contentWindow && ele.contentWindow.location == modelFor(sidebar).url) {
return true;
}
}
// default
return false;
}
exports.isSidebarShowing = isSidebarShowing;
function showSidebar(window, sidebar, newURL) {
window = window || getMostRecentBrowserWindow();
let { promise, resolve, reject } = defer();
let model = modelFor(sidebar);
if (!isPrivateBrowsingSupported && isWindowPrivate(window)) {
reject(Error('You cannot show a sidebar on private windows'));
}
else {
sidebar.once('show', resolve);
let menuitem = window.document.getElementById(makeID(model.id));
menuitem.setAttribute('checked', true);
window.openWebPanel(model.title, newURL || model.url);
}
return promise;
}
exports.showSidebar = showSidebar;
function hideSidebar(window, sidebar) {
window = window || getMostRecentBrowserWindow();
let { promise, resolve, reject } = defer();
if (!isSidebarShowing(window, sidebar)) {
reject(Error('The sidebar is already hidden'));
}
else {
sidebar.once('hide', resolve);
// Below was taken from http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4775
// the code for window.todggleSideBar()..
let { document } = window;
let sidebarEle = document.getElementById('sidebar');
let sidebarTitle = document.getElementById('sidebar-title');
let sidebarBox = document.getElementById('sidebar-box');
let sidebarSplitter = document.getElementById('sidebar-splitter');
let commandID = sidebarBox.getAttribute('sidebarcommand');
let sidebarBroadcaster = document.getElementById(commandID);
sidebarBox.hidden = true;
sidebarSplitter.hidden = true;
sidebarEle.setAttribute('src', 'about:blank');
//sidebarEle.docShell.createAboutBlankContentViewer(null);
sidebarBroadcaster.removeAttribute('checked');
sidebarBox.setAttribute('sidebarcommand', '');
sidebarTitle.value = '';
sidebarBox.hidden = true;
sidebarSplitter.hidden = true;
// TODO: perhaps this isn't necessary if the window is not most recent?
window.gBrowser.selectedBrowser.focus();
}
return promise;
}
exports.hideSidebar = hideSidebar;
function makeID(id) {
return 'jetpack-sidebar-' + id;
}

View File

@ -0,0 +1,240 @@
/* 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';
// The Button module currently supports only Firefox.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
module.metadata = {
'stability': 'experimental',
'engines': {
'Firefox': '*'
}
};
const { Ci } = require('chrome');
const events = require('../event/utils');
const { events: browserEvents } = require('../browser/events');
const { events: tabEvents } = require('../tab/events');
const { windows, isInteractive } = require('../window/utils');
const { BrowserWindow, browserWindows } = require('../windows');
const { windowNS } = require('../window/namespace');
const { Tab } = require('../tabs/tab');
const { getActiveTab, getOwnerWindow, getTabs, getTabId } = require('../tabs/utils');
const { ignoreWindow } = require('../private-browsing/utils');
const { freeze } = Object;
const { merge } = require('../util/object');
const { on, off, emit } = require('../event/core');
const { add, remove, has, clear, iterator } = require("../lang/weak-set");
const { isNil, instanceOf } = require('../lang/type');
const components = new WeakMap();
const ERR_UNREGISTERED = 'The state cannot be set or get. ' +
'The object may be not be registered, or may already have been unloaded.';
/**
* temporary
*/
function getChromeWindow(sdkWindow) windowNS(sdkWindow).window;
/**
* temporary
*/
function getChromeTab(sdkTab) {
for (let tab of getTabs()) {
if (sdkTab.id === getTabId(tab))
return tab;
}
return null;
}
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 isWindowEnumerable = window => !ignoreWindow(window);
function getStateFor(component, target) {
if (!isRegistered(component))
throw new Error(ERR_UNREGISTERED);
if (!components.has(component))
return null;
let states = components.get(component);
let componentState = states.get(component);
let windowState = null;
let tabState = null;
if (target) {
// has a target
if (isTab(target)) {
windowState = states.get(getOwnerWindow(target), null);
if (states.has(target)) {
// we have a tab state
tabState = states.get(target);
}
}
else if (isWindow(target) && states.has(target)) {
// we have a window state
windowState = states.get(target);
}
}
return freeze(merge({}, componentState, windowState, tabState));
}
exports.getStateFor = getStateFor;
function setStateFor(component, target, state) {
if (!isRegistered(component))
throw new Error(ERR_UNREGISTERED);
let targetWindows = [];
let isComponentState = target === component;
if (isWindow(target)) {
targetWindows = [target];
}
else if (isActiveTab(target)) {
targetWindows = [getOwnerWindow(target)];
}
else if (isComponentState) {
targetWindows = windows('navigator:browser', { includePrivate: true}).filter(isInteractive);
}
else if (!isTab(target))
throw new Error('target not allowed.');
// initialize the state's map
if (!components.has(component))
components.set(component, new WeakMap());
let states = components.get(component);
if (state === null && !isComponentState) // component state can't be deleted
states.delete(target);
else {
let base = isComponentState ? states.get(target) : null;
states.set(target, freeze(merge({}, base, state)));
}
for (let window of targetWindows.filter(isWindowEnumerable)) {
let tabState = getStateFor(component, getActiveTab(window));
emit(component.constructor, 'render', component, window, tabState);
}
}
// Exporting `setStateFor` temporary for the sidebar / toolbar, until we do not
// have an 'official' way to get an SDK Window from Chrome Window.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=695143
//
// Misuse of `setStateFor` could leads to side effects with the proper `state`
// implementation.
exports.setStateFor = setStateFor;
function render(component, targetWindows) {
if (!targetWindows)
targetWindows = windows('navigator:browser', { includePrivate: true}).filter(isInteractive);
else
targetWindows = [].concat(targetWindows);
for (let window of targetWindows.filter(isWindowEnumerable)) {
let tabState = getStateFor(component, getActiveTab(window));
emit(component.constructor, 'render', component, window, tabState);
}
}
exports.render = render;
function properties(contract) {
let { rules } = contract;
let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
descriptor[name] = {
get: function() { return getStateFor(this)[name] },
set: function(value) {
let changed = {};
changed[name] = value;
setStateFor(this, this, contract(changed));
}
}
return descriptor;
}, {});
return Object.create(Object.prototype, descriptor);
}
exports.properties = properties;
function state(contract) {
return {
state: function state(target, state) {
// jquery style
let isGet = arguments.length < 2;
if (instanceOf(target, BrowserWindow))
target = getChromeWindow(target);
else if (instanceOf(target, Tab))
target = getChromeTab(target);
else if (target !== this && !isNil(target))
throw new Error('target not allowed.');
if (isGet)
return getStateFor(this, target);
// contract?
setStateFor(this, target, contract(state));
}
}
}
exports.state = state;
function register(component, state) {
add(components, component);
setStateFor(component, component, state);
}
exports.register = register;
function unregister(component) remove(components, component);
exports.unregister = unregister;
function isRegistered(component) has(components, component);
exports.isRegistered = isRegistered;
let tabSelect = events.filter(tabEvents, function(e) e.type === 'TabSelect');
let tabClose = events.filter(tabEvents, function(e) e.type === 'TabClose');
let windowOpen = events.filter(browserEvents, function(e) e.type === 'load');
let windowClose = events.filter(browserEvents, function(e) e.type === 'close');
let close = events.merge([tabClose, windowClose]);
on(windowOpen, 'data', function({target: window}) {
if (ignoreWindow(window)) return;
let tab = getActiveTab(window);
for (let component of iterator(components)) {
emit(component.constructor, 'render', component, window, getStateFor(component, tab));
}
});
on(tabSelect, 'data', function({target: tab}) {
let window = getOwnerWindow(tab);
if (ignoreWindow(window)) return;
for (let component of iterator(components)) {
emit(component.constructor, 'render', component, window, getStateFor(component, tab));
}
});
on(close, 'data', function({target}) {
for (let component of iterator(components)) {
components.get(component).delete(target);
}
});

View File

@ -18,9 +18,9 @@ const { Cc, Ci, Cr } = require('chrome'),
{ WindowTrackerTrait } = windowUtils,
{ ns } = require('../core/namespace'),
{ observer: windowObserver } = require('./observer'),
{ getOwnerWindow } = require('../private-browsing/window/utils'),
viewNS = require('../core/namespace').ns(),
{ isPrivateBrowsingSupported } = require('../self');
{ getOwnerWindow } = require('../private-browsing/window/utils');
const { windowNS } = require('../window/namespace');
const { isPrivateBrowsingSupported } = require('../self');
const { ignoreWindow } = require('sdk/private-browsing/utils');
/**
@ -74,7 +74,7 @@ const BrowserWindowTrait = Trait.compose(
this._load();
viewNS(this._public).window = this._window;
windowNS(this._public).window = this._window;
getOwnerWindow.implement(this._public, getChromeWindow);
return this;
@ -256,7 +256,7 @@ const browserWindows = Trait.resolve({ toString: null }).compose(
)();
function getChromeWindow(window) {
return viewNS(window).window;
return windowNS(window).window;
}
exports.browserWindows = browserWindows;

View File

@ -234,6 +234,10 @@ parser_groups = (
default=False,
cmds=['test', 'testpkgs', 'testaddons',
'testall'])),
(("", "--output-file",), dict(dest="output_file",
help="Where to put the finished .xpi",
default=None,
cmds=['xpi'])),
]
),
@ -892,7 +896,11 @@ def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None,
key,value = kv.split("=", 1)
extra_harness_options[key] = value
# Generate xpi filepath
if options.output_file:
xpi_path = options.output_file
else:
xpi_path = XPI_FILENAME % target_cfg.name
print >>stdout, "Exporting extension to %s." % xpi_path
build_xpi(template_root_dir=app_extension_dir,
manifest=manifest_rdf,

View File

@ -2,7 +2,7 @@
# 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/.
def parse_options_defaults(options, jetpack_id):
def parse_options_defaults(options, preferencesBranch):
# this returns a unicode string
pref_list = []
@ -21,6 +21,6 @@ def parse_options_defaults(options, jetpack_id):
else:
value = str(pref["value"])
pref_list.append("pref(\"extensions." + jetpack_id + "." + pref["name"] + "\", " + value + ");")
pref_list.append("pref(\"extensions." + preferencesBranch + "." + pref["name"] + "\", " + value + ");")
return "\n".join(pref_list) + "\n"

View File

@ -45,7 +45,7 @@ def validate_prefs(options):
# TODO: Check that pref["type"] matches default value type
def parse_options(options, jetpack_id):
def parse_options(options, jetpack_id, preferencesBranch):
doc = Document()
root = doc.createElement("vbox")
root.setAttribute("xmlns", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
@ -58,7 +58,7 @@ def parse_options(options, jetpack_id):
setting = doc.createElement("setting")
setting.setAttribute("pref-name", pref["name"])
setting.setAttribute("data-jetpack-id", jetpack_id)
setting.setAttribute("pref", "extensions." + jetpack_id + "." + pref["name"])
setting.setAttribute("pref", "extensions." + preferencesBranch + "." + pref["name"])
setting.setAttribute("type", pref["type"])
setting.setAttribute("title", pref["title"])

View File

@ -396,6 +396,21 @@ def generate_build_for_target(pkg_cfg, target, deps,
if ('preferences' in target_cfg):
build['preferences'] = target_cfg.preferences
if 'id' in target_cfg:
# NOTE: logic duplicated from buildJID()
jid = target_cfg['id']
if not ('@' in jid or jid.startswith('{')):
jid += '@jetpack'
build['preferencesBranch'] = jid
if 'preferences-branch' in target_cfg:
# check it's a non-empty, valid branch name
preferencesBranch = target_cfg['preferences-branch']
if re.match('^[\w{@}-]+$', preferencesBranch):
build['preferencesBranch'] = preferencesBranch
elif not is_running_tests:
print >>sys.stderr, "IGNORING preferences-branch (not a valid branch name)"
return build
def _get_files_in_dir(path):

View File

@ -0,0 +1,4 @@
/* 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/. */

View File

@ -0,0 +1,14 @@
{
"id": "{34a1eae1-c20a-464f-9b0e-000000000000}",
"fullName": "curly ID test",
"author": "Tomislav Jovanovic",
"preferences": [{
"name": "test13",
"type": "integer",
"title": "test13",
"value": 26
}],
"preferences-branch": "invalid^branch*name"
}

View File

@ -0,0 +1,4 @@
/* 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/. */

View File

@ -0,0 +1,14 @@
{
"id": "test-preferences-branch",
"fullName": "preferences-branch test",
"author": "Tomislav Jovanovic",
"preferences": [{
"name": "test42",
"type": "bool",
"title": "test42",
"value": true
}],
"preferences-branch": "human-readable"
}

View File

@ -22,7 +22,8 @@ def get_configs(pkg_name, dirname='static-files'):
build = packaging.generate_build_for_target(
pkg_cfg=pkg_cfg,
target=pkg_name,
deps=deps
deps=deps,
is_running_tests=True,
)
return Bunch(target_cfg=target_cfg, pkg_cfg=pkg_cfg, build=build)

View File

@ -40,10 +40,11 @@ class PrefsTests(unittest.TestCase):
def testPackageWithSimplePrefs(self):
self.makexpi('simple-prefs')
packageName = 'jid1-fZHqN9JfrDBa8A@jetpack'
self.failUnless('options.xul' in self.xpi.namelist())
optsxul = self.xpi.read('options.xul').decode("utf-8")
self.failUnlessEqual(self.xpi_harness_options["jetpackID"],
"jid1-fZHqN9JfrDBa8A@jetpack")
self.failUnlessEqual(self.xpi_harness_options["jetpackID"], packageName)
self.failUnlessEqual(self.xpi_harness_options["preferencesBranch"], packageName)
root = ElementTree.XML(optsxul.encode('utf-8'))
@ -53,7 +54,6 @@ class PrefsTests(unittest.TestCase):
settings = root.findall(xulNamespacePrefix + 'setting')
def assertPref(setting, name, prefType, title):
packageName = 'jid1-fZHqN9JfrDBa8A@jetpack'
self.failUnlessEqual(setting.get('data-jetpack-id'), packageName)
self.failUnlessEqual(setting.get('pref'),
'extensions.' + packageName + '.' + name)
@ -88,6 +88,25 @@ class PrefsTests(unittest.TestCase):
]
self.failUnlessEqual(prefsjs, "\n".join(exp)+"\n")
def testPackageWithPreferencesBranch(self):
self.makexpi('preferences-branch')
self.failUnless('options.xul' in self.xpi.namelist())
optsxul = self.xpi.read('options.xul').decode("utf-8")
self.failUnlessEqual(self.xpi_harness_options["preferencesBranch"],
"human-readable")
root = ElementTree.XML(optsxul.encode('utf-8'))
xulNamespacePrefix = \
"{http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul}"
setting = root.find(xulNamespacePrefix + 'setting')
self.failUnlessEqual(setting.get('pref'),
'extensions.human-readable.test42')
prefsjs = self.xpi.read('defaults/preferences/prefs.js').decode("utf-8")
self.failUnlessEqual(prefsjs,
'pref("extensions.human-readable.test42", true);\n')
def testPackageWithNoPrefs(self):
self.makexpi('no-prefs')
self.failIf('options.xul' in self.xpi.namelist())
@ -96,6 +115,33 @@ class PrefsTests(unittest.TestCase):
prefsjs = self.xpi.read('defaults/preferences/prefs.js').decode("utf-8")
self.failUnlessEqual(prefsjs, "")
def testPackageWithInvalidPreferencesBranch(self):
self.makexpi('curly-id')
self.failIfEqual(self.xpi_harness_options["preferencesBranch"],
"invalid^branch*name")
self.failUnlessEqual(self.xpi_harness_options["preferencesBranch"],
"{34a1eae1-c20a-464f-9b0e-000000000000}")
def testPackageWithCurlyID(self):
self.makexpi('curly-id')
self.failUnlessEqual(self.xpi_harness_options["jetpackID"],
"{34a1eae1-c20a-464f-9b0e-000000000000}")
self.failUnless('options.xul' in self.xpi.namelist())
optsxul = self.xpi.read('options.xul').decode("utf-8")
root = ElementTree.XML(optsxul.encode('utf-8'))
xulNamespacePrefix = \
"{http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul}"
setting = root.find(xulNamespacePrefix + 'setting')
self.failUnlessEqual(setting.get('pref'),
'extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13')
prefsjs = self.xpi.read('defaults/preferences/prefs.js').decode("utf-8")
self.failUnlessEqual(prefsjs,
'pref("extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test13", 26);\n')
class Bug588119Tests(unittest.TestCase):
def makexpi(self, pkg_name):

View File

@ -77,14 +77,15 @@ def build_xpi(template_root_dir, manifest, xpi_path,
validate_prefs(harness_options["preferences"])
opts_xul = parse_options(harness_options["preferences"],
harness_options["jetpackID"])
harness_options["jetpackID"],
harness_options["preferencesBranch"])
open('.options.xul', 'wb').write(opts_xul.encode("utf-8"))
zf.write('.options.xul', 'options.xul')
os.remove('.options.xul')
from options_defaults import parse_options_defaults
prefs_js = parse_options_defaults(harness_options["preferences"],
harness_options["jetpackID"])
harness_options["preferencesBranch"])
open('.prefs.js', 'wb').write(prefs_js.encode("utf-8"))
else:

View File

@ -0,0 +1,45 @@
/* 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 { id } = require('sdk/self');
const simple = require('sdk/simple-prefs');
const service = require('sdk/preferences/service');
const { preferencesBranch } = require('@loader/options');
const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm');
exports.testCurlyID = function(assert) {
assert.equal(id, '{34a1eae1-c20a-464f-9b0e-000000000000}', 'curly ID is curly');
assert.equal(simple.prefs.test13, 26, 'test13 is 26');
simple.prefs.test14 = '15';
assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), '15', 'test14 is 15');
assert.equal(service.get('extensions.{34a1eae1-c20a-464f-9b0e-000000000000}.test14'), simple.prefs.test14, 'simple test14 also 15');
}
exports.testInvalidPreferencesBranch = function(assert) {
assert.notEqual(preferencesBranch, 'invalid^branch*name', 'invalid preferences-branch value ignored');
assert.equal(preferencesBranch, '{34a1eae1-c20a-464f-9b0e-000000000000}', 'preferences-branch is {34a1eae1-c20a-464f-9b0e-000000000000}');
}
// from `/test/test-self.js`, adapted to `sdk/test/assert` API
exports.testSelfID = function(assert, done) {
assert.equal(typeof(id), 'string', 'self.id is a string');
assert.ok(id.length > 0, 'self.id not empty');
AddonManager.getAddonByID(id, function(addon) {
assert.ok(addon, 'found addon with self.id');
done();
});
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -0,0 +1,14 @@
{
"id": "{34a1eae1-c20a-464f-9b0e-000000000000}",
"fullName": "curly ID test",
"author": "Tomislav Jovanovic",
"preferences": [{
"name": "test13",
"type": "integer",
"title": "test13",
"value": 26
}],
"preferences-branch": "invalid^branch*name"
}

View File

@ -0,0 +1,35 @@
/* 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 { id } = require('sdk/self');
const simple = require('sdk/simple-prefs');
const service = require('sdk/preferences/service');
const { preferencesBranch } = require('@loader/options');
const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm', {});
const expected_id = 'predefined-id@test';
exports.testExpectedID = function(assert) {
assert.equal(id, expected_id, 'ID is as expected');
assert.equal(preferencesBranch, expected_id, 'preferences-branch is ' + expected_id);
assert.equal(simple.prefs.test, 5, 'test pref is 5');
simple.prefs.test2 = '25';
assert.equal(service.get('extensions.'+expected_id+'.test2'), '25', 'test pref is 25');
assert.equal(service.get('extensions.'+expected_id+'.test2'), simple.prefs.test2, 'test pref is 25');
}
exports.testSelfID = function(assert, done) {
assert.equal(typeof(id), 'string', 'self.id is a string');
assert.ok(id.length > 0, 'self.id not empty');
AddonManager.getAddonByID(id, function(addon) {
assert.equal(addon.id, id, 'found addon with self.id');
done();
});
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -0,0 +1,11 @@
{
"id": "predefined-id@test",
"fullName": "predefined ID test",
"author": "Erik Vold",
"preferences": [{
"name": "test",
"type": "integer",
"title": "test",
"value": 5
}]
}

View File

@ -0,0 +1,36 @@
/* 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 { id } = require('sdk/self');
const simple = require('sdk/simple-prefs');
const service = require('sdk/preferences/service');
const { preferencesBranch } = require('@loader/options');
const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm');
exports.testPreferencesBranch = function(assert) {
assert.equal(preferencesBranch, 'human-readable', 'preferencesBranch is human-readable');
assert.equal(simple.prefs.test42, true, 'test42 is true');
simple.prefs.test43 = 'movie';
assert.equal(service.get('extensions.human-readable.test43'), 'movie', 'test43 is a movie');
}
// from `/test/test-self.js`, adapted to `sdk/test/assert` API
exports.testSelfID = function(assert, done) {
assert.equal(typeof(id), 'string', 'self.id is a string');
assert.ok(id.length > 0, 'self.id not empty');
AddonManager.getAddonByID(id, function(addon) {
assert.ok(addon, 'found addon with self.id');
done();
});
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -0,0 +1,14 @@
{
"id": "test-preferences-branch",
"fullName": "preferences-branch test",
"author": "Tomislav Jovanovic",
"preferences": [{
"name": "test42",
"type": "bool",
"title": "test42",
"value": true
}],
"preferences-branch": "human-readable"
}

View File

@ -11,6 +11,7 @@ merge(module.exports,
require('./test-tabs'),
require('./test-page-mod'),
require('./test-private-browsing'),
require('./test-sidebar'),
isGlobalPBSupported ? require('./test-global-private-browsing') : {}
);

View File

@ -0,0 +1,86 @@
/* 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');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { fromIterator } = require('sdk/util/array');
const BLANK_IMG = exports.BLANK_IMG = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
'menu_socialSidebar',
'menu_historySidebar',
'menu_bookmarksSidebar'
];
function isSidebarShowing(window) {
window = window || getMostRecentBrowserWindow();
let sidebar = window.document.getElementById('sidebar-box');
return !sidebar.hidden;
}
exports.isSidebarShowing = isSidebarShowing;
function getSidebarMenuitems(window) {
window = window || getMostRecentBrowserWindow();
return fromIterator(window.document.querySelectorAll('#viewSidebarMenu menuitem'));
}
exports.getSidebarMenuitems = getSidebarMenuitems;
function getExtraSidebarMenuitems() {
let menuitems = getSidebarMenuitems();
return menuitems.filter(function(mi) {
return BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) < 0;
});
}
exports.getExtraSidebarMenuitems = getExtraSidebarMenuitems;
function makeID(id) {
return 'jetpack-sidebar-' + id;
}
exports.makeID = makeID;
function simulateCommand(ele) {
let window = ele.ownerDocument.defaultView;
let { document } = window;
var evt = document.createEvent('XULCommandEvent');
evt.initCommandEvent('command', true, true, window,
0, false, false, false, false, null);
ele.dispatchEvent(evt);
}
exports.simulateCommand = simulateCommand;
function simulateClick(ele) {
let window = ele.ownerDocument.defaultView;
let { document } = window;
let evt = document.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
ele.dispatchEvent(evt);
}
exports.simulateClick = simulateClick;
function getWidget(buttonId, window = getMostRecentBrowserWindow()) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_NAVBAR } = CustomizableUI;
let widgets = CustomizableUI.getWidgetsInArea(AREA_NAVBAR).
filter(({id}) => id.startsWith('button--') && id.endsWith(buttonId));
if (widgets.length === 0)
throw new Error('Widget with id `' + buttonId +'` not found.');
if (widgets.length > 1)
throw new Error('Unexpected number of widgets: ' + widgets.length)
return widgets[0].forWindow(window);
};
exports.getWidget = getWidget;
// OSX and Windows exhibit different behaviors when 'checked' is false,
// so compare against the consistent 'true'. See bug 894809.
function isChecked(node) {
return node.getAttribute('checked') === 'true';
};
exports.isChecked = isChecked;

View File

@ -0,0 +1,217 @@
/* 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 { Loader } = require('sdk/test/loader');
const { show, hide } = require('sdk/ui/sidebar/actions');
const { isShowing } = require('sdk/ui/sidebar/utils');
const { getMostRecentBrowserWindow, isWindowPrivate } = require('sdk/window/utils');
const { open, close, focus, promise: windowPromise } = require('sdk/window/helpers');
const { setTimeout } = require('sdk/timers');
const { isPrivate } = require('sdk/private-browsing');
const { data } = require('sdk/self');
const { URL } = require('sdk/url');
const { BLANK_IMG, BUILTIN_SIDEBAR_MENUITEMS, isSidebarShowing,
getSidebarMenuitems, getExtraSidebarMenuitems, makeID, simulateCommand,
simulateClick, getWidget, isChecked } = require('./sidebar/utils');
exports.testSideBarIsInNewPrivateWindows = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testSideBarIsInNewPrivateWindows';
let sidebar = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+testName
});
let startWindow = getMostRecentBrowserWindow();
let ele = startWindow.document.getElementById(makeID(testName));
assert.ok(ele, 'sidebar element was added');
open(null, { features: { private: true } }).then(function(window) {
let ele = window.document.getElementById(makeID(testName));
assert.ok(isPrivate(window), 'the new window is private');
assert.ok(!!ele, 'sidebar element was added');
sidebar.destroy();
assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
assert.ok(!startWindow.document.getElementById(makeID(testName)), 'sidebar id DNE');
close(window).then(done, assert.fail);
})
}
exports.testSidebarIsOpenInNewPrivateWindow = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testSidebarIsOpenInNewPrivateWindow';
let window = getMostRecentBrowserWindow();
let sidebar = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+testName
});
assert.equal(isPrivate(window), false, 'the window is not private');
sidebar.on('show', function() {
assert.equal(isSidebarShowing(window), true, 'the sidebar is showing');
assert.equal(isShowing(sidebar), true, 'the sidebar is showing');
windowPromise(window.OpenBrowserWindow({private: true}), 'DOMContentLoaded').then(function(window2) {
assert.equal(isPrivate(window2), true, 'the new window is private');
let sidebarEle = window2.document.getElementById('sidebar');
// wait for the sidebar to load something
function onSBLoad() {
sidebarEle.contentDocument.getElementById('web-panels-browser').addEventListener('load', function() {
assert.equal(isSidebarShowing(window), true, 'the sidebar is showing in old window still');
assert.equal(isSidebarShowing(window2), true, 'the sidebar is showing in the new private window');
assert.equal(isShowing(sidebar), true, 'the sidebar is showing');
sidebar.destroy();
close(window2).then(done);
}, true);
}
sidebarEle.addEventListener('load', onSBLoad, true);
assert.pass('waiting for the sidebar to open...');
}, assert.fail).then(null, assert.fail);
});
sidebar.show();
}
// TEST: edge case where web panel is destroyed while loading
exports.testDestroyEdgeCaseBugWithPrivateWindow = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testDestroyEdgeCaseBug';
let window = getMostRecentBrowserWindow();
let sidebar = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+testName
});
// NOTE: purposely not listening to show event b/c the event happens
// between now and then.
sidebar.show();
assert.equal(isPrivate(window), false, 'the new window is not private');
assert.equal(isSidebarShowing(window), true, 'the sidebar is showing');
//assert.equal(isShowing(sidebar), true, 'the sidebar is showing');
open(null, { features: { private: true } }).then(focus).then(function(window2) {
assert.equal(isPrivate(window2), true, 'the new window is private');
assert.equal(isSidebarShowing(window2), false, 'the sidebar is not showing');
assert.equal(isShowing(sidebar), false, 'the sidebar is not showing');
sidebar.destroy();
assert.pass('destroying the sidebar');
close(window2).then(function() {
let loader = Loader(module);
assert.equal(isPrivate(window), false, 'the current window is not private');
let sidebar = loader.require('sdk/ui/sidebar').Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+ testName,
onShow: function() {
assert.pass('onShow works for Sidebar');
loader.unload();
let sidebarMI = getSidebarMenuitems();
for each (let mi in sidebarMI) {
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
}
assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
assert.equal(isSidebarShowing(window), false, 'the sidebar is not showing');
done();
}
})
sidebar.show();
assert.pass('showing the sidebar');
});
});
}
exports.testShowInPrivateWindow = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testShowInPrivateWindow';
let window1 = getMostRecentBrowserWindow();
let url = 'data:text/html;charset=utf-8,'+testName;
let sidebar1 = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: url
});
let menuitemID = makeID(sidebar1.id);
assert.equal(sidebar1.url, url, 'url getter works');
assert.equal(isShowing(sidebar1), false, 'the sidebar is not showing');
assert.ok(!isChecked(window1.document.getElementById(menuitemID)),
'the menuitem is not checked');
assert.equal(isSidebarShowing(window1), false, 'the new window sidebar is not showing');
windowPromise(window1.OpenBrowserWindow({ private: true }), 'load').then(function(window) {
let { document } = window;
assert.equal(isWindowPrivate(window), true, 'new window is private');
assert.equal(isPrivate(window), true, 'new window is private');
sidebar1.show().then(
function good() {
assert.equal(isShowing(sidebar1), true, 'the sidebar is showing');
assert.ok(!!document.getElementById(menuitemID),
'the menuitem exists on the private window');
assert.equal(isSidebarShowing(window), true, 'the new window sidebar is showing');
sidebar1.destroy();
assert.equal(isSidebarShowing(window), false, 'the new window sidebar is showing');
assert.ok(!window1.document.getElementById(menuitemID),
'the menuitem on the new window dne');
// test old window state
assert.equal(isSidebarShowing(window1), false, 'the old window sidebar is not showing');
assert.equal(window1.document.getElementById(menuitemID),
null,
'the menuitem on the old window dne');
close(window).then(done);
},
function bad() {
assert.fail('a successful show should not happen here..');
});
}, assert.fail);
}
// 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/sidebar');
}
catch (err) {
if (!/^Unsupported Application/.test(err.message))
throw err;
module.exports = {
'test Unsupported Application': assert => assert.pass(err.message)
}
}

View File

@ -8,6 +8,7 @@ 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('@loader/options');
const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
@ -89,4 +90,8 @@ if (app.is('Firefox')) {
}
}
exports.testDefaultPreferencesBranch = function(assert) {
assert.equal(preferencesBranch, self.id, 'preferencesBranch default the same as self.id');
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -0,0 +1,45 @@
/* 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 { id } = require('sdk/self');
const simple = require('sdk/simple-prefs');
const service = require('sdk/preferences/service');
const { preferencesBranch } = require('@loader/options');
const { AddonManager } = require('chrome').Cu.import('resource://gre/modules/AddonManager.jsm');
exports.testStandardID = function(assert) {
assert.equal(id, 'standard-id@jetpack', 'standard ID is standard');
assert.equal(simple.prefs.test13, 26, 'test13 is 26');
simple.prefs.test14 = '15';
assert.equal(service.get('extensions.standard-id@jetpack.test14'), '15', 'test14 is 15');
assert.equal(service.get('extensions.standard-id@jetpack.test14'), simple.prefs.test14, 'simple test14 also 15');
}
exports.testInvalidPreferencesBranch = function(assert) {
assert.notEqual(preferencesBranch, 'invalid^branch*name', 'invalid preferences-branch value ignored');
assert.equal(preferencesBranch, 'standard-id@jetpack', 'preferences-branch is standard-id@jetpack');
}
// from `/test/test-self.js`, adapted to `sdk/test/assert` API
exports.testSelfID = function(assert, done) {
assert.equal(typeof(id), 'string', 'self.id is a string');
assert.ok(id.length > 0, 'self.id not empty');
AddonManager.getAddonByID(id, function(addon) {
assert.ok(addon, 'found addon with self.id');
done();
});
}
require('sdk/test/runner').runTestsFromModule(module);

View File

@ -0,0 +1,14 @@
{
"id": "standard-id",
"fullName": "standard ID test",
"author": "Tomislav Jovanovic",
"preferences": [{
"name": "test13",
"type": "integer",
"title": "test13",
"value": 26
}],
"preferences-branch": "invalid^branch*name"
}

View File

@ -0,0 +1,92 @@
/* 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': '> 24'
}
};
const { Cu } = require('chrome');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { fromIterator } = require('sdk/util/array');
const BLANK_IMG = exports.BLANK_IMG = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
'menu_socialSidebar',
'menu_historySidebar',
'menu_bookmarksSidebar'
];
function isSidebarShowing(window) {
window = window || getMostRecentBrowserWindow();
let sidebar = window.document.getElementById('sidebar-box');
return !sidebar.hidden;
}
exports.isSidebarShowing = isSidebarShowing;
function getSidebarMenuitems(window) {
window = window || getMostRecentBrowserWindow();
return fromIterator(window.document.querySelectorAll('#viewSidebarMenu menuitem'));
}
exports.getSidebarMenuitems = getSidebarMenuitems;
function getExtraSidebarMenuitems() {
let menuitems = getSidebarMenuitems();
return menuitems.filter(function(mi) {
return BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) < 0;
});
}
exports.getExtraSidebarMenuitems = getExtraSidebarMenuitems;
function makeID(id) {
return 'jetpack-sidebar-' + id;
}
exports.makeID = makeID;
function simulateCommand(ele) {
let window = ele.ownerDocument.defaultView;
let { document } = window;
var evt = document.createEvent('XULCommandEvent');
evt.initCommandEvent('command', true, true, window,
0, false, false, false, false, null);
ele.dispatchEvent(evt);
}
exports.simulateCommand = simulateCommand;
function simulateClick(ele) {
let window = ele.ownerDocument.defaultView;
let { document } = window;
let evt = document.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
ele.dispatchEvent(evt);
}
exports.simulateClick = simulateClick;
function getWidget(buttonId, window = getMostRecentBrowserWindow()) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_NAVBAR } = CustomizableUI;
let widgets = CustomizableUI.getWidgetsInArea(AREA_NAVBAR).
filter(({id}) => id.startsWith('button--') && id.endsWith(buttonId));
if (widgets.length === 0)
throw new Error('Widget with id `' + buttonId +'` not found.');
if (widgets.length > 1)
throw new Error('Unexpected number of widgets: ' + widgets.length)
return widgets[0].forWindow(window);
};
exports.getWidget = getWidget;
// OSX and Windows exhibit different behaviors when 'checked' is false,
// so compare against the consistent 'true'. See bug 894809.
function isChecked(node) {
return node.getAttribute('checked') === 'true';
};
exports.isChecked = isChecked;

View File

@ -20,9 +20,7 @@ const ERR_FENNEC_MSG = {
};
// TEST: tab unloader
exports.testAutomaticDestroy = function(test) {
test.waitUntilDone();
exports.testAutomaticDestroy = function(assert, done) {
let called = false;
let loader2 = Loader(module);
@ -32,36 +30,36 @@ exports.testAutomaticDestroy = function(test) {
let tabs2Len = tabs2.length;
tabs2.on('open', function onOpen(tab) {
test.fail("an onOpen listener was called that should not have been");
assert.fail("an onOpen listener was called that should not have been");
called = true;
});
tabs2.on('ready', function onReady(tab) {
test.fail("an onReady listener was called that should not have been");
assert.fail("an onReady listener was called that should not have been");
called = true;
});
tabs2.on('select', function onSelect(tab) {
test.fail("an onSelect listener was called that should not have been");
assert.fail("an onSelect listener was called that should not have been");
called = true;
});
tabs2.on('close', function onClose(tab) {
test.fail("an onClose listener was called that should not have been");
assert.fail("an onClose listener was called that should not have been");
called = true;
});
loader2.unload();
tabs3.on('open', function onOpen(tab) {
test.pass("an onOpen listener was called for tabs3");
assert.pass("an onOpen listener was called for tabs3");
tab.on('ready', function onReady(tab) {
test.fail("an onReady listener was called that should not have been");
assert.fail("an onReady listener was called that should not have been");
called = true;
});
tab.on('select', function onSelect(tab) {
test.fail("an onSelect listener was called that should not have been");
assert.fail("an onSelect listener was called that should not have been");
called = true;
});
tab.on('close', function onClose(tab) {
test.fail("an onClose listener was called that should not have been");
assert.fail("an onClose listener was called that should not have been");
called = true;
});
});
@ -70,22 +68,22 @@ exports.testAutomaticDestroy = function(test) {
// Fire a tab event and ensure that the destroyed tab is inactive
tabs.once('open', function(tab) {
test.pass('tabs.once("open") works!');
assert.pass('tabs.once("open") works!');
test.assertEqual(tabs2Len, tabs2.length, "tabs2 length was not changed");
test.assertEqual(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length");
assert.equal(tabs2Len, tabs2.length, "tabs2 length was not changed");
assert.equal(tabs.length, (tabs2.length+2), "tabs.length > tabs2.length");
tab.once('ready', function() {
test.pass('tab.once("ready") works!');
assert.pass('tab.once("ready") works!');
tab.once('close', function() {
test.pass('tab.once("close") works!');
assert.pass('tab.once("close") works!');
timer.setTimeout(function () {
test.assert(!called, "Unloaded tab module is destroyed and inactive");
assert.ok(!called, "Unloaded tab module is destroyed and inactive");
// end test
test.done();
done();
});
});
@ -97,8 +95,7 @@ exports.testAutomaticDestroy = function(test) {
};
// TEST: tab properties
exports.testTabProperties = function(test) {
test.waitUntilDone();
exports.testTabProperties = function(assert, done) {
let { loader, messages } = LoaderWithHookedConsole();
let tabs = loader.require('sdk/tabs');
@ -107,39 +104,37 @@ exports.testTabProperties = function(test) {
tabs.open({
url: url,
onReady: function(tab) {
test.assertEqual(tab.title, "foo", "title of the new tab matches");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assert(tab.favicon, "favicon of the new tab is not empty");
assert.equal(tab.title, "foo", "title of the new tab matches");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.ok(tab.favicon, "favicon of the new tab is not empty");
// TODO: remove need for this test by implementing the favicon feature
test.assertEqual(messages[0].msg,
assert.equal(messages[0].msg,
"tab.favicon is deprecated, and " +
"currently favicon helpers are not yet supported " +
"by Fennec",
"favicon logs an error for now");
test.assertEqual(tab.style, null, "style of the new tab matches");
test.assertEqual(tab.index, tabsLen, "index of the new tab matches");
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
test.assertNotEqual(tab.id, null, "a tab object always has an id property");
assert.equal(tab.style, null, "style of the new tab matches");
assert.equal(tab.index, tabsLen, "index of the new tab matches");
assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
assert.notEqual(tab.id, null, "a tab object always has an id property");
tab.close(function() {
loader.unload();
// end test
test.done();
done();
});
}
});
};
// TEST: tabs iterator and length property
exports.testTabsIteratorAndLength = function(test) {
test.waitUntilDone();
exports.testTabsIteratorAndLength = function(assert, done) {
let newTabs = [];
let startCount = 0;
for each (let t in tabs) startCount++;
test.assertEqual(startCount, tabs.length, "length property is correct");
assert.equal(startCount, tabs.length, "length property is correct");
let url = "data:text/html;charset=utf-8,testTabsIteratorAndLength";
tabs.open({url: url, onOpen: function(tab) newTabs.push(tab)});
@ -149,26 +144,21 @@ exports.testTabsIteratorAndLength = function(test) {
onOpen: function(tab) {
let count = 0;
for each (let t in tabs) count++;
test.assertEqual(count, startCount + 3, "iterated tab count matches");
test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property");
assert.equal(count, startCount + 3, "iterated tab count matches");
assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property");
let newTabsLength = newTabs.length;
newTabs.forEach(function(t) t.close(function() {
if (--newTabsLength > 0) return;
tab.close(function() {
// end test
test.done();
});
tab.close(done);
}));
}
});
};
// TEST: tab.url setter
exports.testTabLocation = function(test) {
test.waitUntilDone();
exports.testTabLocation = function(assert, done) {
let url1 = "data:text/html;charset=utf-8,foo";
let url2 = "data:text/html;charset=utf-8,bar";
@ -177,12 +167,9 @@ exports.testTabLocation = function(test) {
return;
tabs.removeListener('ready', onReady);
test.pass("tab loaded the correct url");
assert.pass("tab loaded the correct url");
tab.close(function() {
// end test
test.done();
});
tab.close(done);
});
tabs.open({
@ -194,9 +181,7 @@ exports.testTabLocation = function(test) {
};
// TEST: tab.move()
exports.testTabMove = function(test) {
test.waitUntilDone();
exports.testTabMove = function(assert, done) {
let { loader, messages } = LoaderWithHookedConsole();
let tabs = loader.require('sdk/tabs');
@ -205,22 +190,22 @@ exports.testTabMove = function(test) {
tabs.open({
url: url,
onOpen: function(tab1) {
test.assert(tab1.index >= 0, "opening a tab returns a tab w/ valid index");
assert.ok(tab1.index >= 0, "opening a tab returns a tab w/ valid index");
tabs.open({
url: url,
onOpen: function(tab) {
let i = tab.index;
test.assert(tab.index > tab1.index, "2nd tab has valid index");
assert.ok(tab.index > tab1.index, "2nd tab has valid index");
tab.index = 0;
test.assertEqual(tab.index, i, "tab index after move matches");
test.assertEqual(JSON.stringify(messages),
assert.equal(tab.index, i, "tab index after move matches");
assert.equal(JSON.stringify(messages),
JSON.stringify([ERR_FENNEC_MSG]),
"setting tab.index logs error");
// end test
tab1.close(function() tab.close(function() {
loader.unload();
test.done();
done();
}));
}
});
@ -229,9 +214,7 @@ exports.testTabMove = function(test) {
};
// TEST: open tab with default options
exports.testTabsOpen_alt = function(test) {
test.waitUntilDone();
exports.testTabsOpen_alt = function(assert, done) {
let { loader, messages } = LoaderWithHookedConsole();
let tabs = loader.require('sdk/tabs');
let url = "data:text/html;charset=utf-8,default";
@ -239,24 +222,22 @@ exports.testTabsOpen_alt = function(test) {
tabs.open({
url: url,
onReady: function(tab) {
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assertEqual(tabs.activeTab, tab, "URL of active tab in the current window matches");
test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
test.assertEqual(messages.length, 1, "isPinned logs error");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.equal(tabs.activeTab, tab, "URL of active tab in the current window matches");
assert.equal(tab.isPinned, false, "The new tab is not pinned");
assert.equal(messages.length, 1, "isPinned logs error");
// end test
tab.close(function() {
loader.unload();
test.done();
done();
});
}
});
};
// TEST: open pinned tab
exports.testOpenPinned_alt = function(test) {
test.waitUntilDone();
exports.testOpenPinned_alt = function(assert, done) {
let { loader, messages } = LoaderWithHookedConsole();
let tabs = loader.require('sdk/tabs');
let url = "about:blank";
@ -265,26 +246,24 @@ exports.testOpenPinned_alt = function(test) {
url: url,
isPinned: true,
onOpen: function(tab) {
test.assertEqual(tab.isPinned, false, "The new tab is pinned");
assert.equal(tab.isPinned, false, "The new tab is pinned");
// We get two error message: one for tabs.open's isPinned argument
// and another one for tab.isPinned
test.assertEqual(JSON.stringify(messages),
assert.equal(JSON.stringify(messages),
JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
"isPinned logs error");
// end test
tab.close(function() {
loader.unload();
test.done();
done();
});
}
});
};
// TEST: pin/unpin opened tab
exports.testPinUnpin_alt = function(test) {
test.waitUntilDone();
exports.testPinUnpin_alt = function(assert, done) {
let { loader, messages } = LoaderWithHookedConsole();
let tabs = loader.require('sdk/tabs');
let url = "data:text/html;charset=utf-8,default";
@ -293,8 +272,8 @@ exports.testPinUnpin_alt = function(test) {
url: url,
onOpen: function(tab) {
tab.pin();
test.assertEqual(tab.isPinned, false, "The tab was pinned correctly");
test.assertEqual(JSON.stringify(messages),
assert.equal(tab.isPinned, false, "The tab was pinned correctly");
assert.equal(JSON.stringify(messages),
JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
"tab.pin() logs error");
@ -302,35 +281,33 @@ exports.testPinUnpin_alt = function(test) {
messages.length = 0;
tab.unpin();
test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
test.assertEqual(JSON.stringify(messages),
assert.equal(tab.isPinned, false, "The tab was unpinned correctly");
assert.equal(JSON.stringify(messages),
JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
"tab.unpin() logs error");
// end test
tab.close(function() {
loader.unload();
test.done();
done();
});
}
});
};
// TEST: open tab in background
exports.testInBackground = function(test) {
test.waitUntilDone();
exports.testInBackground = function(assert, done) {
let activeUrl = tabs.activeTab.url;
let url = "data:text/html;charset=utf-8,background";
let window = windows.browserWindows.activeWindow;
tabs.once('ready', function onReady(tab) {
test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
test.assertEqual(tab.url, url, "URL of the new background tab matches");
test.assertEqual(windows.browserWindows.activeWindow, window, "a new window was not opened");
test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
assert.equal(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
assert.equal(tab.url, url, "URL of the new background tab matches");
assert.equal(windows.browserWindows.activeWindow, window, "a new window was not opened");
assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
// end test
tab.close(function() test.done());
tab.close(done);
});
tabs.open({
@ -340,9 +317,7 @@ exports.testInBackground = function(test) {
};
// TEST: open tab in new window
exports.testOpenInNewWindow = function(test) {
test.waitUntilDone();
exports.testOpenInNewWindow = function(assert, done) {
let url = "data:text/html;charset=utf-8,newwindow";
let window = windows.browserWindows.activeWindow;
@ -350,20 +325,18 @@ exports.testOpenInNewWindow = function(test) {
url: url,
inNewWindow: true,
onReady: function(tab) {
test.assertEqual(windows.browserWindows.length, 1, "a new window was not opened");
test.assertEqual(windows.browserWindows.activeWindow, window, "old window is active");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assertEqual(tabs.activeTab, tab, "tab is the activeTab");
assert.equal(windows.browserWindows.length, 1, "a new window was not opened");
assert.equal(windows.browserWindows.activeWindow, window, "old window is active");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.equal(tabs.activeTab, tab, "tab is the activeTab");
tab.close(function() test.done());
tab.close(done);
}
});
};
// TEST: onOpen event handler
exports.testTabsEvent_onOpen = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onOpen = function(assert, done) {
let url = URL.replace('#title#', 'testTabsEvent_onOpen');
let eventCount = 0;
@ -375,21 +348,19 @@ exports.testTabsEvent_onOpen = function(test) {
// add listener via collection add
tabs.on('open', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('open', listener1);
tabs.removeListener('open', listener2);
// ends test
tab.close(function() test.done());
tab.close(done);
});
tabs.open(url);
};
// TEST: onClose event handler
exports.testTabsEvent_onClose = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onClose = function(assert, done) {
let url = "data:text/html;charset=utf-8,onclose";
let eventCount = 0;
@ -401,12 +372,12 @@ exports.testTabsEvent_onClose = function(test) {
// add listener via collection add
tabs.on('close', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('close', listener1);
tabs.removeListener('close', listener2);
// end test
test.done();
done();
});
tabs.on('ready', function onReady(tab) {
@ -418,9 +389,7 @@ exports.testTabsEvent_onClose = function(test) {
};
// TEST: onClose event handler when a window is closed
exports.testTabsEvent_onCloseWindow = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onCloseWindow = function(assert, done) {
let closeCount = 0, individualCloseCount = 0;
function listener() {
closeCount++;
@ -434,12 +403,12 @@ exports.testTabsEvent_onCloseWindow = function(test) {
if (++openTabs == 3) {
tabs.removeListener("close", listener);
test.assertEqual(closeCount, 3, "Correct number of close events received");
test.assertEqual(individualCloseCount, 3,
assert.equal(closeCount, 3, "Correct number of close events received");
assert.equal(individualCloseCount, 3,
"Each tab with an attached onClose listener received a close " +
"event when the window was closed");
test.done();
done();
}
});
}
@ -464,9 +433,7 @@ exports.testTabsEvent_onCloseWindow = function(test) {
};
// TEST: onReady event handler
exports.testTabsEvent_onReady = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onReady = function(assert, done) {
let url = "data:text/html;charset=utf-8,onready";
let eventCount = 0;
@ -478,21 +445,19 @@ exports.testTabsEvent_onReady = function(test) {
// add listener via collection add
tabs.on('ready', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('ready', listener1);
tabs.removeListener('ready', listener2);
// end test
tab.close(function() test.done());
tab.close(done);
});
tabs.open(url);
};
// TEST: onActivate event handler
exports.testTabsEvent_onActivate = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onActivate = function(assert, done) {
let url = "data:text/html;charset=utf-8,onactivate";
let eventCount = 0;
@ -504,22 +469,20 @@ exports.testTabsEvent_onActivate = function(test) {
// add listener via collection add
tabs.on('activate', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
test.assertEqual(tab, tabs.activeTab, 'the active tab is correct');
assert.equal(++eventCount, 2, "both listeners notified");
assert.equal(tab, tabs.activeTab, 'the active tab is correct');
tabs.removeListener('activate', listener1);
tabs.removeListener('activate', listener2);
// end test
tab.close(function() test.done());
tab.close(done);
});
tabs.open(url);
};
// TEST: onDeactivate event handler
exports.testTabsEvent_onDeactivate = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onDeactivate = function(assert, done) {
let url = "data:text/html;charset=utf-8,ondeactivate";
let eventCount = 0;
@ -531,13 +494,13 @@ exports.testTabsEvent_onDeactivate = function(test) {
// add listener via collection add
tabs.on('deactivate', function listener2(tab) {
test.assertEqual(++eventCount, 2, 'both listeners notified');
test.assertNotEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab');
assert.equal(++eventCount, 2, 'both listeners notified');
assert.notEqual(tab, tabs.activeTab, 'the active tab is not the deactivated tab');
tabs.removeListener('deactivate', listener1);
tabs.removeListener('deactivate', listener2);
// end test
tab.close(function() test.done());
tab.close(done);
});
tabs.on('activate', function onActivate(tab) {
@ -550,9 +513,7 @@ exports.testTabsEvent_onDeactivate = function(test) {
};
// TEST: per-tab event handlers
exports.testPerTabEvents = function(test) {
test.waitUntilDone();
exports.testPerTabEvents = function(assert, done) {
let eventCount = 0;
tabs.open({
@ -566,19 +527,18 @@ exports.testPerTabEvents = function(test) {
// add listener via collection add
tab.on('ready', function listener2() {
test.assertEqual(eventCount, 1, "both listeners notified");
assert.equal(eventCount, 1, "both listeners notified");
tab.removeListener('ready', listener1);
tab.removeListener('ready', listener2);
// end test
tab.close(function() test.done());
tab.close(done);
});
}
});
};
exports.testUniqueTabIds = function(test) {
test.waitUntilDone();
exports.testUniqueTabIds = function(assert, done) {
var tabs = require('sdk/tabs');
var tabIds = {};
var steps = [
@ -601,8 +561,8 @@ exports.testUniqueTabIds = function(test) {
});
},
function (index) {
test.assertNotEqual(tabIds.tab1, tabIds.tab2, "Tab ids should be unique.");
test.done();
assert.notEqual(tabIds.tab1, tabIds.tab2, "Tab ids should be unique.");
done();
}
];
@ -617,3 +577,5 @@ exports.testUniqueTabIds = function(test) {
next(0);
}
require('sdk/test').run(exports);

View File

@ -19,19 +19,17 @@ const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
"bWRR9AAAAABJRU5ErkJggg%3D%3D";
// Bug 682681 - tab.title should never be empty
exports.testBug682681_aboutURI = function(test) {
test.waitUntilDone();
exports.testBug682681_aboutURI = function(assert, done) {
let tabStrings = StringBundle('chrome://browser/locale/tabbrowser.properties');
tabs.on('ready', function onReady(tab) {
tabs.removeListener('ready', onReady);
test.assertEqual(tab.title,
assert.equal(tab.title,
tabStrings.get('tabs.emptyTabTitle'),
"title of about: tab is not blank");
tab.close(function() test.done());
tab.close(done);
});
// open a about: url
@ -42,42 +40,36 @@ exports.testBug682681_aboutURI = function(test) {
};
// related to Bug 682681
exports.testTitleForDataURI = function(test) {
test.waitUntilDone();
exports.testTitleForDataURI = function(assert, done) {
tabs.open({
url: "data:text/html;charset=utf-8,<title>tab</title>",
inBackground: true,
onReady: function(tab) {
test.assertEqual(tab.title, "tab", "data: title is not Connecting...");
tab.close(function() test.done());
assert.equal(tab.title, "tab", "data: title is not Connecting...");
tab.close(done);
}
});
};
// TEST: 'BrowserWindow' instance creation on tab 'activate' event
// See bug 648244: there was a infinite loop.
exports.testBrowserWindowCreationOnActivate = function(test) {
test.waitUntilDone();
exports.testBrowserWindowCreationOnActivate = function(assert, done) {
let windows = require("sdk/windows").browserWindows;
let gotActivate = false;
tabs.once('activate', function onActivate(eventTab) {
test.assert(windows.activeWindow, "Is able to fetch activeWindow");
assert.ok(windows.activeWindow, "Is able to fetch activeWindow");
gotActivate = true;
});
open().then(function(window) {
test.assert(gotActivate, "Received activate event before openBrowserWindow's callback is called");
closeBrowserWindow(window, function () test.done());
assert.ok(gotActivate, "Received activate event before openBrowserWindow's callback is called");
close(window).then(done);
});
}
// TEST: tab unloader
exports.testAutomaticDestroy = function(test) {
test.waitUntilDone();
exports.testAutomaticDestroy = function(assert, done) {
// Create a second tab instance that we will destroy
let called = false;
@ -92,20 +84,18 @@ exports.testAutomaticDestroy = function(test) {
// Fire a tab event and ensure that the destroyed tab is inactive
tabs.once('open', function (tab) {
timer.setTimeout(function () {
test.assert(!called, "Unloaded tab module is destroyed and inactive");
tab.close(test.done.bind(test));
assert.ok(!called, "Unloaded tab module is destroyed and inactive");
tab.close(done);
});
});
tabs.open("data:text/html;charset=utf-8,foo");
};
exports.testTabPropertiesInNewWindow = function(test) {
test.waitUntilDone();
exports.testTabPropertiesInNewWindow = function(assert, done) {
let count = 0;
function onReadyOrLoad (tab) {
if (count++) {
close(getOwnerWindow(tab)).then(test.done.bind(test));
close(getOwnerWindow(tab)).then(done);
}
}
@ -114,40 +104,38 @@ exports.testTabPropertiesInNewWindow = function(test) {
inNewWindow: true,
url: url,
onReady: function(tab) {
test.assertEqual(tab.title, "foo", "title of the new tab matches");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assert(tab.favicon, "favicon of the new tab is not empty");
test.assertEqual(tab.style, null, "style of the new tab matches");
test.assertEqual(tab.index, 0, "index of the new tab matches");
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
assert.equal(tab.title, "foo", "title of the new tab matches");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.ok(tab.favicon, "favicon of the new tab is not empty");
assert.equal(tab.style, null, "style of the new tab matches");
assert.equal(tab.index, 0, "index of the new tab matches");
assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
assert.notEqual(tab.id, null, "a tab object always has an id property.");
onReadyOrLoad(tab);
},
onLoad: function(tab) {
test.assertEqual(tab.title, "foo", "title of the new tab matches");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assert(tab.favicon, "favicon of the new tab is not empty");
test.assertEqual(tab.style, null, "style of the new tab matches");
test.assertEqual(tab.index, 0, "index of the new tab matches");
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
assert.equal(tab.title, "foo", "title of the new tab matches");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.ok(tab.favicon, "favicon of the new tab is not empty");
assert.equal(tab.style, null, "style of the new tab matches");
assert.equal(tab.index, 0, "index of the new tab matches");
assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
assert.notEqual(tab.id, null, "a tab object always has an id property.");
onReadyOrLoad(tab);
}
});
};
exports.testTabPropertiesInSameWindow = function(test) {
test.waitUntilDone();
exports.testTabPropertiesInSameWindow = function(assert, done) {
// Get current count of tabs so we know the index of the
// new tab, bug 893846
let tabCount = tabs.length;
let count = 0;
function onReadyOrLoad (tab) {
if (count++) {
tab.close(test.done.bind(test));
tab.close(done);
}
}
@ -155,24 +143,24 @@ exports.testTabPropertiesInSameWindow = function(test) {
tabs.open({
url: url,
onReady: function(tab) {
test.assertEqual(tab.title, "foo", "title of the new tab matches");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assert(tab.favicon, "favicon of the new tab is not empty");
test.assertEqual(tab.style, null, "style of the new tab matches");
test.assertEqual(tab.index, tabCount, "index of the new tab matches");
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
assert.equal(tab.title, "foo", "title of the new tab matches");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.ok(tab.favicon, "favicon of the new tab is not empty");
assert.equal(tab.style, null, "style of the new tab matches");
assert.equal(tab.index, tabCount, "index of the new tab matches");
assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
assert.notEqual(tab.id, null, "a tab object always has an id property.");
onReadyOrLoad(tab);
},
onLoad: function(tab) {
test.assertEqual(tab.title, "foo", "title of the new tab matches");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assert(tab.favicon, "favicon of the new tab is not empty");
test.assertEqual(tab.style, null, "style of the new tab matches");
test.assertEqual(tab.index, tabCount, "index of the new tab matches");
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
assert.equal(tab.title, "foo", "title of the new tab matches");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.ok(tab.favicon, "favicon of the new tab is not empty");
assert.equal(tab.style, null, "style of the new tab matches");
assert.equal(tab.index, tabCount, "index of the new tab matches");
assert.notEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
assert.notEqual(tab.id, null, "a tab object always has an id property.");
onReadyOrLoad(tab);
}
@ -180,9 +168,7 @@ exports.testTabPropertiesInSameWindow = function(test) {
};
// TEST: tab properties
exports.testTabContentTypeAndReload = function(test) {
test.waitUntilDone();
exports.testTabContentTypeAndReload = function(assert, done) {
open().then(focus).then(function(window) {
let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>";
@ -190,11 +176,12 @@ exports.testTabContentTypeAndReload = function(test) {
url: url,
onReady: function(tab) {
if (tab.url === url) {
test.assertEqual(tab.contentType, "text/html");
assert.equal(tab.contentType, "text/html");
tab.url = urlXML;
} else {
test.assertEqual(tab.contentType, "text/xml");
closeBrowserWindow(window, function() test.done());
}
else {
assert.equal(tab.contentType, "text/xml");
close(window).then(done);
}
}
});
@ -202,13 +189,11 @@ exports.testTabContentTypeAndReload = function(test) {
};
// TEST: tabs iterator and length property
exports.testTabsIteratorAndLength = function(test) {
test.waitUntilDone();
exports.testTabsIteratorAndLength = function(assert, done) {
open(null, { features: { chrome: true, toolbar: true } }).then(focus).then(function(window) {
let startCount = 0;
for each (let t in tabs) startCount++;
test.assertEqual(startCount, tabs.length, "length property is correct");
assert.equal(startCount, tabs.length, "length property is correct");
let url = "data:text/html;charset=utf-8,default";
tabs.open(url);
@ -218,19 +203,17 @@ exports.testTabsIteratorAndLength = function(test) {
onOpen: function(tab) {
let count = 0;
for each (let t in tabs) count++;
test.assertEqual(count, startCount + 3, "iterated tab count matches");
test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property");
assert.equal(count, startCount + 3, "iterated tab count matches");
assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property");
closeBrowserWindow(window, function() test.done());
close(window).then(done);
}
});
});
};
// TEST: tab.url setter
exports.testTabLocation = function(test) {
test.waitUntilDone();
exports.testTabLocation = function(assert, done) {
open().then(focus).then(function(window) {
let url1 = "data:text/html;charset=utf-8,foo";
let url2 = "data:text/html;charset=utf-8,bar";
@ -239,8 +222,8 @@ exports.testTabLocation = function(test) {
if (tab.url != url2)
return;
tabs.removeListener('ready', onReady);
test.pass("tab.load() loaded the correct url");
closeBrowserWindow(window, function() test.done());
assert.pass("tab.load() loaded the correct url");
close(window).then(done);
});
tabs.open({
@ -253,119 +236,108 @@ exports.testTabLocation = function(test) {
};
// TEST: tab.close()
exports.testTabClose = function(test) {
test.waitUntilDone();
exports.testTabClose = function(assert, done) {
let url = "data:text/html;charset=utf-8,foo";
test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab");
assert.notEqual(tabs.activeTab.url, url, "tab is not the active tab");
tabs.on('ready', function onReady(tab) {
tabs.removeListener('ready', onReady);
test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
assert.equal(tabs.activeTab.url, tab.url, "tab is now the active tab");
let secondOnCloseCalled = false;
// Bug 699450: Multiple calls to tab.close should not throw
tab.close(function() secondOnCloseCalled = true);
try {
tab.close(function () {
test.assert(secondOnCloseCalled,
assert.ok(secondOnCloseCalled,
"The immediate second call to tab.close gots its callback fired");
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
test.done();
assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
done();
});
}
catch(e) {
test.fail("second call to tab.close() thrown an exception: " + e);
assert.fail("second call to tab.close() thrown an exception: " + e);
}
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
assert.notEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
});
tabs.open(url);
};
// TEST: tab.move()
exports.testTabMove = function(test) {
test.waitUntilDone();
exports.testTabMove = function(assert, done) {
open().then(focus).then(function(window) {
let url = "data:text/html;charset=utf-8,foo";
tabs.open({
url: url,
onOpen: function(tab) {
test.assertEqual(tab.index, 1, "tab index before move matches");
assert.equal(tab.index, 1, "tab index before move matches");
tab.index = 0;
test.assertEqual(tab.index, 0, "tab index after move matches");
closeBrowserWindow(window, function() test.done());
assert.equal(tab.index, 0, "tab index after move matches");
close(window).then(done);
}
});
});
};
// TEST: open tab with default options
exports.testOpen = function(test) {
test.waitUntilDone();
exports.testOpen = function(assert, done) {
let url = "data:text/html;charset=utf-8,default";
tabs.open({
url: url,
onReady: function(tab) {
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.equal(tab.isPinned, false, "The new tab is not pinned");
tab.close(function() test.done());
tab.close(done);
}
});
};
// TEST: opening a pinned tab
exports.testOpenPinned = function(test) {
test.waitUntilDone();
exports.testOpenPinned = function(assert, done) {
let url = "data:text/html;charset=utf-8,default";
tabs.open({
url: url,
isPinned: true,
onOpen: function(tab) {
test.assertEqual(tab.isPinned, true, "The new tab is pinned");
tab.close(test.done.bind(test));
assert.equal(tab.isPinned, true, "The new tab is pinned");
tab.close(done);
}
});
};
// TEST: pin/unpin opened tab
exports.testPinUnpin = function(test) {
test.waitUntilDone();
exports.testPinUnpin = function(assert, done) {
let url = "data:text/html;charset=utf-8,default";
tabs.open({
url: url,
inBackground: true,
onOpen: function(tab) {
tab.pin();
test.assertEqual(tab.isPinned, true, "The tab was pinned correctly");
assert.equal(tab.isPinned, true, "The tab was pinned correctly");
tab.unpin();
test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
tab.close(test.done.bind(test));
assert.equal(tab.isPinned, false, "The tab was unpinned correctly");
tab.close(done);
}
});
}
// TEST: open tab in background
exports.testInBackground = function(test) {
test.waitUntilDone();
exports.testInBackground = function(assert, done) {
let window = getMostRecentBrowserWindow();
let activeUrl = tabs.activeTab.url;
let url = "data:text/html;charset=utf-8,background";
test.assertEqual(activeWindow, window, "activeWindow matches this window");
assert.equal(activeWindow, window, "activeWindow matches this window");
tabs.on('ready', function onReady(tab) {
tabs.removeListener('ready', onReady);
test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
test.assertEqual(tab.url, url, "URL of the new background tab matches");
test.assertEqual(activeWindow, window, "a new window was not opened");
test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
tab.close(test.done.bind(test));
assert.equal(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
assert.equal(tab.url, url, "URL of the new background tab matches");
assert.equal(activeWindow, window, "a new window was not opened");
assert.notEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
tab.close(done);
});
tabs.open({
@ -375,9 +347,7 @@ exports.testInBackground = function(test) {
}
// TEST: open tab in new window
exports.testOpenInNewWindow = function(test) {
test.waitUntilDone();
exports.testOpenInNewWindow = function(assert, done) {
let startWindowCount = windows().length;
let url = "data:text/html;charset=utf-8,testOpenInNewWindow";
@ -386,25 +356,23 @@ exports.testOpenInNewWindow = function(test) {
inNewWindow: true,
onReady: function(tab) {
let newWindow = getOwnerWindow(tab);
test.assertEqual(windows().length, startWindowCount + 1, "a new window was opened");
assert.equal(windows().length, startWindowCount + 1, "a new window was opened");
onFocus(newWindow).then(function() {
test.assertEqual(activeWindow, newWindow, "new window is active");
test.assertEqual(tab.url, url, "URL of the new tab matches");
test.assertEqual(newWindow.content.location, url, "URL of new tab in new window matches");
test.assertEqual(tabs.activeTab.url, url, "URL of activeTab matches");
assert.equal(activeWindow, newWindow, "new window is active");
assert.equal(tab.url, url, "URL of the new tab matches");
assert.equal(newWindow.content.location, url, "URL of new tab in new window matches");
assert.equal(tabs.activeTab.url, url, "URL of activeTab matches");
closeBrowserWindow(newWindow, test.done.bind(test));
}, test.fail).then(null, test.fail);
close(newWindow).then(done);
}, assert.fail).then(null, assert.fail);
}
});
}
// Test tab.open inNewWindow + onOpen combination
exports.testOpenInNewWindowOnOpen = function(test) {
test.waitUntilDone();
exports.testOpenInNewWindowOnOpen = function(assert, done) {
let startWindowCount = windows().length;
let url = "data:text/html;charset=utf-8,newwindow";
@ -415,20 +383,17 @@ exports.testOpenInNewWindowOnOpen = function(test) {
let newWindow = getOwnerWindow(tab);
onFocus(newWindow).then(function() {
test.assertEqual(windows().length, startWindowCount + 1, "a new window was opened");
test.assertEqual(activeWindow, newWindow, "new window is active");
assert.equal(windows().length, startWindowCount + 1, "a new window was opened");
assert.equal(activeWindow, newWindow, "new window is active");
closeBrowserWindow(newWindow, function() {
test.done();
});
close(newWindow).then(done);
});
}
});
};
// TEST: onOpen event handler
exports.testTabsEvent_onOpen = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onOpen = function(assert, done) {
openBrowserWindow(function(window, browser) {
let url = "data:text/html;charset=utf-8,1";
let eventCount = 0;
@ -441,10 +406,10 @@ exports.testTabsEvent_onOpen = function(test) {
// add listener via collection add
tabs.on('open', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('open', listener1);
tabs.removeListener('open', listener2);
closeBrowserWindow(window, function() test.done());
close(window).then(done);
});
tabs.open(url);
@ -452,8 +417,7 @@ exports.testTabsEvent_onOpen = function(test) {
};
// TEST: onClose event handler
exports.testTabsEvent_onClose = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onClose = function(assert, done) {
openBrowserWindow(function(window, browser) {
let url = "data:text/html;charset=utf-8,onclose";
let eventCount = 0;
@ -466,10 +430,10 @@ exports.testTabsEvent_onClose = function(test) {
// add listener via collection add
tabs.on('close', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('close', listener1);
tabs.removeListener('close', listener2);
closeBrowserWindow(window, function() test.done());
close(window).then(done);
});
tabs.on('ready', function onReady(tab) {
@ -482,8 +446,7 @@ exports.testTabsEvent_onClose = function(test) {
};
// TEST: onClose event handler when a window is closed
exports.testTabsEvent_onCloseWindow = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onCloseWindow = function(assert, done) {
let closeCount = 0;
let individualCloseCount = 0;
@ -499,12 +462,12 @@ exports.testTabsEvent_onCloseWindow = function(test) {
return;
}
test.assertEqual(closeCount, 4, "Correct number of close events received");
test.assertEqual(individualCloseCount, 3,
assert.equal(closeCount, 4, "Correct number of close events received");
assert.equal(individualCloseCount, 3,
"Each tab with an attached onClose listener received a close " +
"event when the window was closed");
test.done();
done();
}
// One tab is already open with the window
@ -536,8 +499,7 @@ exports.testTabsEvent_onCloseWindow = function(test) {
}
// TEST: onReady event handler
exports.testTabsEvent_onReady = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onReady = function(assert, done) {
openBrowserWindow(function(window, browser) {
let url = "data:text/html;charset=utf-8,onready";
let eventCount = 0;
@ -550,10 +512,10 @@ exports.testTabsEvent_onReady = function(test) {
// add listener via collection add
tabs.on('ready', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('ready', listener1);
tabs.removeListener('ready', listener2);
closeBrowserWindow(window, function() test.done());
close(window).then(done);
});
tabs.open(url);
@ -561,8 +523,7 @@ exports.testTabsEvent_onReady = function(test) {
};
// TEST: onActivate event handler
exports.testTabsEvent_onActivate = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onActivate = function(assert, done) {
openBrowserWindow(function(window, browser) {
let url = "data:text/html;charset=utf-8,onactivate";
let eventCount = 0;
@ -575,10 +536,10 @@ exports.testTabsEvent_onActivate = function(test) {
// add listener via collection add
tabs.on('activate', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('activate', listener1);
tabs.removeListener('activate', listener2);
closeBrowserWindow(window, function() test.done());
close(window).then(done);
});
tabs.open(url);
@ -586,8 +547,7 @@ exports.testTabsEvent_onActivate = function(test) {
};
// onDeactivate event handler
exports.testTabsEvent_onDeactivate = function(test) {
test.waitUntilDone();
exports.testTabsEvent_onDeactivate = function(assert, done) {
openBrowserWindow(function(window, browser) {
let url = "data:text/html;charset=utf-8,ondeactivate";
let eventCount = 0;
@ -600,10 +560,10 @@ exports.testTabsEvent_onDeactivate = function(test) {
// add listener via collection add
tabs.on('deactivate', function listener2(tab) {
test.assertEqual(++eventCount, 2, "both listeners notified");
assert.equal(++eventCount, 2, "both listeners notified");
tabs.removeListener('deactivate', listener1);
tabs.removeListener('deactivate', listener2);
closeBrowserWindow(window, function() test.done());
close(window).then(done);
});
tabs.on('open', function onOpen(tab) {
@ -616,8 +576,7 @@ exports.testTabsEvent_onDeactivate = function(test) {
};
// pinning
exports.testTabsEvent_pinning = function(test) {
test.waitUntilDone();
exports.testTabsEvent_pinning = function(assert, done) {
openBrowserWindow(function(window, browser) {
let url = "data:text/html;charset=utf-8,1";
@ -628,14 +587,14 @@ exports.testTabsEvent_pinning = function(test) {
tabs.on('pinned', function onPinned(tab) {
tabs.removeListener('pinned', onPinned);
test.assert(tab.isPinned, "notified tab is pinned");
assert.ok(tab.isPinned, "notified tab is pinned");
tab.unpin();
});
tabs.on('unpinned', function onUnpinned(tab) {
tabs.removeListener('unpinned', onUnpinned);
test.assert(!tab.isPinned, "notified tab is not pinned");
closeBrowserWindow(window, function() test.done());
assert.ok(!tab.isPinned, "notified tab is not pinned");
close(window).then(done);
});
tabs.open(url);
@ -643,8 +602,7 @@ exports.testTabsEvent_pinning = function(test) {
};
// TEST: per-tab event handlers
exports.testPerTabEvents = function(test) {
test.waitUntilDone();
exports.testPerTabEvents = function(assert, done) {
openBrowserWindow(function(window, browser) {
let eventCount = 0;
@ -659,19 +617,18 @@ exports.testPerTabEvents = function(test) {
// add listener via collection add
tab.on('ready', function listener2() {
test.assertEqual(eventCount, 1, "both listeners notified");
assert.equal(eventCount, 1, "both listeners notified");
tab.removeListener('ready', listener1);
tab.removeListener('ready', listener2);
closeBrowserWindow(window, function() test.done());
close(window).then(done);
});
}
});
});
};
exports.testAttachOnOpen = function (test) {
exports.testAttachOnOpen = function (assert, done) {
// Take care that attach has to be called on tab ready and not on tab open.
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
tabs.open({
url: "data:text/html;charset=utf-8,foobar",
@ -679,10 +636,10 @@ exports.testAttachOnOpen = function (test) {
let worker = tab.attach({
contentScript: 'self.postMessage(document.location.href); ',
onMessage: function (msg) {
test.assertEqual(msg, "about:blank",
assert.equal(msg, "about:blank",
"Worker document url is about:blank on open");
worker.destroy();
closeBrowserWindow(window, function() test.done());
close(window).then(done);
}
});
}
@ -691,9 +648,8 @@ exports.testAttachOnOpen = function (test) {
});
}
exports.testAttachOnMultipleDocuments = function (test) {
exports.testAttachOnMultipleDocuments = function (assert, done) {
// Example of attach that process multiple tab documents
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let firstLocation = "data:text/html;charset=utf-8,foobar";
let secondLocation = "data:text/html;charset=utf-8,bar";
@ -702,6 +658,7 @@ exports.testAttachOnMultipleDocuments = function (test) {
let worker1 = null;
let worker2 = null;
let detachEventCount = 0;
tabs.open({
url: firstLocation,
onReady: function (tab) {
@ -712,14 +669,14 @@ exports.testAttachOnMultipleDocuments = function (test) {
' function () self.postMessage(document.location.href)' +
');',
onMessage: function (msg) {
test.assertEqual(msg, firstLocation,
assert.equal(msg, firstLocation,
"Worker url is equal to the 1st document");
tab.url = secondLocation;
},
onDetach: function () {
detachEventCount++;
test.pass("Got worker1 detach event");
test.assertRaises(function () {
assert.pass("Got worker1 detach event");
assert.throws(function () {
worker1.postMessage("ex-1");
},
/Couldn't find the worker/,
@ -736,14 +693,14 @@ exports.testAttachOnMultipleDocuments = function (test) {
' function () self.postMessage(document.location.href)' +
');',
onMessage: function (msg) {
test.assertEqual(msg, secondLocation,
assert.equal(msg, secondLocation,
"Worker url is equal to the 2nd document");
tab.url = thirdLocation;
},
onDetach: function () {
detachEventCount++;
test.pass("Got worker2 detach event");
test.assertRaises(function () {
assert.pass("Got worker2 detach event");
assert.throws(function () {
worker2.postMessage("ex-2");
},
/Couldn't find the worker/,
@ -763,18 +720,17 @@ exports.testAttachOnMultipleDocuments = function (test) {
if (detachEventCount != 2)
return;
test.pass("Got all detach events");
assert.pass("Got all detach events");
closeBrowserWindow(window, function() test.done());
close(window).then(done);
}
});
}
exports.testAttachWrappers = function (test) {
exports.testAttachWrappers = function (assert, done) {
// Check that content script has access to wrapped values by default
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let document = "data:text/html;charset=utf-8,<script>var globalJSVar = true; " +
" document.getElementById = 3;</script>";
@ -791,9 +747,9 @@ exports.testAttachWrappers = function (test) {
' self.postMessage(e.message);' +
'}',
onMessage: function (msg) {
test.assertEqual(msg, true, "Worker has wrapped objects ("+count+")");
assert.equal(msg, true, "Worker has wrapped objects ("+count+")");
if (count++ == 1)
closeBrowserWindow(window, function() test.done());
close(window).then(done);
}
});
}
@ -805,9 +761,8 @@ exports.testAttachWrappers = function (test) {
/*
// We do not offer unwrapped access to DOM since bug 601295 landed
// See 660780 to track progress of unwrap feature
exports.testAttachUnwrapped = function (test) {
exports.testAttachUnwrapped = function (assert, done) {
// Check that content script has access to unwrapped values through unsafeWindow
test.waitUntilDone();
openBrowserWindow(function(window, browser) {
let document = "data:text/html;charset=utf-8,<script>var globalJSVar=true;</script>";
let count = 0;
@ -822,8 +777,8 @@ exports.testAttachUnwrapped = function (test) {
' self.postMessage(e.message);' +
'}',
onMessage: function (msg) {
test.assertEqual(msg, true, "Worker has access to javascript content globals ("+count+")");
closeBrowserWindow(window, function() test.done());
assert.equal(msg, true, "Worker has access to javascript content globals ("+count+")");
close(window).then(done);
}
});
}
@ -833,27 +788,25 @@ exports.testAttachUnwrapped = function (test) {
}
*/
exports['test window focus changes active tab'] = function(test) {
test.waitUntilDone();
exports['test window focus changes active tab'] = function(assert, done) {
let url1 = "data:text/html;charset=utf-8," + encodeURIComponent("test window focus changes active tab</br><h1>Window #1");
let win1 = openBrowserWindow(function() {
test.pass("window 1 is open");
assert.pass("window 1 is open");
let win2 = openBrowserWindow(function() {
test.pass("window 2 is open");
assert.pass("window 2 is open");
focus(win2).then(function() {
tabs.on("activate", function onActivate(tab) {
tabs.removeListener("activate", onActivate);
test.pass("activate was called on windows focus change.");
test.assertEqual(tab.url, url1, 'the activated tab url is correct');
assert.pass("activate was called on windows focus change.");
assert.equal(tab.url, url1, 'the activated tab url is correct');
close(win2).then(function() {
test.pass('window 2 was closed');
assert.pass('window 2 was closed');
return close(win1);
}).then(test.done.bind(test));
}).then(done);
});
win1.focus();
@ -862,24 +815,21 @@ exports['test window focus changes active tab'] = function(test) {
}, url1);
};
exports['test ready event on new window tab'] = function(test) {
test.waitUntilDone();
exports['test ready event on new window tab'] = function(assert, done) {
let uri = encodeURI("data:text/html;charset=utf-8,Waiting for ready event!");
require("sdk/tabs").on("ready", function onReady(tab) {
if (tab.url === uri) {
require("sdk/tabs").removeListener("ready", onReady);
test.pass("ready event was emitted");
closeBrowserWindow(window, function() {
test.done();
});
assert.pass("ready event was emitted");
close(window).then(done);
}
});
let window = openBrowserWindow(function(){}, uri);
};
exports['test unique tab ids'] = function(test) {
exports['test unique tab ids'] = function(assert, done) {
var windows = require('sdk/windows').browserWindows;
var { all, defer } = require('sdk/core/promise');
@ -891,9 +841,9 @@ exports['test unique tab ids'] = function(test) {
});
win.on('open', function(window) {
test.assert(window.tabs.length);
test.assert(window.tabs.activeTab);
test.assert(window.tabs.activeTab.id);
assert.ok(window.tabs.length);
assert.ok(window.tabs.activeTab);
assert.ok(window.tabs.activeTab.id);
deferred.resolve({
id: window.tabs.activeTab.id,
win: win
@ -903,32 +853,29 @@ exports['test unique tab ids'] = function(test) {
return deferred.promise;
}
test.waitUntilDone();
var one = openWindow(), two = openWindow();
all([one, two]).then(function(results) {
test.assertNotEqual(results[0].id, results[1].id, "tab Ids should not be equal.");
assert.notEqual(results[0].id, results[1].id, "tab Ids should not be equal.");
results[0].win.close();
results[1].win.close();
test.done();
done();
});
}
// related to Bug 671305
exports.testOnLoadEventWithDOM = function(test) {
test.waitUntilDone();
exports.testOnLoadEventWithDOM = function(assert, done) {
openBrowserWindow(function(window, browser) {
let count = 0;
tabs.on('load', function onLoad(tab) {
test.assertEqual(tab.title, 'tab', 'tab passed in as arg, load called');
assert.equal(tab.title, 'tab', 'tab passed in as arg, load called');
if (!count++) {
tab.reload();
}
else {
// end of test
tabs.removeListener('load', onLoad);
test.pass('onLoad event called on reload');
closeBrowserWindow(window, function() test.done());
assert.pass('onLoad event called on reload');
close(window).then(done);
}
});
@ -941,9 +888,7 @@ exports.testOnLoadEventWithDOM = function(test) {
};
// related to Bug 671305
exports.testOnLoadEventWithImage = function(test) {
test.waitUntilDone();
exports.testOnLoadEventWithImage = function(assert, done) {
openBrowserWindow(function(window, browser) {
let count = 0;
tabs.on('load', function onLoad(tab) {
@ -953,8 +898,8 @@ exports.testOnLoadEventWithImage = function(test) {
else {
// end of test
tabs.removeListener('load', onLoad);
test.pass('onLoad event called on reload with image');
closeBrowserWindow(window, function() test.done());
assert.pass('onLoad event called on reload with image');
close(window).then(done);
}
});
@ -966,23 +911,22 @@ exports.testOnLoadEventWithImage = function(test) {
});
};
exports.testFaviconGetterDeprecation = function (test) {
exports.testFaviconGetterDeprecation = function (assert, done) {
const { LoaderWithHookedConsole } = require("sdk/test/loader");
let { loader, messages } = LoaderWithHookedConsole(module);
let tabs = loader.require('sdk/tabs');
test.waitUntilDone();
tabs.open({
url: 'data:text/html;charset=utf-8,',
onOpen: function (tab) {
let favicon = tab.favicon;
test.assert(messages.length === 1, 'only one error is dispatched');
test.assert(messages[0].type, 'error', 'the console message is an error');
assert.ok(messages.length === 1, 'only one error is dispatched');
assert.ok(messages[0].type, 'error', 'the console message is an error');
let msg = messages[0].msg;
test.assert(msg.indexOf('tab.favicon is deprecated') !== -1,
assert.ok(msg.indexOf('tab.favicon is deprecated') !== -1,
'message contains the given message');
tab.close(test.done.bind(test));
tab.close(done);
loader.unload();
}
});
@ -1027,11 +971,4 @@ function openBrowserWindow(callback, url) {
return window;
}
// Helper for calling code at window close
function closeBrowserWindow(window, callback) {
window.addEventListener("unload", function unload() {
window.removeEventListener("unload", unload, false);
callback();
}, false);
window.close();
}
require('sdk/test').run(exports);

View File

@ -1,7 +1,6 @@
/* 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 { Loader } = require("sdk/test/loader");

View File

@ -132,26 +132,27 @@ exports["test Document Reload"] = function(assert, done) {
let url2 = "data:text/html;charset=utf-8,page2";
let content =
"<script>" +
"window.onload = function() {" +
" setTimeout(function () {" +
"window.addEventListener('message', function() {"+
" window.location = '" + url2 + "';" +
" }, 0);" +
"}" +
'}, false);' +
"</script>";
let messageCount = 0;
let panel = Panel({
// using URL here is intentional, see bug 859009
contentURL: URL("data:text/html;charset=utf-8," + encodeURIComponent(content)),
contentScript: "self.postMessage(window.location.href)",
contentScript: "self.postMessage(window.location.href);" +
// initiate change to url2
"self.port.once('move', function() document.defaultView.postMessage('move', '*'));",
onMessage: function (message) {
messageCount++;
assert.notEqual(message, 'about:blank', 'about:blank is not a message ' + messageCount);
assert.notEqual(message, "about:blank", "about:blank is not a message " + messageCount);
if (messageCount == 1) {
assert.ok(/data:text\/html/.test(message), "First document had a content script " + message);
assert.ok(/data:text\/html/.test(message), "First document had a content script; " + message);
panel.port.emit('move');
}
else if (messageCount == 2) {
assert.equal(message, url2, "Second document too");
assert.equal(message, url2, "Second document too; " + message);
panel.destroy();
done();
}

View File

@ -87,6 +87,9 @@ exports.testSearchURL = function (assert, done) {
});
};
// Disabling due to intermittent Bug 892619
// TODO solve this
/*
exports.testSearchTimeRange = function (assert, done) {
let firstTime, secondTime;
addVisits([
@ -120,7 +123,7 @@ exports.testSearchTimeRange = function (assert, done) {
done();
});
};
*/
exports.testSearchQuery = function (assert, done) {
addVisits([
'http://mozilla.com', 'http://webaud.io', 'http://mozilla.com/webfwd'

View File

@ -3,21 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const app = require("sdk/system/xul-app");
module.metadata = {
'engines': {
'Firefox': '*',
'Fennec': '*'
}
};
if (app.is("Firefox")) {
module.exports = require("./tabs/test-firefox-tabs");
}
else if (app.is("Fennec")) {
module.exports = require("./tabs/test-fennec-tabs");
const app = require('sdk/system/xul-app');
if (app.is('Fennec')) {
module.exports = require('./tabs/test-fennec-tabs');
}
else {
require("test").run({
"test Unsupported Application": function Unsupported (assert) {
assert.pass(
"The tabs module currently supports only Firefox and Fennec." +
"In the future we would like it to support other applications, however."
);
}
});
module.exports = require('./tabs/test-firefox-tabs');
}

View File

@ -57,4 +57,4 @@ exports["test LoaderWithHookedConsole"] = function (assert) {
assert.equal(count, 6, "Called for all messages");
};
require("test").run(exports);
require("sdk/test").run(exports);

View File

@ -0,0 +1,998 @@
/* 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': '> 24'
}
};
const { Cu } = require('chrome');
const { Loader } = require('sdk/test/loader');
const { data } = require('sdk/self');
const { open, focus, close } = require('sdk/window/helpers');
const { setTimeout } = require('sdk/timers');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
function getWidget(buttonId, window = getMostRecentBrowserWindow()) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
const { AREA_NAVBAR } = CustomizableUI;
let widgets = CustomizableUI.getWidgetsInArea(AREA_NAVBAR).
filter(({id}) => id.startsWith('button--') && id.endsWith(buttonId));
if (widgets.length === 0)
throw new Error('Widget with id `' + id +'` not found.');
if (widgets.length > 1)
throw new Error('Unexpected number of widgets: ' + widgets.length)
return widgets[0].forWindow(window);
};
exports['test basic constructor validation'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
assert.throws(
() => Button({}),
/^The option/,
'throws on no option given');
// Test no label
assert.throws(
() => Button({ id: 'my-button', icon: './icon.png'}),
/^The option "label"/,
'throws on no label given');
// Test no id
assert.throws(
() => Button({ label: 'my button', icon: './icon.png' }),
/^The option "id"/,
'throws on no id given');
// Test no icon
assert.throws(
() => Button({ id: 'my-button', label: 'my button' }),
/^The option "icon"/,
'throws on no icon given');
// Test empty label
assert.throws(
() => Button({ id: 'my-button', label: '', icon: './icon.png' }),
/^The option "label"/,
'throws on no valid label given');
// Test invalid id
assert.throws(
() => Button({ id: 'my button', label: 'my button', icon: './icon.png' }),
/^The option "id"/,
'throws on no valid id given');
// Test empty id
assert.throws(
() => Button({ id: '', label: 'my button', icon: './icon.png' }),
/^The option "id"/,
'throws on no valid id given');
// Test remote icon
assert.throws(
() => Button({ id: 'my-button', label: 'my button', icon: 'http://www.mozilla.org/favicon.ico'}),
/^The option "icon"/,
'throws on no valid icon given');
// Test wrong icon: no absolute URI to local resource, neither relative './'
assert.throws(
() => Button({ id: 'my-button', label: 'my button', icon: 'icon.png'}),
/^The option "icon"/,
'throws on no valid icon given');
// Test wrong icon: no absolute URI to local resource, neither relative './'
assert.throws(
() => Button({ id: 'my-button', label: 'my button', icon: 'foo and bar'}),
/^The option "icon"/,
'throws on no valid icon given');
// Test wrong icon: '../' is not allowed
assert.throws(
() => Button({ id: 'my-button', label: 'my button', icon: '../icon.png'}),
/^The option "icon"/,
'throws on no valid icon given');
// Test wrong size: number
assert.throws(
() => Button({
id:'my-button',
label: 'my button',
icon: './icon.png',
size: 32
}),
/^The option "size"/,
'throws on no valid size given');
// Test wrong size: string
assert.throws(
() => Button({
id:'my-button',
label: 'my button',
icon: './icon.png',
size: 'huge'
}),
/^The option "size"/,
'throws on no valid size given');
// Test wrong type
assert.throws(
() => Button({
id:'my-button',
label: 'my button',
icon: './icon.png',
type: 'custom'
}),
/^The option "type"/,
'throws on no valid type given');
loader.unload();
};
exports['test button added'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let button = Button({
id: 'my-button-1',
label: 'my button',
icon: './icon.png'
});
// check defaults
assert.equal(button.size, 'small',
'size is set to default "small" value');
assert.equal(button.disabled, false,
'disabled is set to default `false` value');
assert.equal(button.checked, false,
'checked is set to default `false` value');
assert.equal(button.type, 'button',
'type is set to default "button" value');
let { node } = getWidget(button.id);
assert.ok(!!node, 'The button is in the navbar');
assert.equal(button.label, node.getAttribute('label'),
'label is set');
assert.equal(button.label, node.getAttribute('tooltiptext'),
'tooltip is set');
assert.equal(data.url(button.icon.substr(2)), node.getAttribute('image'),
'icon is set');
assert.equal(button.type, node.getAttribute('type'),
'type is set to default');
assert.equal(16, node.getAttribute('width'),
'width is set to small');
loader.unload();
}
exports['test button added with resource URI'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let button = Button({
id: 'my-button-1',
label: 'my button',
icon: data.url('icon.png')
});
assert.equal(button.icon, data.url('icon.png'),
'icon is set');
let { node } = getWidget(button.id);
assert.equal(button.icon, node.getAttribute('image'),
'icon on node is set');
loader.unload();
}
exports['test button duplicate id'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let button = Button({
id: 'my-button-2',
label: 'my button',
icon: './icon.png'
});
assert.throws(() => {
let doppelganger = Button({
id: 'my-button-2',
label: 'my button',
icon: './icon.png'
});
},
/^The ID/,
'No duplicates allowed');
loader.unload();
}
exports['test button multiple destroy'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let button = Button({
id: 'my-button-2',
label: 'my button',
icon: './icon.png'
});
button.destroy();
button.destroy();
button.destroy();
assert.pass('multiple destroy doesn\'t matter');
loader.unload();
}
exports['test button removed on dispose'] = function(assert, done) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let widgetId;
CustomizableUI.addListener({
onWidgetDestroyed: function(id) {
if (id === widgetId) {
CustomizableUI.removeListener(this);
assert.pass('button properly removed');
loader.unload();
done();
}
}
});
let button = Button({
id: 'my-button-3',
label: 'my button',
icon: './icon.png'
});
// Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
// was removed or it's not in the UX build yet
widgetId = getWidget(button.id).id;
button.destroy();
};
exports['test button global state updated'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let button = Button({
id: 'my-button-4',
label: 'my button',
icon: './icon.png'
});
// Tried to use `getWidgetIdsInArea` but seems undefined, not sure if it
// was removed or it's not in the UX build yet
let { node, id: widgetId } = getWidget(button.id);
// check read-only properties
assert.throws(() => button.id = 'another-id',
/^setting a property that has only a getter/,
'id cannot be set at runtime');
assert.equal(button.id, 'my-button-4',
'id is unchanged');
assert.equal(node.id, widgetId,
'node id is unchanged');
assert.throws(() => button.type = 'checkbox',
/^setting a property that has only a getter/,
'type cannot be set at runtime');
assert.equal(button.type, 'button',
'type is unchanged');
assert.equal(node.getAttribute('type'), button.type,
'node type is unchanged');
assert.throws(() => button.size = 'medium',
/^setting a property that has only a getter/,
'size cannot be set at runtime');
assert.equal(button.size, 'small',
'size is unchanged');
assert.equal(node.getAttribute('width'), 16,
'node width is unchanged');
// check writable properties
button.label = 'New label';
assert.equal(button.label, 'New label',
'label is updated');
assert.equal(node.getAttribute('label'), 'New label',
'node label is updated');
assert.equal(node.getAttribute('tooltiptext'), 'New label',
'node tooltip is updated');
button.icon = './new-icon.png';
assert.equal(button.icon, './new-icon.png',
'icon is updated');
assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
'node image is updated');
button.disabled = true;
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
// TODO: test validation on update
loader.unload();
}
exports['test button global state updated on multiple windows'] = function(assert, done) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let button = Button({
id: 'my-button-5',
label: 'my button',
icon: './icon.png'
});
let nodes = [getWidget(button.id).node];
open(null, { features: { toolbar: true }}).then(window => {
nodes.push(getWidget(button.id, window).node);
button.label = 'New label';
button.icon = './new-icon.png';
button.disabled = true;
for (let node of nodes) {
assert.equal(node.getAttribute('label'), 'New label',
'node label is updated');
assert.equal(node.getAttribute('tooltiptext'), 'New label',
'node tooltip is updated');
assert.equal(button.icon, './new-icon.png',
'icon is updated');
assert.equal(node.getAttribute('image'), data.url('new-icon.png'),
'node image is updated');
assert.equal(button.disabled, true,
'disabled is updated');
assert.equal(node.getAttribute('disabled'), 'true',
'node disabled is updated');
};
return window;
}).
then(close).
then(loader.unload).
then(done, assert.fail);
};
exports['test button window state'] = function(assert, done) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
let button = Button({
id: 'my-button-6',
label: 'my button',
icon: './icon.png'
});
let mainWindow = browserWindows.activeWindow;
let nodes = [getWidget(button.id).node];
open(null, { features: { toolbar: true }}).then(focus).then(window => {
nodes.push(getWidget(button.id, window).node);
let { activeWindow } = browserWindows;
button.state(activeWindow, {
label: 'New label',
icon: './new-icon.png',
disabled: true
});
// check the states
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
let state = button.state(mainWindow);
assert.equal(state.label, 'my button',
'previous window label unchanged');
assert.equal(state.icon, './icon.png',
'previous window icon unchanged');
assert.equal(state.disabled, false,
'previous window disabled unchanged');
let state = button.state(activeWindow);
assert.equal(state.label, 'New label',
'active window label updated');
assert.equal(state.icon, './new-icon.png',
'active window icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
// change the global state, only the windows without a state are affected
button.label = 'A good label';
assert.equal(button.label, 'A good label',
'global label updated');
assert.equal(button.state(mainWindow).label, 'A good label',
'previous window label updated');
assert.equal(button.state(activeWindow).label, 'New label',
'active window label unchanged');
// delete the window state will inherits the global state again
button.state(activeWindow, null);
assert.equal(button.state(activeWindow).label, 'A good label',
'active window label inherited');
// check the nodes properties
let node = nodes[0];
let state = button.state(mainWindow);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
let node = nodes[1];
let state = button.state(activeWindow);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
return window;
}).
then(close).
then(loader.unload).
then(done, assert.fail);
};
exports['test button tab state'] = function(assert, done) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
let tabs = loader.require('sdk/tabs');
let button = Button({
id: 'my-button-7',
label: 'my button',
icon: './icon.png'
});
let mainTab = tabs.activeTab;
let node = getWidget(button.id).node;
tabs.open({
url: 'about:blank',
onActivate: function onActivate(tab) {
tab.removeListener('activate', onActivate);
let { activeWindow } = browserWindows;
// set window state
button.state(activeWindow, {
label: 'Window label',
icon: './window-icon.png'
});
// set previous active tab state
button.state(mainTab, {
label: 'Tab label',
icon: './tab-icon.png',
});
// set current active tab state
button.state(tab, {
icon: './another-tab-icon.png',
disabled: true
});
// check the states
Cu.schedulePreciseGC(() => {
assert.equal(button.label, 'my button',
'global label unchanged');
assert.equal(button.icon, './icon.png',
'global icon unchanged');
assert.equal(button.disabled, false,
'global disabled unchanged');
let state = button.state(mainTab);
assert.equal(state.label, 'Tab label',
'previous tab label updated');
assert.equal(state.icon, './tab-icon.png',
'previous tab icon updated');
assert.equal(state.disabled, false,
'previous tab disabled unchanged');
let state = button.state(tab);
assert.equal(state.label, 'Window label',
'active tab inherited from window state');
assert.equal(state.icon, './another-tab-icon.png',
'active tab icon updated');
assert.equal(state.disabled, true,
'active disabled updated');
// change the global state
button.icon = './good-icon.png';
// delete the tab state
button.state(tab, null);
assert.equal(button.icon, './good-icon.png',
'global icon updated');
assert.equal(button.state(mainTab).icon, './tab-icon.png',
'previous tab icon unchanged');
assert.equal(button.state(tab).icon, './window-icon.png',
'tab icon inherited from window');
// delete the window state
button.state(activeWindow, null);
assert.equal(button.state(tab).icon, './good-icon.png',
'tab icon inherited from global');
// check the node properties
let state = button.state(tabs.activeTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
tabs.once('activate', () => {
// This is made in order to avoid to check the node before it
// is updated, need a better check
setTimeout(() => {
let state = button.state(mainTab);
assert.equal(node.getAttribute('label'), state.label,
'node label is correct');
assert.equal(node.getAttribute('tooltiptext'), state.label,
'node tooltip is correct');
assert.equal(node.getAttribute('image'), data.url(state.icon.substr(2)),
'node image is correct');
assert.equal(node.hasAttribute('disabled'), state.disabled,
'disabled is correct');
tab.close(() => {
loader.unload();
done();
});
}, 500);
});
mainTab.activate();
});
}
});
};
exports['test button click'] = function(assert, done) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
let labels = [];
let button = Button({
id: 'my-button-8',
label: 'my button',
icon: './icon.png',
onClick: ({label}) => labels.push(label)
});
let mainWindow = browserWindows.activeWindow;
let chromeWindow = getMostRecentBrowserWindow();
open(null, { features: { toolbar: true }}).then(focus).then(window => {
button.state(mainWindow, { label: 'nothing' });
button.state(mainWindow.tabs.activeTab, { label: 'foo'})
button.state(browserWindows.activeWindow, { label: 'bar' });
button.click();
focus(chromeWindow).then(() => {
button.click();
assert.deepEqual(labels, ['bar', 'foo'],
'button click works');
close(window).
then(loader.unload).
then(done, assert.fail);
});
}).then(null, assert.fail);
}
exports['test button type checkbox'] = function(assert, done) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
let events = [];
let button = Button({
id: 'my-button-9',
label: 'my button',
icon: './icon.png',
type: 'checkbox',
onClick: ({label}) => events.push('clicked:' + label),
onChange: state => events.push('changed:' + state.label + ':' + state.checked)
});
let { node } = getWidget(button.id);
assert.equal(button.type, 'checkbox',
'button type is set');
assert.equal(node.getAttribute('type'), 'checkbox',
'node type is set');
let mainWindow = browserWindows.activeWindow;
let chromeWindow = getMostRecentBrowserWindow();
open(null, { features: { toolbar: true }}).then(focus).then(window => {
button.state(mainWindow, { label: 'nothing' });
button.state(mainWindow.tabs.activeTab, { label: 'foo'})
button.state(browserWindows.activeWindow, { label: 'bar' });
button.click();
button.click();
focus(chromeWindow).then(() => {
button.click();
button.click();
assert.deepEqual(events, [
'clicked:bar', 'changed:bar:true', 'clicked:bar', 'changed:bar:false',
'clicked:foo', 'changed:foo:true', 'clicked:foo', 'changed:foo:false'
],
'button change events works');
close(window).
then(loader.unload).
then(done, assert.fail);
})
}).then(null, assert.fail);
}
exports['test button icon set'] = function(assert) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
// Test remote icon set
assert.throws(
() => Button({
id: 'my-button-10',
label: 'my button',
icon: {
'16': 'http://www.mozilla.org/favicon.ico'
}
}),
/^The option "icon"/,
'throws on no valid icon given');
let button = Button({
id: 'my-button-11',
label: 'my button',
icon: {
'5': './icon5.png',
'16': './icon16.png',
'32': './icon32.png',
'64': './icon64.png'
}
});
let { node, id: widgetId } = getWidget(button.id);
let { devicePixelRatio } = node.ownerDocument.defaultView;
let size = 16 * devicePixelRatio;
assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
'the icon is set properly in navbar');
let size = 32 * devicePixelRatio;
CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
assert.equal(node.getAttribute('image'), data.url(button.icon[size].substr(2)),
'the icon is set properly in panel');
// Using `loader.unload` without move back the button to the original area
// raises an error in the CustomizableUI. This is doesn't happen if the
// button is moved manually from navbar to panel. I believe it has to do
// with `addWidgetToArea` method, because even with a `timeout` the issue
// persist.
CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR);
loader.unload();
}
exports['test button icon se with only one option'] = function(assert) {
const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
// Test remote icon set
assert.throws(
() => Button({
id: 'my-button-10',
label: 'my button',
icon: {
'16': 'http://www.mozilla.org/favicon.ico'
}
}),
/^The option "icon"/,
'throws on no valid icon given');
let button = Button({
id: 'my-button-11',
label: 'my button',
icon: {
'5': './icon5.png'
}
});
let { node, id: widgetId } = getWidget(button.id);
assert.equal(node.getAttribute('image'), data.url(button.icon['5'].substr(2)),
'the icon is set properly in navbar');
CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_PANEL);
assert.equal(node.getAttribute('image'), data.url(button.icon['5'].substr(2)),
'the icon is set properly in panel');
// Using `loader.unload` without move back the button to the original area
// raises an error in the CustomizableUI. This is doesn't happen if the
// button is moved manually from navbar to panel. I believe it has to do
// with `addWidgetToArea` method, because even with a `timeout` the issue
// persist.
CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR);
loader.unload();
}
exports['test button state validation'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
let button = Button({
id: 'my-button-12',
label: 'my button',
icon: './icon.png'
})
button.state(button, {
size: 'large'
});
assert.equal(button.size, 'small',
'button.size is unchanged');
let state = button.state(button);
assert.equal(button.size, 'small',
'button state is unchanged');
assert.throws(
() => button.state(button, { icon: 'http://www.mozilla.org/favicon.ico' }),
/^The option "icon"/,
'throws on remote icon given');
loader.unload();
};
exports['test button are not in private windows'] = function(assert, done) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let{ isPrivate } = loader.require('sdk/private-browsing');
let { browserWindows } = loader.require('sdk/windows');
let button = Button({
id: 'my-button-13',
label: 'my button',
icon: './icon.png'
});
open(null, { features: { toolbar: true, private: true }}).then(window => {
assert.ok(isPrivate(window),
'the new window is private');
let { node } = getWidget(button.id, window);
assert.ok(!node || node.style.display === 'none',
'the button is not added / is not visible on private window');
return window;
}).
then(close).
then(loader.unload).
then(done, assert.fail)
}
exports['test button state are snapshot'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
let tabs = loader.require('sdk/tabs');
let button = Button({
id: 'my-button-14',
label: 'my button',
icon: './icon.png'
});
let state = button.state(button);
let windowState = button.state(browserWindows.activeWindow);
let tabState = button.state(tabs.activeTab);
assert.deepEqual(windowState, state,
'window state has the same properties of button state');
assert.deepEqual(tabState, state,
'tab state has the same properties of button state');
assert.notEqual(windowState, state,
'window state is not the same object of button state');
assert.notEqual(tabState, state,
'tab state is not the same object of button state');
assert.deepEqual(button.state(button), state,
'button state has the same content of previous button state');
assert.deepEqual(button.state(browserWindows.activeWindow), windowState,
'window state has the same content of previous window state');
assert.deepEqual(button.state(tabs.activeTab), tabState,
'tab state has the same content of previous tab state');
assert.notEqual(button.state(button), state,
'button state is not the same object of previous button state');
assert.notEqual(button.state(browserWindows.activeWindow), windowState,
'window state is not the same object of previous window state');
assert.notEqual(button.state(tabs.activeTab), tabState,
'tab state is not the same object of previous tab state');
loader.unload();
}
exports['test button after destroy'] = function(assert) {
let loader = Loader(module);
let { Button } = loader.require('sdk/ui');
let { browserWindows } = loader.require('sdk/windows');
let { activeTab } = loader.require('sdk/tabs');
let button = Button({
id: 'my-button-15',
label: 'my button',
icon: './icon.png',
onClick: () => assert.fail('onClick should not be called')
});
button.destroy();
assert.throws(
() => button.click(),
/^The state cannot be set or get/,
'button.click() not executed');
assert.throws(
() => button.label,
/^The state cannot be set or get/,
'button.label cannot be get after destroy');
assert.throws(
() => button.label = 'my label',
/^The state cannot be set or get/,
'button.label cannot be set after destroy');
assert.throws(
() => {
button.state(browserWindows.activeWindow, {
label: 'window label'
});
},
/^The state cannot be set or get/,
'window state label cannot be set after destroy');
assert.throws(
() => button.state(browserWindows.activeWindow).label,
/^The state cannot be set or get/,
'window state label cannot be get after destroy');
assert.throws(
() => {
button.state(activeTab, {
label: 'tab label'
});
},
/^The state cannot be set or get/,
'tab state label cannot be set after destroy');
assert.throws(
() => button.state(activeTab).label,
/^The state cannot be set or get/,
'window state label cannot se get after destroy');
loader.unload();
};
// 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');
}
catch (err) {
if (!/^Unsupported Application/.test(err.message))
throw err;
module.exports = {
'test Unsupported Application': assert => assert.pass(err.message)
}
}
require('sdk/test').run(exports);

View File

@ -0,0 +1,208 @@
/* 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': '> 24'
}
};
const { Loader } = require('sdk/test/loader');
const { show, hide } = require('sdk/ui/sidebar/actions');
const { isShowing } = require('sdk/ui/sidebar/utils');
const { getMostRecentBrowserWindow, isWindowPrivate } = require('sdk/window/utils');
const { open, close, focus, promise: windowPromise } = require('sdk/window/helpers');
const { setTimeout } = require('sdk/timers');
const { isPrivate } = require('sdk/private-browsing');
const { data } = require('sdk/self');
const { URL } = require('sdk/url');
const { BLANK_IMG, BUILTIN_SIDEBAR_MENUITEMS, isSidebarShowing,
getSidebarMenuitems, getExtraSidebarMenuitems, makeID, simulateCommand,
simulateClick, getWidget, isChecked } = require('./sidebar/utils');
exports.testSideBarIsNotInNewPrivateWindows = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testSideBarIsNotInNewPrivateWindows';
let sidebar = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+testName
});
let startWindow = getMostRecentBrowserWindow();
let ele = startWindow.document.getElementById(makeID(testName));
assert.ok(ele, 'sidebar element was added');
open(null, { features: { private: true } }).then(function(window) {
let ele = window.document.getElementById(makeID(testName));
assert.ok(isPrivate(window), 'the new window is private');
assert.equal(ele, null, 'sidebar element was not added');
sidebar.destroy();
assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
assert.ok(!startWindow.document.getElementById(makeID(testName)), 'sidebar id DNE');
close(window).then(done, assert.fail);
})
}
/*
exports.testSidebarIsNotOpenInNewPrivateWindow = function(assert, done) {
let testName = 'testSidebarIsNotOpenInNewPrivateWindow';
let window = getMostRecentBrowserWindow();
let sidebar = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+testName
});
sidebar.on('show', function() {
assert.equal(isPrivate(window), false, 'the new window is not private');
assert.equal(isSidebarShowing(window), true, 'the sidebar is showing');
assert.equal(isShowing(sidebar), true, 'the sidebar is showing');
let window2 = window.OpenBrowserWindow({private: true});
windowPromise(window2, 'load').then(focus).then(function() {
// TODO: find better alt to setTimeout...
setTimeout(function() {
assert.equal(isPrivate(window2), true, 'the new window is private');
assert.equal(isSidebarShowing(window), true, 'the sidebar is showing in old window still');
assert.equal(isSidebarShowing(window2), false, 'the sidebar is not showing in the new private window');
assert.equal(isShowing(sidebar), false, 'the sidebar is not showing');
sidebar.destroy();
close(window2).then(done);
}, 500)
})
});
sidebar.show();
}
*/
// TEST: edge case where web panel is destroyed while loading
exports.testDestroyEdgeCaseBugWithPrivateWindow = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testDestroyEdgeCaseBug';
let window = getMostRecentBrowserWindow();
let sidebar = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+testName
});
// NOTE: purposely not listening to show event b/c the event happens
// between now and then.
sidebar.show();
assert.equal(isPrivate(window), false, 'the new window is not private');
assert.equal(isSidebarShowing(window), true, 'the sidebar is showing');
//assert.equal(isShowing(sidebar), true, 'the sidebar is showing');
open(null, { features: { private: true } }).then(focus).then(function(window2) {
assert.equal(isPrivate(window2), true, 'the new window is private');
assert.equal(isSidebarShowing(window2), false, 'the sidebar is not showing');
assert.equal(isShowing(sidebar), false, 'the sidebar is not showing');
sidebar.destroy();
assert.pass('destroying the sidebar');
close(window2).then(function() {
let loader = Loader(module);
assert.equal(isPrivate(window), false, 'the current window is not private');
let sidebar = loader.require('sdk/ui/sidebar').Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: 'data:text/html;charset=utf-8,'+ testName,
onShow: function() {
assert.pass('onShow works for Sidebar');
loader.unload();
let sidebarMI = getSidebarMenuitems();
for each (let mi in sidebarMI) {
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
}
assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
assert.equal(isSidebarShowing(window), false, 'the sidebar is not showing');
done();
}
})
sidebar.show();
assert.pass('showing the sidebar');
});
});
}
exports.testShowInPrivateWindow = function(assert, done) {
const { Sidebar } = require('sdk/ui/sidebar');
let testName = 'testShowInPrivateWindow';
let window = getMostRecentBrowserWindow();
let { document } = window;
let url = 'data:text/html;charset=utf-8,'+testName;
let sidebar1 = Sidebar({
id: testName,
title: testName,
icon: BLANK_IMG,
url: url
});
assert.equal(sidebar1.url, url, 'url getter works');
assert.equal(isShowing(sidebar1), false, 'the sidebar is not showing');
assert.ok(!isChecked(document.getElementById(makeID(sidebar1.id))),
'the menuitem is not checked');
assert.equal(isSidebarShowing(window), false, 'the new window sidebar is not showing');
windowPromise(window.OpenBrowserWindow({ private: true }), 'load').then(function(window) {
let { document } = window;
assert.equal(isWindowPrivate(window), true, 'new window is private');
assert.equal(isPrivate(window), true, 'new window is private');
sidebar1.show().then(
function bad() {
assert.fail('a successful show should not happen here..');
},
function good() {
assert.equal(isShowing(sidebar1), false, 'the sidebar is still not showing');
assert.equal(document.getElementById(makeID(sidebar1.id)),
null,
'the menuitem dne on the private window');
assert.equal(isSidebarShowing(window), false, 'the new window sidebar is not showing');
sidebar1.destroy();
close(window).then(done);
});
}, assert.fail);
}
// 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/sidebar');
}
catch (err) {
if (!/^Unsupported Application/.test(err.message))
throw err;
module.exports = {
'test Unsupported Application': assert => assert.pass(err.message)
}
}
require('sdk/test').run(exports);

File diff suppressed because it is too large Load Diff

View File

@ -3,17 +3,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Opening new windows in Fennec causes issues
module.metadata = {
engines: {
'Firefox': '*'
}
};
const { Loader } = require("sdk/test/loader");
const { open, close } = require("sdk/window/helpers");
const { browserWindows: windows } = require("sdk/windows");
const { isBrowser } = require('sdk/window/utils');
const app = require("sdk/system/xul-app");
exports["test unload window observer"] = function(assert, done) {
// Hacky way to be able to create unloadable modules via makeSandboxedLoader.
@ -23,17 +17,14 @@ exports["test unload window observer"] = function(assert, done) {
let closed = 0;
let windowsOpen = windows.length;
observer.on("open", function onOpen(window) {
// Ignoring non-browser windows
if (isBrowser(window))
opened++;
});
observer.on("close", function onClose(window) {
// Ignore non-browser windows & already opened `activeWindow` (unload will
// emit close on it even though it is not actually closed).
if (isBrowser(window))
closed++;
});
observer.on("open", onOpen);
observer.on("close", onClose);
// On Fennec, only test that the module does not throw an error
if (app.is("Fennec")) {
assert.pass("Windows observer did not throw on Fennec");
return cleanUp();
}
// Open window and close it to trigger observers.
open().
@ -46,7 +37,25 @@ exports["test unload window observer"] = function(assert, done) {
assert.equal(1, opened, "observer open was called before unload only");
assert.equal(windowsOpen + 1, closed, "observer close was called before unload only");
}).
then(done, assert.fail);
then(cleanUp, assert.fail);
function cleanUp () {
observer.removeListener("open", onOpen);
observer.removeListener("close", onClose);
done();
}
function onOpen(window) {
// Ignoring non-browser windows
if (isBrowser(window))
opened++;
}
function onClose(window) {
// Ignore non-browser windows & already opened `activeWindow` (unload will
// emit close on it even though it is not actually closed).
if (isBrowser(window))
closed++;
}
};
require("test").run(exports);