This commit is contained in:
Phil Ringnalda 2013-08-18 17:51:12 -07:00
Родитель 9dab8f05ab f452200e54
Коммит f26020beac
180 изменённых файлов: 8878 добавлений и 2101 удалений

2
addon-sdk/source/app-extension/bootstrap.js поставляемый
Просмотреть файл

@ -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: {

Просмотреть файл

@ -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

Просмотреть файл

@ -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 ###

Просмотреть файл

@ -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() {

Просмотреть файл

@ -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>

Просмотреть файл

@ -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

Просмотреть файл

@ -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 });

Просмотреть файл

@ -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;

Просмотреть файл

@ -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);
});

Просмотреть файл

@ -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;

Просмотреть файл

@ -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));

Просмотреть файл

@ -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');

Просмотреть файл

@ -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.'
}
});

Просмотреть файл

@ -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);

Просмотреть файл

@ -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');

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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);
}
});

Просмотреть файл

@ -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;

Просмотреть файл

@ -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
xpi_path = XPI_FILENAME % target_cfg.name
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,

Просмотреть файл

@ -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"

Просмотреть файл

@ -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"])

Просмотреть файл

@ -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):

Просмотреть файл

@ -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/. */

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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/. */

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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)

Просмотреть файл

@ -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):

Просмотреть файл

@ -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:

Просмотреть файл

@ -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);

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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);

Просмотреть файл

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

Просмотреть файл

@ -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);

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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') : {}
);

Просмотреть файл

@ -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;

Просмотреть файл

@ -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)
}
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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"
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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);

Просмотреть файл

@ -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,
"Each tab with an attached onClose listener received a close " +
"event when the window was closed");
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);

Просмотреть файл

@ -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");

Просмотреть файл

@ -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.location = '" + url2 + "';" +
" }, 0);" +
"}" +
"window.addEventListener('message', function() {"+
" window.location = '" + url2 + "';" +
'}, 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();
}

Просмотреть файл

@ -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'

Просмотреть файл

@ -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');
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -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);

Просмотреть файл

@ -1,4 +1,4 @@
{
"revision": "6127157a7933badbe075f894743a09c1152afe58",
"revision": "a7d496de30d0a7612b31de99b01b5a4e2b749728",
"repo_path": "/integration/gaia-central"
}

Просмотреть файл

@ -5,6 +5,14 @@
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
#main-window:not([chromehidden~="toolbar"]) {
%ifdef XP_MACOSX
min-width: 425px;
%else
min-width: 390px;
%endif
}
searchbar {
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
}

Просмотреть файл

@ -8,9 +8,15 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
"resource://gre/modules/FormHistory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
"resource:///modules/DownloadsCommon.jsm");
function Sanitizer() {}
Sanitizer.prototype = {
// warning to the caller: this one may raise an exception (e.g. bug #265028)
@ -303,47 +309,47 @@ Sanitizer.prototype = {
downloads: {
clear: function ()
{
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
if (DownloadsCommon.useJSTransfer) {
Task.spawn(function () {
let filterByTime = this.range ?
(download => download.startTime >= this.range[0] &&
download.startTime <= this.range[1]) : null;
var dlsToRemove = [];
if (this.range) {
// First, remove the completed/cancelled downloads
dlMgr.removeDownloadsByTimeframe(this.range[0], this.range[1]);
// Clear all completed/cancelled downloads
let publicList = yield Downloads.getPublicDownloadList();
publicList.removeFinished(filterByTime);
// Queue up any active downloads that started in the time span as well
for (let dlsEnum of [dlMgr.activeDownloads, dlMgr.activePrivateDownloads]) {
while (dlsEnum.hasMoreElements()) {
var dl = dlsEnum.next();
if (dl.startTime >= this.range[0])
dlsToRemove.push(dl);
}
}
let privateList = yield Downloads.getPrivateDownloadList();
privateList.removeFinished(filterByTime);
}.bind(this)).then(null, Cu.reportError);
}
else {
// Clear all completed/cancelled downloads
dlMgr.cleanUp();
dlMgr.cleanUpPrivate();
// Queue up all active ones as well
for (let dlsEnum of [dlMgr.activeDownloads, dlMgr.activePrivateDownloads]) {
while (dlsEnum.hasMoreElements()) {
dlsToRemove.push(dlsEnum.next());
}
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
if (this.range) {
// First, remove the completed/cancelled downloads
dlMgr.removeDownloadsByTimeframe(this.range[0], this.range[1]);
}
else {
// Clear all completed/cancelled downloads
dlMgr.cleanUp();
dlMgr.cleanUpPrivate();
}
}
// Remove any queued up active downloads
dlsToRemove.forEach(function (dl) {
dl.remove();
});
},
get canClear()
canClear : function(aCallback, aArg)
{
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
return dlMgr.canCleanUp || dlMgr.canCleanUpPrivate;
if (DownloadsCommon.useJSTransfer) {
aCallback("downloads", true, aArg);
}
else {
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
aCallback("downloads", dlMgr.canCleanUp || dlMgr.canCleanUpPrivate, aArg);
}
return false;
}
},

Просмотреть файл

@ -45,13 +45,7 @@ const EXPECTED_REFLOWS = [
"ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
"@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|" +
"@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_forEachBrowserWindow@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_getCurrentState@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_saveState@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_onTimerCallback@resource:///modules/sessionstore/SessionStore.jsm|" +
"ssi_observe@resource:///modules/sessionstore/SessionStore.jsm|",
"ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|",
// tabPreviews.capture()
"tabPreviews_capture@chrome://browser/content/browser.js|" +

Просмотреть файл

@ -53,6 +53,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
"resource://gre/modules/DownloadUIHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
"resource://gre/modules/DownloadUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
@ -480,65 +482,44 @@ this.DownloadsCommon = {
if (!(aOwnerWindow instanceof Ci.nsIDOMWindow))
throw new Error("aOwnerWindow must be a dom-window object");
// Confirm opening executable files if required.
let promiseShouldLaunch;
if (aFile.isExecutable()) {
let showAlert = true;
try {
showAlert = Services.prefs.getBoolPref(kPrefBdmAlertOnExeOpen);
} catch (ex) { }
// On Vista and above, we rely on native security prompting for
// downloaded content unless it's disabled.
if (DownloadsCommon.isWinVistaOrHigher) {
try {
if (Services.prefs.getBoolPref(kPrefBdmScanWhenDone)) {
showAlert = false;
}
} catch (ex) { }
}
if (showAlert) {
let name = aFile.leafName;
let message =
DownloadsCommon.strings.fileExecutableSecurityWarning(name, name);
let title =
DownloadsCommon.strings.fileExecutableSecurityWarningTitle;
let dontAsk =
DownloadsCommon.strings.fileExecutableSecurityWarningDontAsk;
let checkbox = { value: false };
let open = Services.prompt.confirmCheck(aOwnerWindow, title, message,
dontAsk, checkbox);
if (!open) {
return;
}
Services.prefs.setBoolPref(kPrefBdmAlertOnExeOpen,
!checkbox.value);
}
// We get a prompter for the provided window here, even though anchoring
// to the most recently active window should work as well.
promiseShouldLaunch =
DownloadUIHelper.getPrompter(aOwnerWindow)
.confirmLaunchExecutable(aFile.path);
} else {
promiseShouldLaunch = Promise.resolve(true);
}
// Actually open the file.
try {
if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
aMimeInfo.launchWithFile(aFile);
promiseShouldLaunch.then(shouldLaunch => {
if (!shouldLaunch) {
return;
}
}
catch(ex) { }
// If either we don't have the mime info, or the preferred action failed,
// attempt to launch the file directly.
try {
aFile.launch();
}
catch(ex) {
// If launch fails, try sending it through the system's external "file:"
// URL handler.
Cc["@mozilla.org/uriloader/external-protocol-service;1"]
.getService(Ci.nsIExternalProtocolService)
.loadUrl(NetUtil.newURI(aFile));
}
// Actually open the file.
try {
if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
aMimeInfo.launchWithFile(aFile);
return;
}
}
catch(ex) { }
// If either we don't have the mime info, or the preferred action failed,
// attempt to launch the file directly.
try {
aFile.launch();
}
catch(ex) {
// If launch fails, try sending it through the system's external "file:"
// URL handler.
Cc["@mozilla.org/uriloader/external-protocol-service;1"]
.getService(Ci.nsIExternalProtocolService)
.loadUrl(NetUtil.newURI(aFile));
}
}).then(null, Cu.reportError);
},
/**
@ -718,7 +699,7 @@ DownloadsDataCtor.prototype = {
return;
}
this._downloadToDataItemMap.remove(aDownload);
this._downloadToDataItemMap.delete(aDownload);
this.dataItems[dataItem.downloadGuid] = null;
for (let view of this._views) {
view.onDataItemRemoved(dataItem);
@ -1344,7 +1325,7 @@ DownloadsDataItem.prototype = {
this.currBytes = this._download.currentBytes;
this.maxBytes = this._download.totalBytes;
this.resumable = this._download.hasPartialData;
this.speed = 0;
this.speed = this._download.speed;
this.percentComplete = this._download.progress;
},

Просмотреть файл

@ -741,12 +741,11 @@ let SessionStoreInternal = {
Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
} else {
TelemetryTimestamps.add("sessionRestoreRestoring");
// make sure that the restored tabs are first in the window
aInitialState._firstTabs = true;
this._restoreCount = aInitialState.windows ? aInitialState.windows.length : 0;
let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
this.restoreWindow(aWindow, aInitialState, overwrite);
let options = {firstWindow: true, overwriteTabs: overwrite};
this.restoreWindow(aWindow, aInitialState, options);
// _loadState changed from "stopped" to "running". Save the session's
// load state immediately so that crashes happening during startup
@ -764,8 +763,9 @@ let SessionStoreInternal = {
}
// this window was opened by _openWindowWithState
else if (!this._isWindowLoaded(aWindow)) {
let followUp = this._statesToRestore[aWindow.__SS_restoreID].windows.length == 1;
this.restoreWindow(aWindow, this._statesToRestore[aWindow.__SS_restoreID], true, followUp);
let state = this._statesToRestore[aWindow.__SS_restoreID];
let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
this.restoreWindow(aWindow, state, options);
}
// The user opened another, non-private window after starting up with
// a single private one. Let's restore the session we actually wanted to
@ -773,10 +773,9 @@ let SessionStoreInternal = {
else if (this._deferredInitialState && !isPrivateWindow &&
aWindow.toolbar.visible) {
this._deferredInitialState._firstTabs = true;
this._restoreCount = this._deferredInitialState.windows ?
this._deferredInitialState.windows.length : 0;
this.restoreWindow(aWindow, this._deferredInitialState, false);
this.restoreWindow(aWindow, this._deferredInitialState, {firstWindow: true});
this._deferredInitialState = null;
}
else if (this._restoreLastWindow && aWindow.toolbar.visible &&
@ -837,7 +836,8 @@ let SessionStoreInternal = {
// Ensure that the window state isn't hidden
this._restoreCount = 1;
let state = { windows: [newWindowState] };
this.restoreWindow(aWindow, state, this._isCmdLineEmpty(aWindow, state));
let options = {overwriteTabs: this._isCmdLineEmpty(aWindow, state)};
this.restoreWindow(aWindow, state, options);
}
}
// we actually restored the session just now.
@ -1399,7 +1399,7 @@ let SessionStoreInternal = {
this._restoreCount = state.windows ? state.windows.length : 0;
// restore to the given state
this.restoreWindow(window, state, true);
this.restoreWindow(window, state, {overwriteTabs: true});
},
getWindowState: function ssi_getWindowState(aWindow) {
@ -1419,7 +1419,7 @@ let SessionStoreInternal = {
if (!aWindow.__SSi)
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
this.restoreWindow(aWindow, aState, aOverwrite);
this.restoreWindow(aWindow, aState, {overwriteTabs: aOverwrite});
},
getTabState: function ssi_getTabState(aTab) {
@ -1780,7 +1780,8 @@ let SessionStoreInternal = {
// in _preWindowToRestoreInto will prevent most (all?) Panorama
// weirdness but we will still merge other extData.
// Bug 588217 should make this go away by merging the group data.
this.restoreWindow(windowToUse, { windows: [winState] }, canOverwriteTabs, true);
let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true};
this.restoreWindow(windowToUse, { windows: [winState] }, options);
}
else {
this._openWindowWithState({ windows: [winState] });
@ -2613,13 +2614,19 @@ let SessionStoreInternal = {
* Window reference
* @param aState
* JS object or its eval'able source
* @param aOverwriteTabs
* bool overwrite existing tabs w/ new ones
* @param aFollowUp
* bool this isn't the restoration of the first window
* @param aOptions
* {overwriteTabs: true} to overwrite existing tabs w/ new ones
* {isFollowUp: true} if this is not the restoration of the 1st window
* {firstWindow: true} if this is the first non-private window we're
* restoring in this session, that might open an
* external link as well
*/
restoreWindow: function ssi_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) {
if (!aFollowUp) {
restoreWindow: function ssi_restoreWindow(aWindow, aState, aOptions = {}) {
let overwriteTabs = aOptions && aOptions.overwriteTabs;
let isFollowUp = aOptions && aOptions.isFollowUp;
let firstWindow = aOptions && aOptions.firstWindow;
if (isFollowUp) {
this.windowToFocus = aWindow;
}
// initialize window if necessary
@ -2670,13 +2677,13 @@ let SessionStoreInternal = {
}
// don't restore a single blank tab when we've had an external
// URL passed in for loading at startup (cf. bug 357419)
else if (root._firstTabs && !aOverwriteTabs && winData.tabs.length == 1 &&
else if (firstWindow && !overwriteTabs && winData.tabs.length == 1 &&
(!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
winData.tabs = [];
}
var tabbrowser = aWindow.gBrowser;
var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
var openTabCount = overwriteTabs ? tabbrowser.browsers.length : -1;
var newTabCount = winData.tabs.length;
var tabs = [];
@ -2686,14 +2693,14 @@ let SessionStoreInternal = {
tabstrip.smoothScroll = false;
// unpin all tabs to ensure they are not reordered in the next loop
if (aOverwriteTabs) {
if (overwriteTabs) {
for (let t = tabbrowser._numPinnedTabs - 1; t > -1; t--)
tabbrowser.unpinTab(tabbrowser.tabs[t]);
}
// make sure that the selected tab won't be closed in order to
// prevent unnecessary flickering
if (aOverwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
if (overwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
let numVisibleTabs = 0;
@ -2703,7 +2710,7 @@ let SessionStoreInternal = {
tabbrowser.tabs[t] :
tabbrowser.addTab("about:blank", {skipAnimation: true}));
// when resuming at startup: add additionally requested pages to the end
if (!aOverwriteTabs && root._firstTabs) {
if (!overwriteTabs && firstWindow) {
tabbrowser.moveTabTo(tabs[t], t);
}
@ -2730,7 +2737,7 @@ let SessionStoreInternal = {
// tabs will be rebuilt and marked if they need to be restored after loading
// state (in restoreHistoryPrecursor).
// We also want to invalidate any cached information on the tab state.
if (aOverwriteTabs) {
if (overwriteTabs) {
for (let i = 0; i < tabbrowser.tabs.length; i++) {
let tab = tabbrowser.tabs[i];
TabStateCache.delete(tab);
@ -2746,7 +2753,7 @@ let SessionStoreInternal = {
// count in case there are still tabs restoring.
if (!aWindow.__SS_tabsToRestore)
aWindow.__SS_tabsToRestore = 0;
if (aOverwriteTabs)
if (overwriteTabs)
aWindow.__SS_tabsToRestore = newTabCount;
else
aWindow.__SS_tabsToRestore += newTabCount;
@ -2759,12 +2766,12 @@ let SessionStoreInternal = {
aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
// when overwriting tabs, remove all superflous ones
if (aOverwriteTabs && newTabCount < openTabCount) {
if (overwriteTabs && newTabCount < openTabCount) {
Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
.forEach(tabbrowser.removeTab, tabbrowser);
}
if (aOverwriteTabs) {
if (overwriteTabs) {
this.restoreWindowFeatures(aWindow, winData);
delete this._windows[aWindow.__SSi].extData;
}
@ -2779,12 +2786,12 @@ let SessionStoreInternal = {
this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
}
}
if (aOverwriteTabs || root._firstTabs) {
if (overwriteTabs || firstWindow) {
this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || [];
}
this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs,
(aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0);
(overwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0);
if (aState.scratchpads) {
ScratchpadManager.restoreSession(aState.scratchpads);

Просмотреть файл

@ -3,7 +3,7 @@
function test() {
waitForExplicitFinish();
newWindowWithTabView(onTabViewShown);
newWindowWithTabView(onTabViewShown, null, 850);
}
function onTabViewShown(win) {

Просмотреть файл

@ -45,12 +45,8 @@ InspectorPanel.prototype = {
*/
open: function InspectorPanel_open() {
return this.target.makeRemote().then(() => {
return this.target.inspector.getWalker();
}).then(walker => {
if (this._destroyPromise) {
walker.release().then(null, console.error);
}
this.walker = walker;
return this._getWalker();
}).then(() => {
return this._getDefaultNodeForSelection();
}).then(defaultSelection => {
return this._deferredOpen(defaultSelection);
@ -149,6 +145,16 @@ InspectorPanel.prototype = {
return deferred.promise;
},
_getWalker: function() {
let inspector = this.target.inspector;
return inspector.getWalker().then(walker => {
this.walker = walker;
return inspector.getPageStyle();
}).then(pageStyle => {
this.pageStyle = pageStyle;
});
},
/**
* Return a promise that will resolve to the default node for selection.
*/
@ -317,7 +323,7 @@ InspectorPanel.prototype = {
try {
selfUpdate(selection);
} catch(ex) {
console.error(ex);
console.error(ex)
}
}, Ci.nsIThread.DISPATCH_NORMAL);
},
@ -403,6 +409,7 @@ InspectorPanel.prototype = {
if (this.walker) {
this._destroyPromise = this.walker.release().then(null, console.error);
delete this.walker;
delete this.pageStyle;
} else {
this._destroyPromise = promise.resolve(null);
}

Просмотреть файл

@ -81,7 +81,7 @@ Selection.prototype = {
attributeChange = true;
}
if (m.type == "childList") {
if (!detached && !this.isConnected()) {
if (!detached && this.isNode() && !this.isConnected()) {
parentNode = m.target;
detached = true;
}

Просмотреть файл

@ -40,7 +40,7 @@ function test() {
testDiv.style.fontSize = "10px";
// Start up the style inspector panel...
Services.obs.addObserver(stylePanelTests, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", stylePanelTests);
inspector.selection.setNode(testDiv);
});
@ -48,15 +48,13 @@ function test() {
function stylePanelTests()
{
Services.obs.removeObserver(stylePanelTests, "StyleInspector-populated");
let computedview = inspector.sidebar.getWindowForTab("computedview").computedview;
ok(computedview, "Style Panel has a cssHtmlTree");
let propView = getInspectorProp("font-size");
is(propView.value, "10px", "Style inspector should be showing the correct font size.");
Services.obs.addObserver(stylePanelAfterChange, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", stylePanelAfterChange);
testDiv.style.fontSize = "15px";
inspector.emit("layout-change");
@ -64,8 +62,6 @@ function test() {
function stylePanelAfterChange()
{
Services.obs.removeObserver(stylePanelAfterChange, "StyleInspector-populated");
let propView = getInspectorProp("font-size");
is(propView.value, "15px", "Style inspector should be showing the new font size.");
@ -78,7 +74,7 @@ function test() {
inspector.sidebar.select("ruleview");
executeSoon(function() {
Services.obs.addObserver(stylePanelAfterSwitch, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", stylePanelAfterSwitch);
testDiv.style.fontSize = "20px";
inspector.sidebar.select("computedview");
});
@ -86,8 +82,6 @@ function test() {
function stylePanelAfterSwitch()
{
Services.obs.removeObserver(stylePanelAfterSwitch, "StyleInspector-populated");
let propView = getInspectorProp("font-size");
is(propView.value, "20px", "Style inspector should be showing the newest font size.");

Просмотреть файл

@ -68,14 +68,12 @@ function performTests()
// inspector has been told of the pseudoclass lock change.
inspector.selection.once("pseudoclass", () => {
// Give the rule view time to update.
executeSoon(() => {
inspector.once("rule-view-refreshed", () => {
testAdded();
// toggle the lock off and wait for the pseudoclass event again.
// Change the pseudo class and give the rule view time to update.
inspector.togglePseudoClass(pseudo);
inspector.selection.once("pseudoclass", () => {
// Give the rule view time to update.
executeSoon(() => {
inspector.once("rule-view-refreshed", () => {
testRemoved();
testRemovedFromUI();

Просмотреть файл

@ -25,8 +25,8 @@
accesskey="&netmonitorUI.context.copyUrl.accesskey;"
oncommand="NetMonitorView.RequestsMenu.copyUrl();"/>
<menuitem id="request-menu-context-resend"
label="&netmonitorUI.summary.resend;"
accesskey="&netmonitorUI.summary.resend.accesskey;"
label="&netmonitorUI.summary.editAndResend;"
accesskey="&netmonitorUI.summary.editAndResend.accesskey;"
oncommand="NetMonitorView.RequestsMenu.cloneSelectedRequest();"/>
</menupopup>
</popupset>
@ -314,7 +314,7 @@
crop="end"
flex="1"/>
<button id="headers-summary-resend"
label="&netmonitorUI.summary.resend;"
label="&netmonitorUI.summary.editAndResend;"
class="devtools-toolbarbutton"
onclick="NetMonitorView.RequestsMenu.cloneSelectedRequest();"/>
</hbox>

Просмотреть файл

@ -53,45 +53,39 @@ function test() {
instance.setSize(500, 500);
openInspector(onInspectorUIOpen);
openComputedView(onInspectorUIOpen);
}
function onInspectorUIOpen(aInspector) {
function onInspectorUIOpen(aInspector, aComputedView) {
inspector = aInspector;
ok(inspector, "Got inspector instance");
inspector.sidebar.select("computedview");
let div = content.document.getElementsByTagName("div")[0];
inspector.sidebar.once("computedview-ready", function() {
Services.obs.addObserver(testShrink, "StyleInspector-populated", false);
inspector.selection.setNode(div);
});
inspector.selection.setNode(div);
inspector.once("inspector-updated", testShrink);
}
function testShrink() {
Services.obs.removeObserver(testShrink, "StyleInspector-populated");
computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
ok(computedView, "We have access to the Computed View object");
is(computedWidth(), "500px", "Should show 500px initially.");
Services.obs.addObserver(function onShrink() {
Services.obs.removeObserver(onShrink, "StyleInspector-populated");
inspector.once("computed-view-refreshed", function onShrink() {
is(computedWidth(), "100px", "div should be 100px after shrinking.");
testGrow();
}, "StyleInspector-populated", false);
});
instance.setSize(100, 100);
}
function testGrow() {
Services.obs.addObserver(function onGrow() {
Services.obs.removeObserver(onGrow, "StyleInspector-populated");
inspector.once("computed-view-refreshed", function onGrow() {
is(computedWidth(), "500px", "Should be 500px after growing.");
finishUp();
}, "StyleInspector-populated", false);
});
instance.setSize(500, 500);
}

Просмотреть файл

@ -48,26 +48,20 @@ function test() {
instance.setSize(500, 500);
openInspector(onInspectorUIOpen);
openRuleView(onInspectorUIOpen);
}
function onInspectorUIOpen(aInspector) {
function onInspectorUIOpen(aInspector, aRuleView) {
inspector = aInspector;
ruleView = aRuleView;
ok(inspector, "Got inspector instance");
inspector.sidebar.select("ruleview");
let div = content.document.getElementsByTagName("div")[0];
inspector.sidebar.once("ruleview-ready", function() {
Services.obs.addObserver(testShrink, "StyleInspector-populated", false);
inspector.selection.setNode(div);
});
inspector.selection.setNode(div);
inspector.once("inspector-updated", testShrink);
}
function testShrink() {
Services.obs.removeObserver(testShrink, "StyleInspector-populated");
ruleView = inspector.sidebar.getWindowForTab("ruleview").ruleview.view;
is(numberOfRules(), 2, "Should have two rules initially.");

Просмотреть файл

@ -18,3 +18,23 @@ function openInspector(callback)
});
}
function openComputedView(callback)
{
openInspector(inspector => {
inspector.sidebar.once("computedview-ready", () => {
inspector.sidebar.select("computedview");
let ruleView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
callback(inspector, ruleView);
})
});
}
function openRuleView(callback)
{
openInspector(inspector => {
inspector.sidebar.once("ruleview-ready", () => {
inspector.sidebar.select("ruleview");
let ruleView = inspector.sidebar.getWindowForTab("ruleview").ruleview.view;
callback(inspector, ruleView);
})
});
}

Просмотреть файл

@ -8,6 +8,9 @@ const {Cc, Ci, Cu} = require("chrome");
let ToolDefinitions = require("main").Tools;
let {CssLogic} = require("devtools/styleinspector/css-logic");
let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
let promise = require("sdk/core/promise");
let {EventEmitter} = require("devtools/shared/event-emitter");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PluralForm.jsm");
@ -90,6 +93,7 @@ UpdateProcess.prototype = {
this.onDone();
return;
}
console.error(e);
throw e;
}
},
@ -115,14 +119,18 @@ UpdateProcess.prototype = {
* will generally only be one).
*
* @params {StyleInspector} aStyleInspector The owner of this CssHtmlTree
* @param {PageStyleFront} aPageStyle
* Front for the page style actor that will be providing
* the style information.
*
* @constructor
*/
function CssHtmlTree(aStyleInspector)
function CssHtmlTree(aStyleInspector, aPageStyle)
{
this.styleWindow = aStyleInspector.window;
this.styleDocument = aStyleInspector.window.document;
this.styleInspector = aStyleInspector;
this.cssLogic = aStyleInspector.cssLogic;
this.pageStyle = aPageStyle;
this.propertyViews = [];
let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
@ -144,6 +152,8 @@ function CssHtmlTree(aStyleInspector)
// No results text.
this.noResults = this.styleDocument.getElementById("noResults");
CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
// The element that we're inspecting, and the document that it comes from.
this.viewedElement = null;
this.createStyleViews();
@ -207,8 +217,6 @@ CssHtmlTree.prototype = {
// Cache the list of properties that match the selected element.
_matchedProperties: null,
htmlComplete: false,
// Used for cancelling timeouts in the style filter.
_filterChangedTimeout: null,
@ -227,6 +235,10 @@ CssHtmlTree.prototype = {
// Number of visible properties
numVisibleProperties: 0,
setPageStyle: function(pageStyle) {
this.pageStyle = pageStyle;
},
get includeBrowserStyles()
{
return this.includeBrowserStylesCheckbox.checked;
@ -236,65 +248,65 @@ CssHtmlTree.prototype = {
* Update the highlighted element. The CssHtmlTree panel will show the style
* information for the given element.
* @param {nsIDOMElement} aElement The highlighted node to get styles for.
*
* @returns a promise that will be resolved when highlighting is complete.
*/
highlight: function CssHtmlTree_highlight(aElement)
{
this.viewedElement = aElement;
this._matchedProperties = null;
highlight: function(aElement) {
if (!aElement) {
if (this._refreshProcess) {
this._refreshProcess.cancel();
}
return;
return promise.resolve(undefined)
}
if (this.htmlComplete) {
this.refreshSourceFilter();
this.refreshPanel();
} else {
if (this._refreshProcess) {
this._refreshProcess.cancel();
if (aElement === this.viewedElement) {
return promise.resolve(undefined);
}
this.viewedElement = aElement;
this.refreshSourceFilter();
return this.refreshPanel();
},
_createPropertyViews: function()
{
if (this._createViewsPromise) {
return this._createViewsPromise;
}
let deferred = promise.defer();
this._createViewsPromise = deferred.promise;
this.refreshSourceFilter();
this.numVisibleProperties = 0;
let fragment = this.styleDocument.createDocumentFragment();
this._createViewsProcess = new UpdateProcess(this.styleWindow, CssHtmlTree.propertyNames, {
onItem: (aPropertyName) => {
// Per-item callback.
let propView = new PropertyView(this, aPropertyName);
fragment.appendChild(propView.buildMain());
fragment.appendChild(propView.buildSelectorContainer());
if (propView.visible) {
this.numVisibleProperties++;
}
this.propertyViews.push(propView);
},
onCancel: () => {
deferred.reject("_createPropertyViews cancelled");
},
onDone: () => {
// Completed callback.
this.propertyContainer.appendChild(fragment);
this.noResults.hidden = this.numVisibleProperties > 0;
deferred.resolve(undefined);
}
});
CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
// Refresh source filter ... this must be done after templateRoot has been
// processed.
this.refreshSourceFilter();
this.numVisibleProperties = 0;
let fragment = this.styleDocument.createDocumentFragment();
this._refreshProcess = new UpdateProcess(this.styleWindow, CssHtmlTree.propertyNames, {
onItem: function(aPropertyName) {
// Per-item callback.
let propView = new PropertyView(this, aPropertyName);
fragment.appendChild(propView.buildMain());
fragment.appendChild(propView.buildSelectorContainer());
if (propView.visible) {
this.numVisibleProperties++;
}
propView.refreshMatchedSelectors();
this.propertyViews.push(propView);
}.bind(this),
onDone: function() {
// Completed callback.
this.htmlComplete = true;
this.propertyContainer.appendChild(fragment);
this.noResults.hidden = this.numVisibleProperties > 0;
this._refreshProcess = null;
// If a refresh was scheduled during the building, complete it.
if (this._needsRefresh) {
delete this._needsRefresh;
this.refreshPanel();
} else {
Services.obs.notifyObservers(null, "StyleInspector-populated", null);
}
}.bind(this)});
this._refreshProcess.schedule();
}
this._createViewsProcess.schedule();
return deferred.promise;
},
/**
@ -302,39 +314,54 @@ CssHtmlTree.prototype = {
*/
refreshPanel: function CssHtmlTree_refreshPanel()
{
// If we're still in the process of creating the initial layout,
// leave it alone.
if (!this.htmlComplete) {
if (this._refreshProcess) {
this._needsRefresh = true;
return promise.all([
this._createPropertyViews(),
this.pageStyle.getComputed(this.viewedElement, {
filter: this._sourceFilter,
onlyMatched: !this.includeBrowserStyles,
markMatched: true
})
]).then(([createViews, computed]) => {
this._matchedProperties = new Set;
for (let name in computed) {
if (computed[name].matched) {
this._matchedProperties.add(name);
}
}
return;
}
this._computed = computed;
if (this._refreshProcess) {
this._refreshProcess.cancel();
}
if (this._refreshProcess) {
this._refreshProcess.cancel();
}
this.noResults.hidden = true;
this.noResults.hidden = true;
// Reset visible property count
this.numVisibleProperties = 0;
// Reset visible property count
this.numVisibleProperties = 0;
// Reset zebra striping.
this._darkStripe = true;
// Reset zebra striping.
this._darkStripe = true;
let display = this.propertyContainer.style.display;
this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
onItem: function(aPropView) {
aPropView.refresh();
}.bind(this),
onDone: function() {
this._refreshProcess = null;
this.noResults.hidden = this.numVisibleProperties > 0;
Services.obs.notifyObservers(null, "StyleInspector-populated", null);
}.bind(this)
});
this._refreshProcess.schedule();
let display = this.propertyContainer.style.display;
let deferred = promise.defer();
this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
onItem: (aPropView) => {
aPropView.refresh();
},
onCancel: () => {
deferred.reject("refresh cancelled");
},
onDone: () => {
this._refreshProcess = null;
this.noResults.hidden = this.numVisibleProperties > 0;
this.styleInspector.inspector.emit("computed-view-refreshed");
deferred.resolve(undefined);
}
});
this._refreshProcess.schedule();
return deferred.promise;
}).then(null, (err) => console.error(err));
},
/**
@ -377,7 +404,7 @@ CssHtmlTree.prototype = {
refreshSourceFilter: function CssHtmlTree_setSourceFilter()
{
this._matchedProperties = null;
this.cssLogic.sourceFilter = this.includeBrowserStyles ?
this._sourceFilter = this.includeBrowserStyles ?
CssLogic.FILTER.UA :
CssLogic.FILTER.USER;
},
@ -409,21 +436,18 @@ CssHtmlTree.prototype = {
CssHtmlTree.propertyNames.sort();
CssHtmlTree.propertyNames.push.apply(CssHtmlTree.propertyNames,
mozProps.sort());
this._createPropertyViews();
},
/**
* Get a list of properties that have matched selectors.
* Get a set of properties that have matched selectors.
*
* @return {object} the object maps property names (keys) to booleans (values)
* that tell if the given property has matched selectors or not.
* @return {Set} If a property name is in the set, it has matching selectors.
*/
get matchedProperties()
{
if (!this._matchedProperties) {
this._matchedProperties =
this.cssLogic.hasMatchedSelectors(CssHtmlTree.propertyNames);
}
return this._matchedProperties;
return this._matchedProperties || new Set;
},
/**
@ -473,6 +497,9 @@ CssHtmlTree.prototype = {
this.searchField.removeEventListener("command", this.filterChanged);
// Cancel tree construction
if (this._createViewsProcess) {
this._createViewsProcess.cancel();
}
if (this._refreshProcess) {
this._refreshProcess.cancel();
}
@ -517,11 +544,18 @@ CssHtmlTree.prototype = {
delete this.propertyViews;
delete this.styleWindow;
delete this.styleDocument;
delete this.cssLogic;
delete this.styleInspector;
},
};
function PropertyInfo(aTree, aName) {
this.tree = aTree;
this.name = aName;
}
PropertyInfo.prototype = {
get value() this.tree._computed ? this.tree._computed[this.name].value : ""
}
/**
* A container to give easy access to property data from the template engine.
*
@ -539,6 +573,7 @@ function PropertyView(aTree, aName)
this.link = "https://developer.mozilla.org/CSS/" + aName;
this.templateMatchedSelectors = aTree.styleDocument.getElementById("templateMatchedSelectors");
this._propertyInfo = new PropertyInfo(aTree, aName);
}
PropertyView.prototype = {
@ -585,7 +620,7 @@ PropertyView.prototype = {
*/
get propertyInfo()
{
return this.tree.cssLogic.getPropertyInfo(this.name);
return this._propertyInfo;
},
/**
@ -593,7 +628,7 @@ PropertyView.prototype = {
*/
get hasMatchedSelectors()
{
return this.name in this.tree.matchedProperties;
return this.tree.matchedProperties.has(this.name);
},
/**
@ -738,15 +773,29 @@ PropertyView.prototype = {
}
if (this.matchedExpanded && hasMatchedSelectors) {
CssHtmlTree.processTemplate(this.templateMatchedSelectors,
this.matchedSelectorsContainer, this);
this.matchedExpander.setAttribute("open", "");
return this.tree.pageStyle.getMatchedSelectors(this.tree.viewedElement, this.name).then(matched => {
if (!this.matchedExpanded) {
return;
}
this._matchedSelectorResponse = matched;
CssHtmlTree.processTemplate(this.templateMatchedSelectors,
this.matchedSelectorsContainer, this);
this.matchedExpander.setAttribute("open", "");
this.tree.styleInspector.inspector.emit("computed-view-property-expanded");
}).then(null, console.error);
} else {
this.matchedSelectorsContainer.innerHTML = "";
this.matchedExpander.removeAttribute("open");
return promise.resolve(undefined);
}
},
get matchedSelectors()
{
return this._matchedSelectorResponse;
},
/**
* Provide access to the matched SelectorViews that we are currently
* displaying.
@ -755,7 +804,7 @@ PropertyView.prototype = {
{
if (!this._matchedSelectorViews) {
this._matchedSelectorViews = [];
this.propertyInfo.matchedSelectors.forEach(
this._matchedSelectorResponse.forEach(
function matchedSelectorViews_convert(aSelectorInfo) {
this._matchedSelectorViews.push(new SelectorView(this.tree, aSelectorInfo));
}, this);
@ -802,6 +851,15 @@ function SelectorView(aTree, aSelectorInfo)
this.tree = aTree;
this.selectorInfo = aSelectorInfo;
this._cacheStatusNames();
let rule = this.selectorInfo.rule;
if (rule && rule.parentStyleSheet) {
this.sheet = rule.parentStyleSheet;
this.source = CssLogic.shortSource(this.sheet) + ":" + rule.line;
} else {
this.source = CssLogic.l10n("rule.sourceElement");
this.href = "#";
}
}
/**
@ -859,24 +917,25 @@ SelectorView.prototype = {
return SelectorView.CLASS_NAMES[this.selectorInfo.status - 1];
},
/**
* A localized Get localized human readable info
*/
text: function SelectorView_text(aElement) {
let result = this.selectorInfo.selector.text;
if (this.selectorInfo.elementStyle) {
let source = this.selectorInfo.sourceElement;
let inspector = this.tree.styleInspector.inspector;
if (inspector.selection.node == source) {
result = "this";
} else {
result = CssLogic.getShortName(source);
}
result += ".style";
get href()
{
if (this._href) {
return this._href;
}
let sheet = this.selectorInfo.rule.parentStyleSheet;
this._href = sheet ? sheet.href : "#";
return this._href;
},
return result;
get sourceText()
{
return this.selectorInfo.sourceText;
},
get value()
{
return this.selectorInfo.value;
},
maybeOpenStyleEditor: function(aEvent)
@ -900,12 +959,8 @@ SelectorView.prototype = {
openStyleEditor: function(aEvent)
{
let inspector = this.tree.styleInspector.inspector;
let contentDoc = inspector.selection.document;
let cssSheet = this.selectorInfo.selector.cssRule._cssSheet;
let line = this.selectorInfo.ruleLine || 0;
let contentSheet = false;
let styleSheet;
let styleSheets;
let rule = this.selectorInfo.rule;
let line = rule.line || 0;
// The style editor can only display stylesheets coming from content because
// chrome stylesheets are not listed in the editor's stylesheet selector.
@ -913,40 +968,28 @@ SelectorView.prototype = {
// If the stylesheet is a content stylesheet we send it to the style
// editor else we display it in the view source window.
//
// We check if cssSheet exists in case of inline styles (which contain no
// sheet)
if (cssSheet) {
styleSheet = cssSheet.domSheet;
styleSheets = contentDoc.styleSheets;
// Array.prototype.indexOf always returns -1 here so we loop through
// the styleSheets array instead.
for each (let sheet in styleSheets) {
if (sheet == styleSheet) {
contentSheet = true;
break;
}
}
}
if (contentSheet) {
let href = rule.href;
let sheet = rule.parentStyleSheet;
if (sheet && href && !sheet.isSystem) {
let target = inspector.target;
if (ToolDefinitions.styleEditor.isTargetSupported(target)) {
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
toolbox.getCurrentPanel().selectStyleSheet(styleSheet.href, line);
toolbox.getCurrentPanel().selectStyleSheet(href, line);
});
}
} else {
let href = styleSheet ? styleSheet.href : "";
let viewSourceUtils = inspector.viewSourceUtils;
if (this.selectorInfo.sourceElement) {
href = this.selectorInfo.sourceElement.ownerDocument.location.href;
}
viewSourceUtils.viewSource(href, null, contentDoc, line);
return;
}
},
let contentDoc = null;
let rawNode = this.tree.viewedElement.rawNode();
if (rawNode) {
contentDoc = rawNode.ownerDocument;
}
let viewSourceUtils = inspector.viewSourceUtils;
viewSourceUtils.viewSource(href, null, contentDoc, line);
}
};
exports.CssHtmlTree = CssHtmlTree;

Просмотреть файл

@ -98,12 +98,12 @@
<a target="_blank" class="link theme-link"
onclick="${selector.openStyleEditor}"
onkeydown="${selector.maybeOpenStyleEditor}"
title="${selector.selectorInfo.href}"
tabindex="0">${selector.selectorInfo.source}</a>
title="${selector.href}"
tabindex="0">${selector.source}</a>
</span>
<span dir="ltr" class="rule-text ${selector.statusClass} theme-fg-color3" title="${selector.statusText}">
${selector.text(__element)}
<span class="other-property-value theme-fg-color1">${selector.selectorInfo.value}</span>
${selector.sourceText}
<span class="other-property-value theme-fg-color1">${selector.value}</span>
</span>
</p>
</loop>

Просмотреть файл

@ -7,9 +7,11 @@
"use strict";
const {Cc, Ci, Cu} = require("chrome");
const promise = require("sdk/core/promise");
let {CssLogic} = require("devtools/styleinspector/css-logic");
let {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -34,6 +36,46 @@ const CSS_RESOURCE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
const IOService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
function promiseWarn(err) {
console.error(err);
return promise.reject(err);
}
/**
* To figure out how shorthand properties are interpreted by the
* engine, we will set properties on a dummy element and observe
* how their .style attribute reflects them as computed values.
* This function creates the document in which those dummy elements
* will be created.
*/
var gDummyPromise;
function createDummyDocument() {
if (gDummyPromise) {
return gDummyPromise;
}
const { getDocShell, create: makeFrame } = require("sdk/frame/utils");
let frame = makeFrame(Services.appShell.hiddenDOMWindow.document, {
nodeName: "iframe",
namespaceURI: "http://www.w3.org/1999/xhtml",
allowJavascript: false,
allowPlugins: false,
allowAuth: false
});
let docShell = getDocShell(frame);
let eventTarget = docShell.chromeEventHandler;
docShell.createAboutBlankContentViewer(Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal));
let window = docShell.contentViewer.DOMDocument.defaultView;
window.location = "data:text/html,<html></html>";
let deferred = promise.defer()
eventTarget.addEventListener("DOMContentLoaded", function handler(event) {
eventTarget.removeEventListener("DOMContentLoaded", handler, false);
deferred.resolve(window.document);
}, false);
gDummyPromise = deferred.promise;
return gDummyPromise;
}
/**
* Our model looks like this:
*
@ -62,13 +104,17 @@ const IOService = Cc["@mozilla.org/network/io-service;1"]
* The ElementStyle can use this object to store metadata
* that might outlast the rule view, particularly the current
* set of disabled properties.
* @param {PageStyleFront} aPageStyle
* Front for the page style actor that will be providing
* the style information.
*
* @constructor
*/
function ElementStyle(aElement, aStore)
function ElementStyle(aElement, aStore, aPageStyle)
{
this.element = aElement;
this.store = aStore || {};
this.pageStyle = aPageStyle;
// We don't want to overwrite this.store.userProperties so we only create it
// if it doesn't already exist.
@ -85,9 +131,12 @@ function ElementStyle(aElement, aStore)
// To figure out how shorthand properties are interpreted by the
// engine, we will set properties on a dummy element and observe
// how their .style attribute reflects them as computed values.
this.dummyElement = doc.createElementNS(this.element.namespaceURI,
this.element.tagName);
this.populate();
this.dummyElementPromise = createDummyDocument().then(document => {
this.dummyElement = document.createElementNS(this.element.namespaceURI,
this.element.tagName);
document.documentElement.appendChild(this.dummyElement);
return this.dummyElement;
}).then(null, promiseWarn);
}
// We're exporting _ElementStyle for unit tests.
exports._ElementStyle = ElementStyle;
@ -101,6 +150,17 @@ ElementStyle.prototype = {
// to figure out how shorthand properties will be parsed.
dummyElement: null,
destroy: function()
{
this.dummyElement = null;
this.dummyElementPromise.then(dummyElement => {
if (dummyElement.parentNode) {
dummyElement.parentNode.removeChild(dummyElement);
}
this.dummyElementPromise = null;
});
},
/**
* Called by the Rule object when it has been changed through the
* setProperty* methods.
@ -115,62 +175,44 @@ ElementStyle.prototype = {
/**
* Refresh the list of rules to be displayed for the active element.
* Upon completion, this.rules[] will hold a list of Rule objects.
*
* Returns a promise that will be resolved when the elementStyle is
* ready.
*/
populate: function ElementStyle_populate()
{
// Store the current list of rules (if any) during the population
// process. They will be reused if possible.
this._refreshRules = this.rules;
let populated = this.pageStyle.getApplied(this.element, {
inherited: true,
matchedSelectors: true
}).then(entries => {
// Make sure the dummy element has been created before continuing...
return this.dummyElementPromise.then(() => {
if (this.populated != populated) {
// Don't care anymore.
return promise.reject("unused");
}
this.rules = [];
// Store the current list of rules (if any) during the population
// process. They will be reused if possible.
this._refreshRules = this.rules;
let element = this.element;
do {
this._addElementRules(element);
} while ((element = element.parentNode) &&
element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
this.rules = [];
// Mark overridden computed styles.
this.markOverridden();
for (let entry of entries) {
this._maybeAddRule(entry);
}
// We're done with the previous list of rules.
delete this._refreshRules;
},
// Mark overridden computed styles.
this.markOverridden();
_addElementRules: function ElementStyle_addElementRules(aElement)
{
let inherited = aElement !== this.element ? aElement : null;
// We're done with the previous list of rules.
delete this._refreshRules;
// Include the element's style first.
this._maybeAddRule({
style: aElement.style,
selectorText: CssLogic.l10n("rule.sourceElement"),
inherited: inherited
});
// Get the styles that apply to the element.
var domRules = domUtils.getCSSStyleRules(aElement);
// getCSStyleRules returns ordered from least-specific to
// most-specific.
for (let i = domRules.Count() - 1; i >= 0; i--) {
let domRule = domRules.GetElementAt(i);
// XXX: Optionally provide access to system sheets.
let contentSheet = CssLogic.isContentStylesheet(domRule.parentStyleSheet);
if (!contentSheet) {
continue;
}
if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
continue;
}
this._maybeAddRule({
domRule: domRule,
inherited: inherited
return null;
});
}
}).then(null, promiseWarn);
this.populated = populated;
return this.populated;
},
/**
@ -186,8 +228,12 @@ ElementStyle.prototype = {
{
// If we've already included this domRule (for example, when a
// common selector is inherited), ignore it.
if (aOptions.domRule &&
this.rules.some(function(rule) rule.domRule === aOptions.domRule)) {
if (aOptions.rule &&
this.rules.some(function(rule) rule.domRule === aOptions.rule)) {
return false;
}
if (aOptions.system) {
return false;
}
@ -195,11 +241,13 @@ ElementStyle.prototype = {
// If we're refreshing and the rule previously existed, reuse the
// Rule object.
for (let r of (this._refreshRules || [])) {
if (r.matches(aOptions)) {
rule = r;
rule.refresh();
break;
if (this._refreshRules) {
for (let r of this._refreshRules) {
if (r.matches(aOptions)) {
rule = r;
rule.refresh(aOptions);
break;
}
}
}
@ -214,6 +262,7 @@ ElementStyle.prototype = {
}
this.rules.push(rule);
return true;
},
/**
@ -324,11 +373,7 @@ ElementStyle.prototype = {
* The ElementStyle to which this rule belongs.
* @param {object} aOptions
* The information used to construct this rule. Properties include:
* domRule: the nsIDOMCSSStyleRule to view, if any.
* style: the nsIDOMCSSStyleDeclaration to view. If omitted,
* the domRule's style will be used.
* selectorText: selector text to display. If omitted, the domRule's
* selectorText will be used.
* rule: A StyleRuleActor
* inherited: An element this rule was inherited from. If omitted,
* the rule applies directly to the current element.
* @constructor
@ -336,15 +381,17 @@ ElementStyle.prototype = {
function Rule(aElementStyle, aOptions)
{
this.elementStyle = aElementStyle;
this.domRule = aOptions.domRule || null;
this.style = aOptions.style || this.domRule.style;
this.selectorText = aOptions.selectorText || this.domRule.selectorText;
this.domRule = aOptions.rule || null;
this.style = aOptions.rule;
this.matchedSelectors = aOptions.matchedSelectors || [];
this.inherited = aOptions.inherited || null;
this._modificationDepth = 0;
if (this.domRule) {
let parentRule = this.domRule.parentRule;
if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
this.mediaText = parentRule.media.mediaText;
this.mediaText = parentRule.mediaText;
}
}
@ -363,11 +410,12 @@ Rule.prototype = {
return this._title;
}
this._title = CssLogic.shortSource(this.sheet);
if (this.domRule) {
if (this.domRule.type !== ELEMENT_STYLE) {
this._title += ":" + this.ruleLine;
}
return this._title + (this.mediaText ? " @media " + this.mediaText : "");
this._title = this._title + (this.mediaText ? " @media " + this.mediaText : "");
return this._title;
},
get inheritedSource()
@ -387,6 +435,11 @@ Rule.prototype = {
return this._inheritedSource;
},
get selectorText()
{
return this.domRule.selectors ? this.domRule.selectors.join(", ") : CssLogic.l10n("rule.sourceElement");
},
/**
* The rule's stylesheet.
*/
@ -400,11 +453,7 @@ Rule.prototype = {
*/
get ruleLine()
{
if (!this.sheet) {
// No stylesheet, no ruleLine
return null;
}
return domUtils.getRuleLine(this.domRule);
return this.domRule ? this.domRule.line : null;
},
/**
@ -416,7 +465,7 @@ Rule.prototype = {
*/
matches: function Rule_matches(aOptions)
{
return (this.style === (aOptions.style || aOptions.domRule.style));
return this.style === aOptions.rule;
},
/**
@ -447,12 +496,15 @@ Rule.prototype = {
* when calling from setPropertyValue & setPropertyName to signify
* that the property should be saved in store.userProperties.
*/
applyProperties: function Rule_applyProperties(aName)
applyProperties: function Rule_applyProperties(aModifications, aName)
{
if (!aModifications) {
aModifications = this.style.startModifyingProperties();
}
let disabledProps = [];
let store = this.elementStyle.store;
for each (let prop in this.textProps) {
for (let prop of this.textProps) {
if (!prop.enabled) {
disabledProps.push({
name: prop.name,
@ -462,21 +514,11 @@ Rule.prototype = {
continue;
}
this.style.setProperty(prop.name, prop.value, prop.priority);
aModifications.setProperty(prop.name, prop.value, prop.priority);
if (aName && prop.name == aName) {
store.userProperties.setProperty(
this.style, prop.name,
this.style.getPropertyValue(prop.name),
prop.value);
}
// Refresh the property's priority from the style, to reflect
// any changes made during parsing.
prop.priority = this.style.getPropertyPriority(prop.name);
prop.updateComputed();
}
this.elementStyle._changed();
// Store disabled properties in the disabled store.
let disabled = this.elementStyle.store.disabled;
@ -486,7 +528,46 @@ Rule.prototype = {
disabled.delete(this.style);
}
this.elementStyle.markOverridden();
let promise = aModifications.apply().then(() => {
let cssProps = {};
for (let cssProp of this._parseCSSText(this.style.cssText)) {
cssProps[cssProp.name] = cssProp;
}
for (let textProp of this.textProps) {
if (!textProp.enabled) {
continue;
}
let cssProp = cssProps[textProp.name];
if (!cssProp) {
cssProp = {
name: textProp.name,
value: "",
priority: ""
}
}
if (aName && textProp.name == aName) {
store.userProperties.setProperty(
this.style, textProp.name,
null,
cssProp.value,
textProp.value);
}
textProp.priority = cssProp.priority;
}
this.elementStyle.markOverridden();
if (promise === this._applyingModifications) {
this._applyingModifications = null;
}
this.elementStyle._changed();
}).then(null, promiseWarn);
this._applyingModifications = promise;
return promise;
},
/**
@ -502,9 +583,10 @@ Rule.prototype = {
if (aName === aProperty.name) {
return;
}
this.style.removeProperty(aProperty.name);
let modifications = this.style.startModifyingProperties();
modifications.removeProperty(aProperty.name);
aProperty.name = aName;
this.applyProperties(aName);
this.applyProperties(modifications, aName);
},
/**
@ -524,7 +606,7 @@ Rule.prototype = {
}
aProperty.value = aValue;
aProperty.priority = aPriority;
this.applyProperties(aProperty.name);
this.applyProperties(null, aProperty.name);
},
/**
@ -533,10 +615,11 @@ Rule.prototype = {
setPropertyEnabled: function Rule_enableProperty(aProperty, aValue)
{
aProperty.enabled = !!aValue;
let modifications = this.style.startModifyingProperties();
if (!aProperty.enabled) {
this.style.removeProperty(aProperty.name);
modifications.removeProperty(aProperty.name);
}
this.applyProperties();
this.applyProperties(modifications);
},
/**
@ -546,10 +629,32 @@ Rule.prototype = {
removeProperty: function Rule_removeProperty(aProperty)
{
this.textProps = this.textProps.filter(function(prop) prop != aProperty);
this.style.removeProperty(aProperty);
let modifications = this.style.startModifyingProperties();
modifications.removeProperty(aProperty.name);
// Need to re-apply properties in case removing this TextProperty
// exposes another one.
this.applyProperties();
this.applyProperties(modifications);
},
_parseCSSText: function Rule_parseProperties(aCssText)
{
let lines = aCssText.match(CSS_LINE_RE);
let props = [];
for (let line of lines) {
dump("line: " + line + "\n");
let [, name, value, priority] = CSS_PROP_RE.exec(line) || []
if (!name || !value) {
continue;
}
props.push({
name: name,
value: value,
priority: priority || ""
});
}
return props;
},
/**
@ -560,19 +665,15 @@ Rule.prototype = {
{
let textProps = [];
let store = this.elementStyle.store;
let lines = this.style.cssText.match(CSS_LINE_RE);
for each (let line in lines) {
let matches = CSS_PROP_RE.exec(line);
if (!matches || !matches[2])
continue;
let name = matches[1];
let props = this._parseCSSText(this.style.cssText);
for (let prop of props) {
let name = prop.name;
if (this.inherited && !domUtils.isInheritedProperty(name)) {
continue;
}
let value = store.userProperties.getProperty(this.style, name, matches[2]);
let prop = new TextProperty(this, name, value, matches[3] || "");
textProps.push(prop);
let value = store.userProperties.getProperty(this.style, name, prop.value);
let textProp = new TextProperty(this, name, value, prop.priority);
textProps.push(textProp);
}
return textProps;
@ -607,8 +708,9 @@ Rule.prototype = {
* Reread the current state of the rules and rebuild text
* properties as needed.
*/
refresh: function Rule_refresh()
refresh: function Rule_refresh(aOptions)
{
this.matchedSelectors = aOptions.matchedSelectors || [];
let newTextProps = this._getTextProperties();
// Update current properties for each property present on the style.
@ -861,14 +963,15 @@ TextProperty.prototype = {
* The CSS rule view can use this object to store metadata
* that might outlast the rule view, particularly the current
* set of disabled properties.
* @param {<iframe>} aOuterIFrame
* The iframe containing the ruleview.
* @param {PageStyleFront} aPageStyle
* The PageStyleFront for communicating with the remote server.
* @constructor
*/
function CssRuleView(aDoc, aStore)
function CssRuleView(aDoc, aStore, aPageStyle)
{
this.doc = aDoc;
this.store = aStore;
this.pageStyle = aPageStyle;
this.element = this.doc.createElementNS(HTML_NS, "div");
this.element.className = "ruleview devtools-monospace";
this.element.flex = 1;
@ -892,6 +995,10 @@ CssRuleView.prototype = {
// The element that we're inspecting.
_viewedElement: null,
setPageStyle: function(aPageStyle) {
this.pageStyle = aPageStyle;
},
/**
* Return {bool} true if the rule view currently has an input editor visible.
*/
@ -910,6 +1017,8 @@ CssRuleView.prototype = {
this.element.parentNode.removeChild(this.element);
}
this.elementStyle.destroy();
this.popup.destroy();
},
@ -922,7 +1031,7 @@ CssRuleView.prototype = {
highlight: function CssRuleView_highlight(aElement)
{
if (this._viewedElement === aElement) {
return;
return promise.resolve(undefined);
}
this.clear();
@ -934,15 +1043,15 @@ CssRuleView.prototype = {
this._viewedElement = aElement;
if (!this._viewedElement) {
this._showEmpty();
return;
return promise.resolve(undefined);
}
this._elementStyle = new ElementStyle(aElement, this.store);
this._elementStyle.onChanged = function() {
this._changed();
}.bind(this);
this._createEditors();
this._elementStyle = new ElementStyle(aElement, this.store, this.pageStyle);
return this._populate().then(() => {
this._elementStyle.onChanged = () => {
this._changed();
};
}).then(null, console.error);
},
/**
@ -952,21 +1061,29 @@ CssRuleView.prototype = {
{
// Ignore refreshes during editing or when no element is selected.
if (this.isEditing || !this._elementStyle) {
return;
return promise.resolve(null);
}
this._clearRules();
// Repopulate the element style.
this._elementStyle.populate();
return this._populate();
},
// Refresh the rule editors.
this._createEditors();
_populate: function() {
let elementStyle = this._elementStyle;
return this._elementStyle.populate().then(() => {
if (this._elementStyle != elementStyle) {
return promise.reject("element changed");
}
this._createEditors();
// Notify anyone that cares that we refreshed.
var evt = this.doc.createEvent("Events");
evt.initEvent("CssRuleViewRefreshed", true, false);
this.element.dispatchEvent(evt);
// Notify anyone that cares that we refreshed.
var evt = this.doc.createEvent("Events");
evt.initEvent("CssRuleViewRefreshed", true, false);
this.element.dispatchEvent(evt);
return undefined;
}).then(null, promiseWarn);
},
/**
@ -1023,7 +1140,10 @@ CssRuleView.prototype = {
// Run through the current list of rules, attaching
// their editors in order. Create editors if needed.
let lastInheritedSource = "";
for each (let rule in this._elementStyle.rules) {
for (let rule of this._elementStyle.rules) {
if (rule.domRule.system) {
continue;
}
let inheritedSource = rule.inheritedSource;
if (inheritedSource != lastInheritedSource) {
@ -1117,7 +1237,7 @@ RuleEditor.prototype = {
class: "ruleview-rule-source theme-link"
});
source.addEventListener("click", function() {
let rule = this.rule;
let rule = this.rule.domRule;
let evt = this.doc.createEvent("CustomEvent");
evt.initCustomEvent("CssRuleViewCSSLinkClicked", true, false, {
rule: rule,
@ -1198,11 +1318,10 @@ RuleEditor.prototype = {
// If selector text comes from a css rule, highlight selectors that
// actually match. For custom selector text (such as for the 'element'
// style, just show the text directly.
if (this.rule.domRule && this.rule.domRule.selectorText) {
let selectors = CssLogic.getSelectors(this.rule.domRule);
let element = this.rule.inherited || this.ruleView._viewedElement;
for (let i = 0; i < selectors.length; i++) {
let selector = selectors[i];
if (this.rule.domRule.type === ELEMENT_STYLE) {
this.selectorText.textContent = this.rule.selectorText;
} else {
this.rule.domRule.selectors.forEach((selector, i) => {
if (i != 0) {
createChild(this.selectorText, "span", {
class: "ruleview-selector-separator",
@ -1210,7 +1329,7 @@ RuleEditor.prototype = {
});
}
let cls;
if (domUtils.selectorMatchesElement(element, this.rule.domRule, i)) {
if (this.rule.matchedSelectors.indexOf(selector) > -1) {
cls = "ruleview-selector-matched";
} else {
cls = "ruleview-selector-unmatched";
@ -1219,9 +1338,7 @@ RuleEditor.prototype = {
class: cls,
textContent: selector
});
}
} else {
this.selectorText.textContent = this.rule.selectorText;
});
}
for (let prop of this.rule.textProps) {
@ -1339,7 +1456,7 @@ function TextPropertyEditor(aRuleEditor, aProperty)
this.browserWindow = this.doc.defaultView.top;
let sheet = this.prop.rule.sheet;
let href = sheet ? CssLogic.href(sheet) : null;
let href = sheet ? (sheet.href || sheet.nodeHref) : null;
if (href) {
this.sheetURI = IOService.newURI(href, null, null);
}

Просмотреть файл

@ -15,7 +15,6 @@ loader.lazyGetter(this, "RuleView", () => require("devtools/styleinspector/rule-
loader.lazyGetter(this, "ComputedView", () => require("devtools/styleinspector/computed-view"));
loader.lazyGetter(this, "_strings", () => Services.strings
.createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
loader.lazyGetter(this, "CssLogic", () => require("devtools/styleinspector/css-logic").CssLogic);
// This module doesn't currently export any symbols directly, it only
// registers inspector tools.
@ -29,19 +28,21 @@ function RuleViewTool(aInspector, aWindow, aIFrame)
this.view = new RuleView.CssRuleView(this.doc, null);
this.doc.documentElement.appendChild(this.view.element);
this._changeHandler = function() {
this._changeHandler = () => {
this.inspector.markDirty();
}.bind(this);
};
this.view.element.addEventListener("CssRuleViewChanged", this._changeHandler)
this.view.element.addEventListener("CssRuleViewChanged", this._changeHandler);
this._cssLinkHandler = function(aEvent) {
let contentDoc = this.inspector.selection.document;
this._refreshHandler = () => {
this.inspector.emit("rule-view-refreshed");
};
this.view.element.addEventListener("CssRuleViewRefreshed", this._refreshHandler);
this._cssLinkHandler = (aEvent) => {
let rule = aEvent.detail.rule;
let line = rule.ruleLine || 0;
let styleSheet = rule.sheet;
let styleSheets = contentDoc.styleSheets;
let contentSheet = false;
let line = rule.line || 0;
// The style editor can only display stylesheets coming from content because
// chrome stylesheets are not listed in the editor's stylesheet selector.
@ -49,42 +50,34 @@ function RuleViewTool(aInspector, aWindow, aIFrame)
// If the stylesheet is a content stylesheet we send it to the style
// editor else we display it in the view source window.
//
// Array.prototype.indexOf always returns -1 here so we loop through
// the styleSheets object instead.
for each (let sheet in styleSheets) {
if (sheet == styleSheet) {
contentSheet = true;
break;
}
}
if (contentSheet) {
let href = rule.href;
let sheet = rule.parentStyleSheet;
if (sheet && href && !sheet.isSystem) {
let target = this.inspector.target;
if (ToolDefinitions.styleEditor.isTargetSupported(target)) {
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
toolbox.getCurrentPanel().selectStyleSheet(styleSheet.href, line);
toolbox.getCurrentPanel().selectStyleSheet(href, line);
});
}
} else {
let href = styleSheet ? styleSheet.href : "";
if (rule.elementStyle.element) {
href = rule.elementStyle.element.ownerDocument.location.href;
}
let viewSourceUtils = this.inspector.viewSourceUtils;
viewSourceUtils.viewSource(href, null, contentDoc, line);
return;
}
}.bind(this);
let contentDoc = this.inspector.selection.document;
let viewSourceUtils = this.inspector.viewSourceUtils;
viewSourceUtils.viewSource(href, null, contentDoc, line);
}
this.view.element.addEventListener("CssRuleViewCSSLinkClicked",
this._cssLinkHandler);
this._onSelect = this.onSelect.bind(this);
this.inspector.selection.on("detached", this._onSelect);
this.inspector.selection.on("new-node", this._onSelect);
this.inspector.selection.on("new-node-front", this._onSelect);
this.refresh = this.refresh.bind(this);
this.inspector.on("layout-change", this.refresh);
this.inspector.sidebar.on("ruleview-selected", this.refresh);
this.panelSelected = this.panelSelected.bind(this);
this.inspector.sidebar.on("ruleview-selected", this.panelSelected);
this.inspector.selection.on("pseudoclass", this.refresh);
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this._onSelect);
@ -97,22 +90,30 @@ exports.RuleViewTool = RuleViewTool;
RuleViewTool.prototype = {
onSelect: function RVT_onSelect(aEvent) {
if (!this.isActive()) {
// We'll update when the panel is selected.
return;
}
this.view.setPageStyle(this.inspector.pageStyle);
if (!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode()) {
this.view.highlight(null);
return;
}
if (!aEvent || aEvent == "new-node") {
if (!aEvent || aEvent == "new-node-front") {
if (this.inspector.selection.reason == "highlighter") {
this.view.highlight(null);
} else {
this.view.highlight(this.inspector.selection.node);
let done = this.inspector.updating("rule-view");
this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
}
}
if (aEvent == "locked") {
this.view.highlight(this.inspector.selection.node);
let done = this.inspector.updating("rule-view");
this.view.highlight(this.inspector.selection.nodeFront).then(done, done);
}
},
@ -126,11 +127,19 @@ RuleViewTool.prototype = {
}
},
panelSelected: function() {
if (this.inspector.selection.nodeFront === this.view.viewedElement) {
this.view.nodeChanged();
} else {
this.onSelect();
}
},
destroy: function RVT_destroy() {
this.inspector.off("layout-change", this.refresh);
this.inspector.sidebar.off("ruleview-selected", this.refresh);
this.inspector.selection.off("pseudoclass", this.refresh);
this.inspector.selection.off("new-node", this._onSelect);
this.inspector.selection.off("new-node-front", this._onSelect);
if (this.inspector.highlighter) {
this.inspector.highlighter.off("locked", this._onSelect);
}
@ -141,6 +150,9 @@ RuleViewTool.prototype = {
this.view.element.removeEventListener("CssRuleViewChanged",
this._changeHandler);
this.view.element.removeEventListener("CssRuleViewRefreshed",
this._refreshHandler);
this.doc.documentElement.removeChild(this.view.element);
this.view.destroy();
@ -158,21 +170,20 @@ function ComputedViewTool(aInspector, aWindow, aIFrame)
this.window = aWindow;
this.document = aWindow.document;
this.outerIFrame = aIFrame;
this.cssLogic = new CssLogic();
this.view = new ComputedView.CssHtmlTree(this);
this.view = new ComputedView.CssHtmlTree(this, aInspector.pageStyle);
this._onSelect = this.onSelect.bind(this);
this.inspector.selection.on("detached", this._onSelect);
this.inspector.selection.on("new-node", this._onSelect);
this.inspector.selection.on("new-node-front", this._onSelect);
if (this.inspector.highlighter) {
this.inspector.highlighter.on("locked", this._onSelect);
}
this.refresh = this.refresh.bind(this);
this.inspector.on("layout-change", this.refresh);
this.inspector.sidebar.on("computedview-selected", this.refresh);
this.inspector.selection.on("pseudoclass", this.refresh);
this.panelSelected = this.panelSelected.bind(this);
this.inspector.sidebar.on("computedview-selected", this.panelSelected);
this.cssLogic.highlight(null);
this.view.highlight(null);
this.onSelect();
@ -183,24 +194,35 @@ exports.ComputedViewTool = ComputedViewTool;
ComputedViewTool.prototype = {
onSelect: function CVT_onSelect(aEvent)
{
if (!this.isActive()) {
// We'll try again when we're selected.
return;
}
this.view.setPageStyle(this.inspector.pageStyle);
if (!this.inspector.selection.isConnected() ||
!this.inspector.selection.isElementNode()) {
this.view.highlight(null);
return;
}
if (!aEvent || aEvent == "new-node") {
if (!aEvent || aEvent == "new-node-front") {
if (this.inspector.selection.reason == "highlighter") {
// FIXME: We should hide view's content
} else {
this.cssLogic.highlight(this.inspector.selection.node);
this.view.highlight(this.inspector.selection.node);
let done = this.inspector.updating("computed-view");
this.view.highlight(this.inspector.selection.nodeFront).then(() => {
done();
});
}
}
if (aEvent == "locked") {
this.cssLogic.highlight(this.inspector.selection.node);
this.view.highlight(this.inspector.selection.node);
if (aEvent == "locked" && this.inspector.selection.nodeFront != this.view.viewedElement) {
let done = this.inspector.updating("computed-view");
this.view.highlight(this.inspector.selection.nodeFront).then(() => {
done();
});
}
},
@ -210,17 +232,24 @@ ComputedViewTool.prototype = {
refresh: function CVT_refresh() {
if (this.isActive()) {
this.cssLogic.highlight(this.inspector.selection.node);
this.view.refreshPanel();
}
},
panelSelected: function() {
if (this.inspector.selection.nodeFront === this.view.viewedElement) {
this.view.refreshPanel();
} else {
this.onSelect();
}
},
destroy: function CVT_destroy(aContext)
{
this.inspector.off("layout-change", this.refresh);
this.inspector.sidebar.off("computedview-selected", this.refresh);
this.inspector.selection.off("pseudoclass", this.refresh);
this.inspector.selection.off("new-node", this._onSelect);
this.inspector.selection.off("new-node-front", this._onSelect);
if (this.inspector.highlighter) {
this.inspector.highlighter.off("locked", this._onSelect);
}

Просмотреть файл

@ -12,7 +12,6 @@ relativesrcdir = @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
MOCHITEST_BROWSER_FILES = \
browser_styleinspector.js \
browser_bug683672.js \
browser_styleinspector_bug_672746_default_styles.js \
browser_styleinspector_bug_672744_search_filter.js \

Просмотреть файл

@ -13,6 +13,7 @@ const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/tes
let tempScope = {};
let {CssHtmlTree, PropertyView} = devtools.require("devtools/styleinspector/computed-view");
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
function test()
{
@ -25,49 +26,49 @@ function tabLoaded()
{
browser.removeEventListener("load", tabLoaded, true);
doc = content.document;
openInspector(selectNode);
openComputedView(selectNode);
}
function selectNode(aInspector)
function selectNode(aInspector, aComputedView)
{
inspector = aInspector;
computedView = aComputedView;
div = content.document.getElementById("test");
ok(div, "captain, we have the div");
inspector.selection.setNode(div);
inspector.sidebar.once("computedview-ready", function() {
computedView = getComputedView(inspector);
inspector.sidebar.select("computedview");
runTests();
});
inspector.once("inspector-updated", runTests);
}
function runTests()
{
testMatchedSelectors();
info("finishing up");
finishUp();
testMatchedSelectors().then(() => {
info("finishing up");
finishUp();
});
}
function testMatchedSelectors()
{
info("checking selector counts, matched rules and titles");
is(div, computedView.viewedElement,
is(div, computedView.viewedElement.rawNode(),
"style inspector node matches the selected node");
let propertyView = new PropertyView(computedView, "color");
let numMatchedSelectors = propertyView.propertyInfo.matchedSelectors.length;
propertyView.buildMain();
propertyView.buildSelectorContainer();
propertyView.matchedExpanded = true;
return propertyView.refreshMatchedSelectors().then(() => {
let numMatchedSelectors = propertyView.matchedSelectors.length;
is(numMatchedSelectors, 6,
"CssLogic returns the correct number of matched selectors for div");
is(numMatchedSelectors, 6,
"CssLogic returns the correct number of matched selectors for div");
is(propertyView.hasMatchedSelectors, true,
"hasMatchedSelectors returns true");
is(propertyView.hasMatchedSelectors, true,
"hasMatchedSelectors returns true");
}).then(null, (err) => console.error(err));
}
function finishUp()

Просмотреть файл

@ -11,6 +11,9 @@ let computedView;
const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/" +
"test/browser_bug722196_identify_media_queries.html";
let {PropertyView} = devtools.require("devtools/styleinspector/computed-view");
let {CssLogic} = devtools.require("devtools/styleinspector/css-logic");
function test()
{
waitForExplicitFinish();
@ -23,26 +26,24 @@ function docLoaded()
browser.removeEventListener("load", docLoaded, true);
doc = content.document;
openInspector(selectNode);
openComputedView(selectNode);
}
function selectNode(aInspector)
function selectNode(aInspector, aComputedView)
{
computedView = aComputedView;
var div = doc.querySelector("div");
ok(div, "captain, we have the div");
aInspector.selection.setNode(div);
aInspector.sidebar.once("computedview-ready", function() {
aInspector.sidebar.select("computedview");
computedView = getComputedView(aInspector);
checkSheets();
});
aInspector.once("inspector-updated", checkCssLogic);
}
function checkSheets()
function checkCssLogic()
{
let cssLogic = computedView.cssLogic;
let cssLogic = new CssLogic();
cssLogic.highlight(doc.querySelector("div"));
cssLogic.processMatchedSelectors();
let _strings = Services.strings
@ -57,7 +58,26 @@ function checkSheets()
is(cssLogic._matchedRules[1][0].source, source2,
"rule.source gives correct output for rule 2");
finishUp();
checkPropertyView();
}
function checkPropertyView()
{
let propertyView = new PropertyView(computedView, "width");
propertyView.buildMain();
propertyView.buildSelectorContainer();
propertyView.matchedExpanded = true;
return propertyView.refreshMatchedSelectors().then(() => {
let numMatchedSelectors = propertyView.matchedSelectors.length;
is(numMatchedSelectors, 2,
"Property view has the correct number of matched selectors for div");
is(propertyView.hasMatchedSelectors, true,
"hasMatchedSelectors returns true");
finishUp();
}).then(null, (err) => console.error(err));
}
function finishUp()

Просмотреть файл

@ -6,6 +6,7 @@
// rule view.
let doc;
let inspector = null;
const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/" +
"test/browser_bug722196_identify_media_queries.html";
@ -21,7 +22,15 @@ function docLoaded()
{
browser.removeEventListener("load", docLoaded, true);
doc = content.document;
checkSheets();
openRuleView(selectNode);
}
function selectNode(aInspector, aRuleView)
{
inspector = aInspector;
inspector.selection.setNode(doc.querySelector("div"));
inspector.once("inspector-updated", checkSheets);
}
function checkSheets()
@ -29,14 +38,14 @@ function checkSheets()
var div = doc.querySelector("div");
ok(div, "captain, we have the div");
let elementStyle = new _ElementStyle(div);
is(elementStyle.rules.length, 3, "Should have 3 rules.");
let elementStyle = ruleView()._elementStyle;
let _strings = Services.strings
.createBundle("chrome://browser/locale/devtools/styleinspector.properties");
let inline = _strings.GetStringFromName("rule.sourceInline");
is(elementStyle.rules.length, 3, "Should have 3 rules.");
is(elementStyle.rules[0].title, inline, "check rule 0 title");
is(elementStyle.rules[1].title, inline +
":15 @media screen and (min-width: 1px)", "check rule 1 title");

Просмотреть файл

@ -6,8 +6,8 @@
// arrow keys works correctly.
let doc;
let ruleDialog;
let ruleView;
let view;
let inspector;
function setUpTests()
{
@ -15,25 +15,22 @@ function setUpTests()
'margin-top:0px;' +
'padding-top: 0px;' +
'color:#000000;' +
'background-color: #000000; >"'+
'background-color: #000000;" >'+
'</div>';
let testElement = doc.getElementById("test");
ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xhtml",
"cssruleviewtest",
"width=350,height=350");
ruleDialog.addEventListener("load", function onLoad(evt) {
ruleDialog.removeEventListener("load", onLoad, true);
let doc = ruleDialog.document;
ruleView = new CssRuleView(doc);
doc.documentElement.appendChild(ruleView.element);
ruleView.highlight(testElement);
waitForFocus(runTests, ruleDialog);
}, true);
openRuleView((aInspector, aRuleView) => {
inspector = aInspector;
view = aRuleView;
inspector.selection.setNode(doc.getElementById("test"));
inspector.once("inspector-updated", () => {
runTests();
})
});
}
function runTests()
{
let idRuleEditor = ruleView.element.children[0]._ruleEditor;
let idRuleEditor = view.element.children[0]._ruleEditor;
let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
let paddingPropEditor = idRuleEditor.rule.textProps[1].editor;
let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor;
@ -52,7 +49,7 @@ function runTests()
8: { pageDown: true, shift: true, start: "0px", end: "-100px", selectAll: true,
nextTest: test2 }
});
EventUtils.synthesizeMouse(marginPropEditor.valueSpan, 1, 1, {}, ruleDialog);
EventUtils.synthesizeMouse(marginPropEditor.valueSpan, 1, 1, {}, view.doc.defaultView);
})();
function test2() {
@ -69,7 +66,7 @@ function runTests()
9: { start: "0ex", end: "1ex", selectAll: true,
nextTest: test3 }
});
EventUtils.synthesizeMouse(paddingPropEditor.valueSpan, 1, 1, {}, ruleDialog);
EventUtils.synthesizeMouse(paddingPropEditor.valueSpan, 1, 1, {}, view.doc.defaultView);
};
function test3() {
@ -83,7 +80,7 @@ function runTests()
6: { down: true, shift: true, start: "#000000", end: "#000000", selectAll: true,
nextTest: test4 }
});
EventUtils.synthesizeMouse(hexColorPropEditor.valueSpan, 1, 1, {}, ruleDialog);
EventUtils.synthesizeMouse(hexColorPropEditor.valueSpan, 1, 1, {}, view.doc.defaultView);
};
function test4() {
@ -97,7 +94,7 @@ function runTests()
6: { down: true, shift: true, start: "rgb(0,5,0)", end: "rgb(0,0,0)", selection: [6,7],
nextTest: test5 }
});
EventUtils.synthesizeMouse(rgbColorPropEditor.valueSpan, 1, 1, {}, ruleDialog);
EventUtils.synthesizeMouse(rgbColorPropEditor.valueSpan, 1, 1, {}, view.doc.defaultView);
};
function test5() {
@ -111,7 +108,7 @@ function runTests()
6: { down: true, shift: true, start: "0px 0px 0px 0px", end: "-10px 0px 0px 0px", selectAll: true,
nextTest: test6 }
});
EventUtils.synthesizeMouse(paddingPropEditor.valueSpan, 1, 1, {}, ruleDialog);
EventUtils.synthesizeMouse(paddingPropEditor.valueSpan, 1, 1, {}, view.doc.defaultView);
};
function test6() {
@ -133,7 +130,7 @@ function runTests()
14: { alt: true, start: "url('test--0.1.png')", end: "url('test-0.png')", selection: [10,14],
endTest: true }
});
EventUtils.synthesizeMouse(marginPropEditor.valueSpan, 1, 1, {}, ruleDialog);
EventUtils.synthesizeMouse(marginPropEditor.valueSpan, 1, 1, {}, view.doc.defaultView);
};
}
@ -172,14 +169,11 @@ function testIncrement( aEditor, aOptions )
key = ( aOptions.pageDown ) ? "VK_PAGE_DOWN" : ( aOptions.pageUp ) ? "VK_PAGE_UP" : key;
EventUtils.synthesizeKey(key,
{altKey: aOptions.alt, shiftKey: aOptions.shift},
ruleDialog);
view.doc.defaultView);
}
function finishTest()
{
ruleView.clear();
ruleDialog.close();
ruleDialog = ruleView = null;
doc = null;
gBrowser.removeCurrentTab();
finish();

Просмотреть файл

@ -62,7 +62,7 @@ function openRuleView()
let node = content.document.getElementsByTagName("h1")[0];
inspector.selection.setNode(node);
inspector.sidebar.once("ruleview-ready", testCompletion);
inspector.once("inspector-updated", testCompletion);
});
}

Просмотреть файл

@ -49,7 +49,7 @@ function openRuleView() {
let node = content.document.getElementsByTagName("h1")[0];
inspector.selection.setNode(node);
inspector.sidebar.once("ruleview-ready", testCompletion);
inspector.once("inspector-updated", testCompletion);
});
}

Просмотреть файл

@ -47,7 +47,7 @@ function openRuleView()
let node = content.document.getElementsByTagName("h1")[0];
inspector.selection.setNode(node);
inspector.sidebar.once("ruleview-ready", testCompletion);
inspector.once("inspector-updated", testCompletion);
});
}

Просмотреть файл

@ -48,7 +48,7 @@ function openRuleView() {
let node = content.document.getElementsByTagName("h1")[0];
inspector.selection.setNode(node);
inspector.sidebar.once("ruleview-ready", testCompletion);
inspector.once("inspector-updated", testCompletion);
});
}

Просмотреть файл

@ -13,29 +13,23 @@ function createDocument()
doc.title = "Style Inspector Selector Text Test";
openInspector(openComputedView);
openComputedView(startTests);
}
function openComputedView(aInspector)
function startTests(aInspector, aComputedView)
{
computedView = aComputedView;
let div = doc.querySelector("div");
ok(div, "captain, we have the test div");
aInspector.selection.setNode(div);
aInspector.sidebar.once("computedview-ready", function() {
aInspector.sidebar.select("computedview");
computedView = getComputedView(aInspector);
Services.obs.addObserver(SI_checkText, "StyleInspector-populated", false);
});
aInspector.once("inspector-updated", SI_checkText);
}
function SI_checkText()
{
Services.obs.removeObserver(SI_checkText, "StyleInspector-populated");
let propertyView = null;
computedView.propertyViews.some(function(aView) {
if (aView.name == "color") {
@ -50,15 +44,16 @@ function SI_checkText()
is(propertyView.hasMatchedSelectors, true, "hasMatchedSelectors is true");
propertyView.matchedExpanded = true;
propertyView.refreshMatchedSelectors();
propertyView.refreshMatchedSelectors().then(() => {
let span = propertyView.matchedSelectorsContainer.querySelector("span.rule-text");
ok(span, "found the first table row");
let span = propertyView.matchedSelectorsContainer.querySelector("span.rule-text");
ok(span, "found the first table row");
let selector = propertyView.matchedSelectorViews[0];
ok(selector, "found the first matched selector view");
let selector = propertyView.matchedSelectorViews[0];
ok(selector, "found the first matched selector view");
finishUp();
finishUp();
});
}
function finishUp()

Просмотреть файл

@ -42,29 +42,20 @@ const DOCUMENT_URL = "data:text/html,"+encodeURIComponent(
function selectNode(aInspector)
function selectNode(aInspector, aComputedView)
{
inspector = aInspector;
computedView = aComputedView;
let span = doc.querySelector("span");
ok(span, "captain, we have the span");
aInspector.selection.setNode(span);
aInspector.sidebar.once("computedview-ready", function() {
aInspector.sidebar.select("computedview");
computedView = getComputedView(aInspector);
Services.obs.addObserver(testInlineStyle, "StyleInspector-populated", false);
});
inspector.selection.setNode(span);
inspector.once("inspector-updated", testInlineStyle);
}
function testInlineStyle()
{
Services.obs.removeObserver(testInlineStyle, "StyleInspector-populated");
info("expanding property");
expandProperty(0, function propertyExpanded() {
Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
if (aTopic != "domwindowopened") {
@ -137,12 +128,13 @@ function validateStyleEditorSheet(aEditor, aExpectedSheetIndex)
function expandProperty(aIndex, aCallback)
{
info("expanding property " + aIndex);
let contentDoc = computedView.styleDocument;
let expando = contentDoc.querySelectorAll(".expandable")[aIndex];
expando.click();
// We use executeSoon to give the property time to expand.
executeSoon(aCallback);
inspector.once("computed-view-property-expanded", aCallback);
}
function getLinkByIndex(aIndex)
@ -168,7 +160,7 @@ function test()
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
true);
doc = content.document;
waitForFocus(function () { openInspector(selectNode); }, content);
waitForFocus(function () { openComputedView(selectNode); }, content);
}, true);
content.location = DOCUMENT_URL;

Просмотреть файл

@ -32,33 +32,24 @@ function createDocument()
'</div>';
doc.title = "Computed view context menu test";
openInspector(selectNode)
openComputedView(selectNode)
}
function selectNode(aInspector)
function selectNode(aInspector, aComputedView)
{
computedView = aComputedView;
win = aInspector.sidebar.getWindowForTab("computedview");
let span = doc.querySelector("span");
ok(span, "captain, we have the span");
aInspector.selection.setNode(span);
aInspector.sidebar.once("computedview-ready", function() {
aInspector.sidebar.select("computedview");
computedView = getComputedView(aInspector);
win = aInspector.sidebar.getWindowForTab("computedview");
Services.obs.addObserver(runStyleInspectorTests,
"StyleInspector-populated", false);
});
aInspector.once("inspector-updated", runStyleInspectorTests);
}
function runStyleInspectorTests()
{
Services.obs.removeObserver(runStyleInspectorTests,
"StyleInspector-populated", false);
let contentDocument = computedView.styleDocument;
let prop = contentDocument.querySelector(".property-view");
ok(prop, "captain, we have the property-view node");

Просмотреть файл

@ -62,42 +62,38 @@ function highlightNode()
// Highlight a node.
let div = content.document.getElementsByTagName("div")[0];
inspector.selection.once("new-node", function() {
inspector.selection.setNode(div);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, div, "selection matches the div element");
testInlineStyle();
});
executeSoon(function() {
inspector.selection.setNode(div);
});
}).then(null, console.error);
}
function testInlineStyle()
{
executeSoon(function() {
info("clicking an inline style");
info("clicking an inline style");
Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
if (aTopic != "domwindowopened") {
return;
}
Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
if (aTopic != "domwindowopened") {
return;
}
win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("load", function windowLoad() {
win.removeEventListener("load", windowLoad);
let windowType = win.document.documentElement.getAttribute("windowtype");
is(windowType, "navigator:view-source", "view source window is open");
win.close();
Services.ww.unregisterNotification(onWindow);
executeSoon(() => {
testInlineStyleSheet();
});
win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("load", function windowLoad() {
win.removeEventListener("load", windowLoad);
let windowType = win.document.documentElement.getAttribute("windowtype");
is(windowType, "navigator:view-source", "view source window is open");
win.close();
Services.ww.unregisterNotification(onWindow);
executeSoon(() => {
testInlineStyleSheet();
});
});
let link = getLinkByIndex(0);
link.scrollIntoView();
link.click();
});
let link = getLinkByIndex(0);
link.scrollIntoView();
link.click();
}
function testInlineStyleSheet()

Просмотреть файл

@ -45,13 +45,12 @@ function highlightNode()
// Highlight a node.
let div = content.document.getElementsByTagName("div")[0];
inspector.selection.once("new-node", function() {
inspector.once("inspector-updated", function() {
is(inspector.selection.node, div, "selection matches the div element");
executeSoon(checkCopySelection);
});
executeSoon(function() {
inspector.selection.setNode(div);
});
inspector.selection.setNode(div);
}
function checkCopySelection()

Просмотреть файл

@ -3,20 +3,9 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
let doc;
let ruleDialog;
let ruleWindow;
let ruleView;
var gRuleViewChanged = false;
function ruleViewChanged()
{
gRuleViewChanged = true;
}
function expectChange()
{
ok(gRuleViewChanged, "Rule view should have fired a change event.");
gRuleViewChanged = false;
}
let inspector;
function startTest()
{
@ -32,18 +21,13 @@ function startTest()
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
let testElement = doc.getElementById("testid");
ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xhtml",
"cssruleviewtest",
"width=200,height=350");
ruleDialog.addEventListener("load", function onLoad(evt) {
ruleDialog.removeEventListener("load", onLoad, true);
let doc = ruleDialog.document;
ruleView = new CssRuleView(doc);
doc.documentElement.appendChild(ruleView.element);
ruleView.element.addEventListener("CssRuleViewChanged", ruleViewChanged, false);
ruleView.highlight(testElement);
waitForFocus(testCancelNew, ruleDialog);
}, true);
openRuleView((aInspector, aRuleView) => {
inspector = aInspector;
ruleView = aRuleView;
ruleWindow = aRuleView.doc.defaultView;
inspector.selection.setNode(testElement);
inspector.once("inspector-updated", testCancelNew);
});
}
function testCancelNew()
@ -55,7 +39,7 @@ function testCancelNew()
is(inplaceEditor(elementRuleEditor.newPropSpan), aEditor, "Next focused editor should be the new property editor.");
let input = aEditor.input;
waitForEditorBlur(aEditor, function () {
ok(!gRuleViewChanged, "Shouldn't get a change event after a cancel.");
ok(!elementRuleEditor.rule._applyingModifications, "Shouldn't have an outstanding request after a cancel.");
is(elementRuleEditor.rule.textProps.length, 0, "Should have canceled creating a new text property.");
ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
testCreateNew();
@ -64,7 +48,7 @@ function testCancelNew()
});
EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
{ },
ruleDialog);
ruleWindow);
}
function testCreateNew()
@ -77,26 +61,28 @@ function testCreateNew()
input.value = "background-color";
waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
expectChange();
is(elementRuleEditor.rule.textProps.length, 1, "Should have created a new text property.");
is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
let textProp = elementRuleEditor.rule.textProps[0];
is(aEditor, inplaceEditor(textProp.editor.valueSpan), "Should be editing the value span now.");
aEditor.input.value = "#XYZ";
waitForEditorBlur(aEditor, function() {
expectChange();
is(textProp.value, "#XYZ", "Text prop should have been changed.");
is(textProp.editor._validate(), false, "#XYZ should not be a valid entry");
testEditProperty();
});
aEditor.input.blur();
promiseDone(expectRuleChange(elementRuleEditor.rule).then(() => {
is(elementRuleEditor.rule.textProps.length, 1, "Should have created a new text property.");
is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
let textProp = elementRuleEditor.rule.textProps[0];
is(aEditor, inplaceEditor(textProp.editor.valueSpan), "Should be editing the value span now.");
aEditor.input.value = "#XYZ";
waitForEditorBlur(aEditor, function() {
promiseDone(expectRuleChange(elementRuleEditor.rule).then(() => {
is(textProp.value, "#XYZ", "Text prop should have been changed.");
is(textProp.editor._validate(), false, "#XYZ should not be a valid entry");
testEditProperty();
}));
});
aEditor.input.blur();
}));
});
EventUtils.synthesizeKey("VK_RETURN", {}, ruleDialog);
EventUtils.synthesizeKey("VK_RETURN", {}, ruleWindow);
});
EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
{ },
ruleDialog);
ruleWindow);
}
function testEditProperty()
@ -107,38 +93,37 @@ function testEditProperty()
is(inplaceEditor(propEditor.nameSpan), aEditor, "Next focused editor should be the name editor.");
let input = aEditor.input;
waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
expectChange();
input = aEditor.input;
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
input = aEditor.input;
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
waitForEditorBlur(aEditor, function() {
expectChange();
let value = idRuleEditor.rule.style.getPropertyValue("border-color");
is(value, "red", "border-color should have been set.");
is(propEditor._validate(), true, "red should be a valid entry");
finishTest();
});
waitForEditorBlur(aEditor, function() {
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
let value = idRuleEditor.rule.domRule._rawStyle().getPropertyValue("border-color");
is(value, "red", "border-color should have been set.");
is(propEditor._validate(), true, "red should be a valid entry");
finishTest();
}));
});
for each (let ch in "red;") {
EventUtils.sendChar(ch, ruleDialog);
}
for (let ch of "red;") {
EventUtils.sendChar(ch, ruleWindow);
}
}));
});
for each (let ch in "border-color:") {
EventUtils.sendChar(ch, ruleDialog);
for (let ch of "border-color:") {
EventUtils.sendChar(ch, ruleWindow);
}
});
EventUtils.synthesizeMouse(propEditor.nameSpan, 32, 1,
{ },
ruleDialog);
ruleWindow);
}
function finishTest()
{
ruleView.element.removeEventListener("CssRuleViewChanged", ruleViewChanged, false);
ruleView.clear();
ruleDialog.close();
ruleDialog = ruleView = null;
inspector = ruleWindow = ruleView = null;
doc = null;
gBrowser.removeCurrentTab();
finish();

Просмотреть файл

@ -9,18 +9,12 @@ let doc;
let inspector;
let stylePanel;
function openRuleView()
function selectNode(aInspector, aRuleView)
{
var target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
inspector = toolbox.getCurrentPanel();
inspector.sidebar.select("ruleview");
// Highlight a node.
let node = content.document.getElementsByTagName("h1")[0];
inspector.sidebar.once("ruleview-ready", testFocus);
});
inspector = aInspector;
let node = content.document.getElementsByTagName("h1")[0];
inspector.selection.setNode(node);
inspector.once("inspector-updated", testFocus);
}
function testFocus()
@ -64,7 +58,7 @@ function test()
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
doc.title = "Rule View Test";
waitForFocus(openRuleView, content);
waitForFocus(() => openRuleView(selectNode), content);
}, true);
content.location = "data:text/html,<h1>Some header text</h1>";

Просмотреть файл

@ -4,8 +4,16 @@
let doc;
function simpleInherit()
let inspector;
let view;
let {ELEMENT_STYLE} = devtools.require("devtools/server/actors/styles");
function simpleInherit(aInspector, aRuleView)
{
inspector = aInspector;
view = aRuleView;
let style = '' +
'#test2 {' +
' background-color: green;' +
@ -15,23 +23,26 @@ function simpleInherit()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="test2"><div id="test1">Styled Node</div></div>';
let elementStyle = new _ElementStyle(doc.getElementById("test1"));
inspector.selection.setNode(doc.getElementById("test1"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
is(elementStyle.rules.length, 2, "Should have 2 rules.");
is(elementStyle.rules.length, 2, "Should have 2 rules.");
let elementRule = elementStyle.rules[0];
ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
let elementRule = elementStyle.rules[0];
ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
let inheritRule = elementStyle.rules[1];
is(inheritRule.selectorText, "#test2", "Inherited rule should be the one that includes inheritable properties.");
ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
is(inheritRule.textProps.length, 1, "Should only display one inherited style");
let inheritProp = inheritRule.textProps[0];
is(inheritProp.name, "color", "color should have been inherited.");
let inheritRule = elementStyle.rules[1];
is(inheritRule.selectorText, "#test2", "Inherited rule should be the one that includes inheritable properties.");
ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
is(inheritRule.textProps.length, 1, "Should only display one inherited style");
let inheritProp = inheritRule.textProps[0];
is(inheritProp.name, "color", "color should have been inherited.");
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
emptyInherit();
emptyInherit();
}).then(null, console.error);
}
function emptyInherit()
@ -45,37 +56,43 @@ function emptyInherit()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="test2"><div id="test1">Styled Node</div></div>';
let elementStyle = new _ElementStyle(doc.getElementById("test1"));
inspector.selection.setNode(doc.getElementById("test1"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
is(elementStyle.rules.length, 1, "Should have 1 rule.");
is(elementStyle.rules.length, 1, "Should have 1 rule.");
let elementRule = elementStyle.rules[0];
ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
let elementRule = elementStyle.rules[0];
ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
elementStyleInherit();
elementStyleInherit();
}).then(null, console.error);
}
function elementStyleInherit()
{
doc.body.innerHTML = '<div id="test2" style="color: red"><div id="test1">Styled Node</div></div>';
let elementStyle = new _ElementStyle(doc.getElementById("test1"));
inspector.selection.setNode(doc.getElementById("test1"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
is(elementStyle.rules.length, 2, "Should have 2 rules.");
is(elementStyle.rules.length, 2, "Should have 2 rules.");
let elementRule = elementStyle.rules[0];
ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
let elementRule = elementStyle.rules[0];
ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
let inheritRule = elementStyle.rules[1];
ok(!inheritRule.domRule, "Inherited rule should be an element style, not a rule.");
ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
is(inheritRule.textProps.length, 1, "Should only display one inherited style");
let inheritProp = inheritRule.textProps[0];
is(inheritProp.name, "color", "color should have been inherited.");
let inheritRule = elementStyle.rules[1];
is(inheritRule.domRule.type, ELEMENT_STYLE, "Inherited rule should be an element style, not a rule.");
ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
is(inheritRule.textProps.length, 1, "Should only display one inherited style");
let inheritProp = inheritRule.textProps[0];
is(inheritProp.name, "color", "color should have been inherited.");
finishTest();
finishTest();
}).then(null, console.error);
}
function finishTest()
@ -92,7 +109,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
waitForFocus(simpleInherit, content);
waitForFocus(() => openRuleView(simpleInherit), content);
}, true);
content.location = "data:text/html,basic style inspector tests";

Просмотреть файл

@ -4,47 +4,68 @@
let doc;
function simpleOverride()
function simpleOverride(aInspector, aRuleView)
{
doc.body.innerHTML = '<div id="testid">Styled Node</div>';
let element = doc.getElementById("testid");
let elementStyle = new _ElementStyle(element);
let elementRule = elementStyle.rules[0];
let firstProp = elementRule.createProperty("background-color", "green", "");
let secondProp = elementRule.createProperty("background-color", "blue", "");
is(elementRule.textProps[0], firstProp, "Rules should be in addition order.");
is(elementRule.textProps[1], secondProp, "Rules should be in addition order.");
aInspector.selection.setNode(element);
aInspector.once("inspector-updated", () => {
let elementStyle = aRuleView._elementStyle;
is(element.style.getPropertyValue("background-color"), "blue", "Second property should have been used.");
let elementRule = elementStyle.rules[0];
let firstProp = elementRule.createProperty("background-color", "green", "");
let secondProp = elementRule.createProperty("background-color", "blue", "");
is(elementRule.textProps[0], firstProp, "Rules should be in addition order.");
is(elementRule.textProps[1], secondProp, "Rules should be in addition order.");
secondProp.remove();
is(element.style.getPropertyValue("background-color"), "green", "After deleting second property, first should be used.");
promiseDone(elementRule._applyingModifications.then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Second property should have been used.");
secondProp = elementRule.createProperty("background-color", "blue", "");
is(element.style.getPropertyValue("background-color"), "blue", "New property should be used.");
secondProp.remove();
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "green", "After deleting second property, first should be used.");
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
secondProp = elementRule.createProperty("background-color", "blue", "");
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "New property should be used.");
secondProp.setEnabled(false);
is(element.style.getPropertyValue("background-color"), "green", "After disabling second property, first value should be used");
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
firstProp.setEnabled(false);
is(element.style.getPropertyValue("background-color"), "", "After disabling both properties, value should be empty.");
secondProp.setEnabled(false);
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "green", "After disabling second property, first value should be used");
secondProp.setEnabled(true);
is(element.style.getPropertyValue("background-color"), "blue", "Value should be set correctly after re-enabling");
firstProp.setEnabled(false);
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "", "After disabling both properties, value should be empty.");
firstProp.setEnabled(true);
is(element.style.getPropertyValue("background-color"), "blue", "Re-enabling an earlier property shouldn't make it override a later property.");
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
secondProp.setEnabled(true);
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Value should be set correctly after re-enabling");
firstProp.setValue("purple", "");
is(element.style.getPropertyValue("background-color"), "blue", "Modifying an earlier property shouldn't override a later property.");
firstProp.setEnabled(true);
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Re-enabling an earlier property shouldn't make it override a later property.");
is(elementRule.textProps[0], firstProp, "Rules shouldn't have switched places.");
is(elementRule.textProps[1], secondProp, "Rules shouldn't have switched places.");
finishTest();
firstProp.setValue("purple", "");
return elementRule._applyingModifications;
}).then(() => {
is(element.style.getPropertyValue("background-color"), "blue", "Modifying an earlier property shouldn't override a later property.");
finishTest();
}));
});
}
function finishTest()
@ -61,7 +82,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
waitForFocus(simpleOverride, content);
waitForFocus(() => openRuleView(simpleOverride), content);
}, true);
content.location = "data:text/html,basic style inspector tests";

Просмотреть файл

@ -3,9 +3,13 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
let doc;
let inspector;
let view;
function simpleOverride()
function simpleOverride(aInspector, aRuleView)
{
inspector = aInspector;
view = aRuleView;
let style = '' +
'#testid {' +
' background-color: blue;' +
@ -17,31 +21,34 @@ function simpleOverride()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
let elementStyle = new _ElementStyle(doc.getElementById("testid"));
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
is(idProp.name, "background-color", "First ID prop should be background-color");
ok(!idProp.overridden, "ID prop should not be overridden.");
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
is(idProp.name, "background-color", "First ID prop should be background-color");
ok(!idProp.overridden, "ID prop should not be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
is(classProp.name, "background-color", "First class prop should be background-color");
ok(classProp.overridden, "Class property should be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
is(classProp.name, "background-color", "First class prop should be background-color");
ok(classProp.overridden, "Class property should be overridden.");
// Override background-color by changing the element style.
let elementRule = elementStyle.rules[0];
elementRule.createProperty("background-color", "purple", "");
let elementProp = elementRule.textProps[0];
is(classProp.name, "background-color", "First element prop should now be background-color");
// Override background-color by changing the element style.
let elementRule = elementStyle.rules[0];
elementRule.createProperty("background-color", "purple", "");
promiseDone(elementRule._applyingModifications.then(() => {
let elementProp = elementRule.textProps[0];
is(classProp.name, "background-color", "First element prop should now be background-color");
ok(!elementProp.overridden, "Element style property should not be overridden");
ok(idProp.overridden, "ID property should be overridden");
ok(classProp.overridden, "Class property should be overridden");
ok(!elementProp.overridden, "Element style property should not be overridden");
ok(idProp.overridden, "ID property should be overridden");
ok(classProp.overridden, "Class property should be overridden");
styleNode.parentNode.removeChild(styleNode);
partialOverride();
styleNode.parentNode.removeChild(styleNode);
partialOverride();
}));
});
}
function partialOverride()
@ -59,22 +66,25 @@ function partialOverride()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
let elementStyle = new _ElementStyle(doc.getElementById("testid"));
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used.");
for each (let computed in classProp.computed) {
if (computed.name.indexOf("margin-left") == 0) {
ok(computed.overridden, "margin-left props should be overridden.");
} else {
ok(!computed.overridden, "Non-margin-left props should not be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used.");
for (let computed of classProp.computed) {
if (computed.name.indexOf("margin-left") == 0) {
ok(computed.overridden, "margin-left props should be overridden.");
} else {
ok(!computed.overridden, "Non-margin-left props should not be overridden.");
}
}
}
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
importantOverride();
importantOverride();
});
}
function importantOverride()
@ -91,24 +101,29 @@ function importantOverride()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
let elementStyle = new _ElementStyle(doc.getElementById("testid"));
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
ok(idProp.overridden, "Not-important rule should be overridden.");
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
ok(idProp.overridden, "Not-important rule should be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Important rule should not be overridden.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Important rule should not be overridden.");
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
let elementRule = elementStyle.rules[0];
let elementProp = elementRule.createProperty("background-color", "purple", "important");
ok(classProp.overridden, "New important prop should override class property.");
ok(!elementProp.overridden, "New important prop should not be overriden.");
let elementRule = elementStyle.rules[0];
let elementProp = elementRule.createProperty("background-color", "purple", "important");
promiseDone(elementRule._applyingModifications.then(() => {
ok(classProp.overridden, "New important prop should override class property.");
ok(!elementProp.overridden, "New important prop should not be overriden.");
disableOverride();
disableOverride();
}));
});
}
function disableOverride()
@ -123,19 +138,23 @@ function disableOverride()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
let elementStyle = new _ElementStyle(doc.getElementById("testid"));
inspector.selection.setNode(doc.getElementById("testid"));
inspector.once("inspector-updated", () => {
let elementStyle = view._elementStyle;
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
idProp.setEnabled(false);
let idRule = elementStyle.rules[1];
let idProp = idRule.textProps[0];
idProp.setEnabled(false);
promiseDone(idRule._applyingModifications.then(() => {
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled.");
let classRule = elementStyle.rules[2];
let classProp = classRule.textProps[0];
ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled.");
styleNode.parentNode.removeChild(styleNode);
styleNode.parentNode.removeChild(styleNode);
finishTest();
finishTest();
}));
});
}
function finishTest()
@ -152,7 +171,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
waitForFocus(simpleOverride, content);
waitForFocus(() => openRuleView(simpleOverride), content);
}, true);
content.location = "data:text/html,basic style inspector tests";

Просмотреть файл

@ -3,23 +3,15 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
let doc;
let ruleDialog;
let inspector;
let ruleWindow;
let ruleView;
var gRuleViewChanged = false;
function ruleViewChanged()
{
gRuleViewChanged = true;
}
function expectChange()
{
ok(gRuleViewChanged, "Rule view should have fired a change event.");
gRuleViewChanged = false;
}
function startTest()
function startTest(aInspector, aRuleView)
{
inspector = aInspector;
ruleWindow = aRuleView.doc.defaultView;
ruleView = aRuleView;
let style = '' +
'#testid {' +
' background-color: blue;' +
@ -30,30 +22,24 @@ function startTest()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
let testElement = doc.getElementById("testid");
ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xhtml",
"cssruleviewtest",
"width=200,height=350");
ruleDialog.addEventListener("load", function onLoad(evt) {
ruleDialog.removeEventListener("load", onLoad);
let doc = ruleDialog.document;
ruleView = new CssRuleView(doc);
doc.documentElement.appendChild(ruleView.element);
ruleView.element.addEventListener("CssRuleViewChanged", ruleViewChanged, false);
is(ruleView.element.querySelectorAll("#noResults").length, 1, "Has a no-results element.");
ruleView.highlight(testElement);
inspector.selection.setNode(testElement);
inspector.once("inspector-updated", () => {
is(ruleView.element.querySelectorAll("#noResults").length, 0, "After a highlight, no longer has a no-results element.");
ruleView.highlight(null);
is(ruleView.element.querySelectorAll("#noResults").length, 1, "After highlighting null, has a no-results element again.");
ruleView.highlight(testElement);
inspector.selection.setNode(null);
inspector.once("inspector-updated", () => {
is(ruleView.element.querySelectorAll("#noResults").length, 1, "After highlighting null, has a no-results element again.");
inspector.selection.setNode(testElement)
inspector.once("inspector-updated", () => {
let classEditor = ruleView.element.children[2]._ruleEditor;
is(classEditor.selectorText.querySelector(".ruleview-selector-matched").textContent, ".testclass", ".textclass should be matched.");
is(classEditor.selectorText.querySelector(".ruleview-selector-unmatched").textContent, ".unmatched", ".unmatched should not be matched.");
let classEditor = ruleView.element.children[2]._ruleEditor;
is(classEditor.selectorText.querySelector(".ruleview-selector-matched").textContent, ".testclass", ".textclass should be matched.");
is(classEditor.selectorText.querySelector(".ruleview-selector-unmatched").textContent, ".unmatched", ".unmatched should not be matched.");
waitForFocus(testCancelNew, ruleDialog);
}, true);
testCancelNew();
});
});
});
}
function testCancelNew()
@ -66,7 +52,7 @@ function testCancelNew()
is(inplaceEditor(elementRuleEditor.newPropSpan), aEditor, "Next focused editor should be the new property editor.");
let input = aEditor.input;
waitForEditorBlur(aEditor, function () {
ok(!gRuleViewChanged, "Shouldn't get a change event after a cancel.");
ok(!elementRuleEditor.rule._applyingModifications, "Shouldn't have an outstanding modification request after a cancel.");
is(elementRuleEditor.rule.textProps.length, 0, "Should have canceled creating a new text property.");
ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
testCreateNew();
@ -76,7 +62,7 @@ function testCancelNew()
EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
{ },
ruleDialog);
ruleWindow);
}
function testCreateNew()
@ -91,33 +77,35 @@ function testCreateNew()
ok(input.selectionStart === 0 && input.selectionEnd === input.value.length, "Editor contents are selected.");
// Try clicking on the editor's input again, shouldn't cause trouble (see bug 761665).
EventUtils.synthesizeMouse(input, 1, 1, { }, ruleDialog);
EventUtils.synthesizeMouse(input, 1, 1, { }, ruleWindow);
input.select();
input.value = "background-color";
waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
expectChange();
is(elementRuleEditor.rule.textProps.length, 1, "Should have created a new text property.");
is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
let textProp = elementRuleEditor.rule.textProps[0];
is(aEditor, inplaceEditor(textProp.editor.valueSpan), "Should be editing the value span now.");
promiseDone(expectRuleChange(elementRuleEditor.rule).then(() => {
is(elementRuleEditor.rule.textProps.length, 1, "Should have created a new text property.");
is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
let textProp = elementRuleEditor.rule.textProps[0];
is(aEditor, inplaceEditor(textProp.editor.valueSpan), "Should be editing the value span now.");
aEditor.input.value = "purple";
waitForEditorBlur(aEditor, function() {
expectChange();
is(textProp.value, "purple", "Text prop should have been changed.");
testEditProperty();
});
aEditor.input.value = "purple";
waitForEditorBlur(aEditor, function() {
expectRuleChange(elementRuleEditor.rule).then(() => {
is(textProp.value, "purple", "Text prop should have been changed.");
testEditProperty();
});
});
aEditor.input.blur();
aEditor.input.blur();
}));
});
EventUtils.sendKey("return", ruleDialog);
EventUtils.sendKey("return", ruleWindow);
});
EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
{ },
ruleDialog);
ruleWindow);
}
function testEditProperty()
@ -133,48 +121,50 @@ function testEditProperty()
ok(input.selectionStart === 0 && input.selectionEnd === input.value.length, "Editor contents are selected.");
// Try clicking on the editor's input again, shouldn't cause trouble (see bug 761665).
EventUtils.synthesizeMouse(input, 1, 1, { }, ruleDialog);
EventUtils.synthesizeMouse(input, 1, 1, { }, ruleWindow);
input.select();
waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
expectChange();
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
input = aEditor.input;
input = aEditor.input;
ok(input.selectionStart === 0 && input.selectionEnd === input.value.length, "Editor contents are selected.");
ok(input.selectionStart === 0 && input.selectionEnd === input.value.length, "Editor contents are selected.");
// Try clicking on the editor's input again, shouldn't cause trouble (see bug 761665).
EventUtils.synthesizeMouse(input, 1, 1, { }, ruleDialog);
input.select();
// Try clicking on the editor's input again, shouldn't cause trouble (see bug 761665).
EventUtils.synthesizeMouse(input, 1, 1, { }, ruleWindow);
input.select();
waitForEditorBlur(aEditor, function() {
expectChange();
is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red",
"border-color should have been set.");
waitForEditorBlur(aEditor, function() {
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {;
is(idRuleEditor.rule.style._rawStyle().getPropertyValue("border-color"), "red",
"border-color should have been set.");
let props = ruleView.element.querySelectorAll(".ruleview-property");
for (let i = 0; i < props.length; i++) {
is(props[i].hasAttribute("dirty"), i <= 1,
"props[" + i + "] marked dirty as appropriate");
let props = ruleView.element.querySelectorAll(".ruleview-property");
for (let i = 0; i < props.length; i++) {
is(props[i].hasAttribute("dirty"), i <= 1,
"props[" + i + "] marked dirty as appropriate");
}
testDisableProperty();
}));
});
for (let ch of "red;") {
EventUtils.sendChar(ch, ruleWindow);
is(propEditor.warning.hidden, ch == "d" || ch == ";",
"warning triangle is hidden or shown as appropriate");
}
testDisableProperty();
});
for each (let ch in "red;") {
EventUtils.sendChar(ch, ruleDialog);
is(propEditor.warning.hidden, ch == "d" || ch == ";",
"warning triangle is hidden or shown as appropriate");
}
}));
});
for each (let ch in "border-color:") {
EventUtils.sendChar(ch, ruleDialog);
for (let ch of "border-color:") {
EventUtils.sendChar(ch, ruleWindow);
}
});
EventUtils.synthesizeMouse(propEditor.nameSpan, 32, 1,
{ },
ruleDialog);
ruleWindow);
}
function testDisableProperty()
@ -183,22 +173,22 @@ function testDisableProperty()
let propEditor = idRuleEditor.rule.textProps[0].editor;
propEditor.enable.click();
is(idRuleEditor.rule.style.getPropertyValue("border-color"), "", "Border-color should have been unset.");
expectChange();
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
is(idRuleEditor.rule.style._rawStyle().getPropertyValue("border-color"), "", "Border-color should have been unset.");
propEditor.enable.click();
is(idRuleEditor.rule.style.getPropertyValue("border-color"), "red",
"Border-color should have been reset.");
expectChange();
propEditor.enable.click();
return expectRuleChange(idRuleEditor.rule);
}).then(() => {
is(idRuleEditor.rule.style._rawStyle().getPropertyValue("border-color"), "red",
"Border-color should have been reset.");
finishTest();
finishTest();
}));
}
function finishTest()
{
ruleView.clear();
ruleDialog.close();
ruleDialog = ruleView = null;
inspector = ruleWindow = ruleView = null;
doc = null;
gBrowser.removeCurrentTab();
finish();
@ -211,7 +201,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
waitForFocus(startTest, content);
waitForFocus(() => openRuleView(startTest), content);
}, true);
content.location = "data:text/html,basic style inspector tests";

Просмотреть файл

@ -3,12 +3,15 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
let doc;
let ruleDialog;
let inspector;
let ruleView;
let testElement;
function startTest()
function startTest(aInspector, aRuleView)
{
inspector = aInspector;
ruleView = aRuleView;
let style = '' +
'#testid {' +
' background-color: blue;' +
@ -19,21 +22,15 @@ function startTest()
let styleNode = addStyle(doc, style);
doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
testElement = doc.getElementById("testid");
let elementStyle = 'margin-top: 1px; padding-top: 5px;'
testElement.setAttribute("style", elementStyle);
ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xhtml",
"cssruleviewtest",
"width=200,height=350");
ruleDialog.addEventListener("load", function onLoad(evt) {
ruleDialog.removeEventListener("load", onLoad);
let doc = ruleDialog.document;
ruleView = new CssRuleView(doc);
doc.documentElement.appendChild(ruleView.element);
ruleView.highlight(testElement);
waitForFocus(testRuleChanges, ruleDialog);
inspector.selection.setNode(testElement);
inspector.once("inspector-updated", () => {
testRuleChanges();
}, true);
}
@ -47,24 +44,24 @@ function testRuleChanges()
// Change the id and refresh.
testElement.setAttribute("id", "differentid");
ruleView.nodeChanged();
promiseDone(ruleView.nodeChanged().then(() => {
let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
is(selectors.length, 2, "Two rules visible.");
is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
is(selectors[1].textContent.indexOf(".testclass"), 0, "Second item is class rule.");
let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
is(selectors.length, 2, "Three rules visible.");
is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
is(selectors[1].textContent.indexOf(".testclass"), 0, "Second item is class rule.");
testElement.setAttribute("id", "testid");
return ruleView.nodeChanged();
}).then(() => {
// Put the id back.
let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
is(selectors.length, 3, "Three rules visible.");
is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
is(selectors[1].textContent.indexOf("#testid"), 0, "Second item is id rule.");
is(selectors[2].textContent.indexOf(".testclass"), 0, "Third item is class rule.");
testElement.setAttribute("id", "testid");
ruleView.nodeChanged();
// Put the id back.
let selectors = ruleView.doc.querySelectorAll(".ruleview-selector");
is(selectors.length, 3, "Three rules visible.");
is(selectors[0].textContent.indexOf("element"), 0, "First item is inline style.");
is(selectors[1].textContent.indexOf("#testid"), 0, "Second item is id rule.");
is(selectors[2].textContent.indexOf(".testclass"), 0, "Third item is class rule.");
testPropertyChanges();
testPropertyChanges();
}));
}
function validateTextProp(aProp, aEnabled, aName, aValue, aDesc)
@ -81,59 +78,64 @@ function validateTextProp(aProp, aEnabled, aName, aValue, aDesc)
function testPropertyChanges()
{
// Add a second margin-top value, just to make things interesting.
let rule = ruleView._elementStyle.rules[0];
let ruleEditor = ruleView._elementStyle.rules[0].editor;
ruleEditor.addProperty("margin-top", "5px", "");
promiseDone(expectRuleChange(rule).then(() => {
// Set the element style back to a 1px margin-top.
testElement.setAttribute("style", "margin-top: 1px; padding-top: 5px");
return ruleView.nodeChanged();
}).then(() => {
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[0], true, "margin-top", "1px", "First margin property re-enabled");
validateTextProp(rule.textProps[2], false, "margin-top", "5px", "Second margin property disabled");
let rule = ruleView._elementStyle.rules[0];
// Now set it back to 5px, the 5px value should be re-enabled.
testElement.setAttribute("style", "margin-top: 5px; padding-top: 5px;");
return ruleView.nodeChanged();
// Set the element style back to a 1px margin-top.
testElement.setAttribute("style", "margin-top: 1px; padding-top: 5px");
ruleView.nodeChanged();
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[0], true, "margin-top", "1px", "First margin property re-enabled");
validateTextProp(rule.textProps[2], false, "margin-top", "5px", "Second margin property disabled");
}).then(() => {
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
validateTextProp(rule.textProps[2], true, "margin-top", "5px", "Second margin property disabled");
// Now set it back to 5px, the 5px value should be re-enabled.
testElement.setAttribute("style", "margin-top: 5px; padding-top: 5px;");
ruleView.nodeChanged();
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
validateTextProp(rule.textProps[2], true, "margin-top", "5px", "Second margin property disabled");
// Set the margin property to a value that doesn't exist in the editor.
// Should reuse the currently-enabled element (the second one.)
testElement.setAttribute("style", "margin-top: 15px; padding-top: 5px;");
return ruleView.nodeChanged();
}).then(() => {
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
validateTextProp(rule.textProps[2], true, "margin-top", "15px", "Second margin property disabled");
// Set the margin property to a value that doesn't exist in the editor.
// Should reuse the currently-enabled element (the second one.)
testElement.setAttribute("style", "margin-top: 15px; padding-top: 5px;");
ruleView.nodeChanged();
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[0], false, "margin-top", "1px", "First margin property re-enabled");
validateTextProp(rule.textProps[2], true, "margin-top", "15px", "Second margin property disabled");
// Remove the padding-top attribute. Should disable the padding property but not remove it.
testElement.setAttribute("style", "margin-top: 5px;");
return ruleView.nodeChanged();
}).then(() => {
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[1], false, "padding-top", "5px", "Padding property disabled");
// Remove the padding-top attribute. Should disable the padding property but not remove it.
testElement.setAttribute("style", "margin-top: 5px;");
ruleView.nodeChanged();
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[1], false, "padding-top", "5px", "Padding property disabled");
// Put the padding-top attribute back in, should re-enable the padding property.
testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px");
return ruleView.nodeChanged();
}).then(() => {
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[1], true, "padding-top", "25px", "Padding property enabled");
// Put the padding-top attribute back in, should re-enable the padding property.
testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px");
ruleView.nodeChanged();
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3, "Correct number of properties");
validateTextProp(rule.textProps[1], true, "padding-top", "25px", "Padding property enabled");
// Add an entirely new property.
testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px; padding-left: 20px;");
return ruleView.nodeChanged();
}).then(() => {
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4, "Added a property");
validateTextProp(rule.textProps[3], true, "padding-left", "20px", "Padding property enabled");
// Add an entirely new property.
testElement.setAttribute("style", "margin-top: 5px; padding-top: 25px; padding-left: 20px;");
ruleView.nodeChanged();
is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4, "Added a property");
validateTextProp(rule.textProps[3], true, "padding-left", "20px", "Padding property enabled");
finishTest();
finishTest();
}));
}
function finishTest()
{
ruleView.clear();
ruleDialog.close();
ruleDialog = ruleView = null;
inspector = ruleView = null;
doc = null;
gBrowser.removeCurrentTab();
finish();
@ -146,7 +148,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
waitForFocus(startTest, content);
waitForFocus(() => openRuleView(startTest), content);
}, true);
content.location = "data:text/html,basic style inspector tests";

Просмотреть файл

@ -1,89 +0,0 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the style inspector works properly
let doc;
let inspector;
let computedView;
function createDocument()
{
doc.body.innerHTML = '<style type="text/css"> ' +
'span { font-variant: small-caps; color: #000000; } ' +
'.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
'<h1>Some header text</h1>\n' +
'<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
'<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
'solely to provide some things to <span style="color: yellow">' +
'highlight</span> and <span style="font-weight: bold">count</span> ' +
'style list-items in the box at right. If you are reading this, ' +
'you should go do something else instead. Maybe read a book. Or better ' +
'yet, write some test-cases for another bit of code. ' +
'<span style="font-style: italic">Maybe more inspector test-cases!</span></p>\n' +
'<p id="closing">end transmission</p>\n' +
'<p>Inspect using inspectstyle(document.querySelectorAll("span")[0])</p>' +
'</div>';
doc.title = "Style Inspector Test";
openInspector(openComputedView);
}
function openComputedView(aInspector)
{
inspector = aInspector;
inspector.sidebar.once("computedview-ready", function() {
computedView = getComputedView(inspector);
inspector.sidebar.select("computedview");
runStyleInspectorTests();
});
}
function runStyleInspectorTests()
{
var spans = doc.querySelectorAll("span");
ok(spans, "captain, we have the spans");
for (var i = 0, numSpans = spans.length; i < numSpans; i++) {
inspector.selection.setNode(spans[i]);
is(spans[i], computedView.viewedElement,
"style inspector node matches the selected node");
is(computedView.viewedElement, computedView.cssLogic.viewedElement,
"cssLogic node matches the cssHtmlTree node");
}
SI_CheckProperty();
finishUp();
}
function SI_CheckProperty()
{
let cssLogic = computedView.cssLogic;
let propertyInfo = cssLogic.getPropertyInfo("color");
ok(propertyInfo.matchedRuleCount > 0, "color property has matching rules");
}
function finishUp()
{
doc = computedView = null;
gBrowser.removeCurrentTab();
finish();
}
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
doc = content.document;
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,basic style inspector tests";
}

Просмотреть файл

@ -16,24 +16,14 @@ function createDocument()
'</div>';
doc.title = "Style Inspector Search Filter Test";
openInspector(openComputedView);
openComputedView(runStyleInspectorTests);
}
function openComputedView(aInspector)
function runStyleInspectorTests(aInspector, aComputedView)
{
inspector = aInspector;
computedView = aComputedView;
inspector.sidebar.once("computedview-ready", function() {
inspector.sidebar.select("computedview");
computedView = getComputedView(inspector);
runStyleInspectorTests();
});
}
function runStyleInspectorTests()
{
Services.obs.addObserver(SI_toggleDefaultStyles, "StyleInspector-populated", false);
SI_inspectNode();
}
@ -43,32 +33,28 @@ function SI_inspectNode()
ok(span, "captain, we have the matches span");
inspector.selection.setNode(span);
is(span, computedView.viewedElement,
"style inspector node matches the selected node");
is(computedView.viewedElement, computedView.cssLogic.viewedElement,
"cssLogic node matches the cssHtmlTree node");
inspector.once("inspector-updated", () => {
is(span, computedView.viewedElement.rawNode(),
"style inspector node matches the selected node");
SI_toggleDefaultStyles();
}).then(null, (err) => console.error(err));
}
function SI_toggleDefaultStyles()
{
Services.obs.removeObserver(SI_toggleDefaultStyles, "StyleInspector-populated");
info("checking \"Browser styles\" checkbox");
let doc = computedView.styleDocument;
let checkbox = doc.querySelector(".includebrowserstyles");
Services.obs.addObserver(SI_AddFilterText, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", SI_AddFilterText);
checkbox.click();
}
function SI_AddFilterText()
{
Services.obs.removeObserver(SI_AddFilterText, "StyleInspector-populated");
let doc = computedView.styleDocument;
let searchbar = doc.querySelector(".devtools-searchinput");
Services.obs.addObserver(SI_checkFilter, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", SI_checkFilter);
info("setting filter text to \"color\"");
searchbar.focus();
@ -82,7 +68,6 @@ function SI_AddFilterText()
function SI_checkFilter()
{
Services.obs.removeObserver(SI_checkFilter, "StyleInspector-populated");
let propertyViews = computedView.propertyViews;
info("check that the correct properties are visible");

Просмотреть файл

@ -16,43 +16,27 @@ function createDocument()
'</div>';
doc.title = "Style Inspector Default Styles Test";
openInspector(openComputedView);
openComputedView(SI_inspectNode);
}
function openComputedView(aInspector)
function SI_inspectNode(aInspector, aComputedView)
{
inspector = aInspector;
computedView = aComputedView;
inspector.sidebar.once("computedview-ready", function() {
inspector.sidebar.select("computedview");
computedView = getComputedView(inspector);
runStyleInspectorTests();
});
}
function runStyleInspectorTests()
{
Services.obs.addObserver(SI_check, "StyleInspector-populated", false);
SI_inspectNode();
}
function SI_inspectNode()
{
let span = doc.querySelector("#matches");
ok(span, "captain, we have the matches span");
inspector.selection.setNode(span);
is(span, computedView.viewedElement,
"style inspector node matches the selected node");
is(computedView.viewedElement, computedView.cssLogic.viewedElement,
"cssLogic node matches the cssHtmlTree node");
inspector.once("inspector-updated", () => {
is(span, computedView.viewedElement.rawNode(),
"style inspector node matches the selected node");
SI_check();
});
}
function SI_check()
{
Services.obs.removeObserver(SI_check, "StyleInspector-populated");
is(propertyVisible("color"), true,
"span #matches color property is visible");
is(propertyVisible("background-color"), false,
@ -66,14 +50,13 @@ function SI_toggleDefaultStyles()
// Click on the checkbox.
let doc = computedView.styleDocument;
let checkbox = doc.querySelector(".includebrowserstyles");
Services.obs.addObserver(SI_checkDefaultStyles, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", SI_checkDefaultStyles);
checkbox.click();
}
function SI_checkDefaultStyles()
{
Services.obs.removeObserver(SI_checkDefaultStyles, "StyleInspector-populated");
// Check that the default styles are now applied.
is(propertyVisible("color"), true,
"span color property is visible");

Просмотреть файл

@ -15,22 +15,11 @@ const TEST_URI = BASE_URL +
const TEST_IMAGE = BASE_URL + "test-image.png";
const BASE_64_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
function createDocument()
{
doc.title = "Style Inspector URL Clickable test";
openInspector(function(aInspector) {
inspector = aInspector;
executeSoon(selectNode);
});
}
function selectNode(aInspector)
function selectNode(aInspector, aRuleView)
{
inspector = aInspector;
let sidebar = inspector.sidebar;
let iframe = sidebar._tabbox.querySelector(".iframe-ruleview");
let contentDoc = iframe.contentWindow.document;
let contentDoc = aRuleView.doc;
let relative = doc.querySelector(".relative");
let absolute = doc.querySelector(".absolute");
@ -45,35 +34,44 @@ function selectNode(aInspector)
ok(noimage, "captain, we have the noimage div");
inspector.selection.setNode(relative);
is(inspector.selection.node, relative, "selection matches the relative element");
let relativeLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (relativeLink, "Link exists for relative node");
ok (relativeLink.getAttribute("href"), TEST_IMAGE);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, relative, "selection matches the relative element");
let relativeLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (relativeLink, "Link exists for relative node");
ok (relativeLink.getAttribute("href"), TEST_IMAGE);
inspector.selection.setNode(absolute);
is(inspector.selection.node, absolute, "selection matches the absolute element");
let absoluteLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (absoluteLink, "Link exists for absolute node");
ok (absoluteLink.getAttribute("href"), TEST_IMAGE);
inspector.selection.setNode(absolute);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, absolute, "selection matches the absolute element");
let absoluteLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (absoluteLink, "Link exists for absolute node");
ok (absoluteLink.getAttribute("href"), TEST_IMAGE);
inspector.selection.setNode(inline);
is(inspector.selection.node, inline, "selection matches the inline element");
let inlineLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (inlineLink, "Link exists for inline node");
ok (inlineLink.getAttribute("href"), TEST_IMAGE);
inspector.selection.setNode(inline);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, inline, "selection matches the inline element");
let inlineLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (inlineLink, "Link exists for inline node");
ok (inlineLink.getAttribute("href"), TEST_IMAGE);
inspector.selection.setNode(base64);
is(inspector.selection.node, base64, "selection matches the base64 element");
let base64Link = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (base64Link, "Link exists for base64 node");
ok (base64Link.getAttribute("href"), BASE_64_URL);
inspector.selection.setNode(base64);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, base64, "selection matches the base64 element");
let base64Link = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (base64Link, "Link exists for base64 node");
ok (base64Link.getAttribute("href"), BASE_64_URL);
inspector.selection.setNode(noimage);
is(inspector.selection.node, noimage, "selection matches the inline element");
let noimageLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (!noimageLink, "There is no link for the node with no background image");
finishUp();
inspector.selection.setNode(noimage);
inspector.once("inspector-updated", () => {
is(inspector.selection.node, noimage, "selection matches the inline element");
let noimageLink = contentDoc.querySelector(".ruleview-propertycontainer a");
ok (!noimageLink, "There is no link for the node with no background image");
finishUp();
});
});
});
});
});
}
function finishUp()
@ -90,7 +88,7 @@ function test()
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
doc = content.document;
waitForFocus(createDocument, content);
waitForFocus(() => openRuleView(selectNode), content);
}, true);
content.location = TEST_URI;

Просмотреть файл

@ -15,45 +15,33 @@ function createDocument()
'<span id="matches" class="matches">Some styled text</span>';
doc.title = "Tests that the no results placeholder works properly";
openInspector(openComputedView);
openComputedView(runStyleInspectorTests);
}
function openComputedView(aInspector)
function runStyleInspectorTests(aInspector, aComputedView)
{
inspector = aInspector;
inspector.sidebar.once("computedview-ready", function() {
inspector.sidebar.select("computedview");
computedView = getComputedView(inspector);
runStyleInspectorTests();
});
}
function runStyleInspectorTests()
{
Services.obs.addObserver(SI_AddFilterText, "StyleInspector-populated", false);
computedView = aComputedView;
let span = doc.querySelector("#matches");
ok(span, "captain, we have the matches span");
inspector.selection.setNode(span);
inspector.once("inspector-updated", () => {
is(span, computedView.viewedElement.rawNode(),
"style inspector node matches the selected node");
SI_AddFilterText();
});
is(span, computedView.viewedElement,
"style inspector node matches the selected node");
is(computedView.viewedElement, computedView.cssLogic.viewedElement,
"cssLogic node matches the cssHtmlTree node");
}
function SI_AddFilterText()
{
Services.obs.removeObserver(SI_AddFilterText, "StyleInspector-populated");
let searchbar = computedView.searchField;
let searchTerm = "xxxxx";
Services.obs.addObserver(SI_checkPlaceholderVisible, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", SI_checkPlaceholderVisible);
info("setting filter text to \"" + searchTerm + "\"");
searchbar.focus();
for each (let c in searchTerm) {
@ -63,7 +51,6 @@ function SI_AddFilterText()
function SI_checkPlaceholderVisible()
{
Services.obs.removeObserver(SI_checkPlaceholderVisible, "StyleInspector-populated");
info("SI_checkPlaceholderVisible called");
let placeholder = computedView.noResults;
let win = computedView.styleWindow;
@ -78,7 +65,7 @@ function SI_ClearFilterText()
{
let searchbar = computedView.searchField;
Services.obs.addObserver(SI_checkPlaceholderHidden, "StyleInspector-populated", false);
inspector.once("computed-view-refreshed", SI_checkPlaceholderHidden);
info("clearing filter text");
searchbar.focus();
searchbar.value = "";
@ -87,7 +74,6 @@ function SI_ClearFilterText()
function SI_checkPlaceholderHidden()
{
Services.obs.removeObserver(SI_checkPlaceholderHidden, "StyleInspector-populated");
let placeholder = computedView.noResults;
let win = computedView.styleWindow;
let display = win.getComputedStyle(placeholder).display;

Просмотреть файл

@ -3,7 +3,13 @@
* 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/. */
Services.prefs.setBoolPref("devtools.debugger.log", true);
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
});
let tempScope = {};
Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
let ConsoleUtils = tempScope.ConsoleUtils;
let gDevTools = tempScope.gDevTools;
@ -16,6 +22,8 @@ let {CssHtmlTree} = devtools.require("devtools/styleinspector/computed-view");
let {CssRuleView, _ElementStyle} = devtools.require("devtools/styleinspector/rule-view");
let {CssLogic, CssSelector} = devtools.require("devtools/styleinspector/css-logic");
let promise = devtools.require("sdk/core/promise");
let {
editableField,
getInplaceEditorForSpan: inplaceEditor
@ -40,6 +48,28 @@ function openInspector(callback)
});
}
function openRuleView(callback)
{
openInspector(inspector => {
inspector.sidebar.once("ruleview-ready", () => {
inspector.sidebar.select("ruleview");
let ruleView = inspector.sidebar.getWindowForTab("ruleview").ruleview.view;
callback(inspector, ruleView);
})
});
}
function openComputedView(callback)
{
openInspector(inspector => {
inspector.sidebar.once("computedview-ready", () => {
inspector.sidebar.select("computedview");
let ruleView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
callback(inspector, ruleView);
})
});
}
function addStyle(aDocument, aString)
{
let node = aDocument.createElement('style');
@ -120,6 +150,21 @@ function contextMenuClick(element) {
element.dispatchEvent(evt);
}
function expectRuleChange(rule) {
return rule._applyingModifications;
}
function promiseDone(promise) {
promise.then(null, err => {
ok(false, "Promise failed: " + err);
if (err.stack) {
dump(err.stack);
}
SimpleTest.finish();
});
}
registerCleanupFunction(tearDown);
waitForExplicitFinish();

Просмотреть файл

@ -177,14 +177,14 @@
- for the Copy URL menu item displayed in the context menu for a request -->
<!ENTITY netmonitorUI.context.copyUrl.accesskey "C">
<!-- LOCALIZATION NOTE (debuggerUI.summary.resend): This is the label displayed
- on the button in the headers tab that opens a form to resend the currently
<!-- LOCALIZATION NOTE (debuggerUI.summary.editAndResend): This is the label displayed
- on the button in the headers tab that opens a form to edit and resend the currently
displayed request -->
<!ENTITY netmonitorUI.summary.resend "Resend">
<!ENTITY netmonitorUI.summary.editAndResend "Edit and Resend">
<!-- LOCALIZATION NOTE (debuggerUI.summary.resend.accesskey): This is the access key
- for the Resend menu item displayed in the context menu for a request -->
<!ENTITY netmonitorUI.summary.resend.accesskey "R">
<!-- LOCALIZATION NOTE (debuggerUI.summary.editAndResend.accesskey): This is the access key
- for the "Edit and Resend" menu item displayed in the context menu for a request -->
<!ENTITY netmonitorUI.summary.editAndResend.accesskey "R">
<!-- LOCALIZATION NOTE (debuggerUI.custom.newRequest): This is the label displayed
- as the title of the new custom request form -->

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше