зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to m-i
This commit is contained in:
Коммит
f26020beac
|
@ -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">
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
...
|
||||
<script>
|
||||
addon.port.on('message', function(msg) { // part 4
|
||||
// msg will == 'I have information for you!'
|
||||
});
|
||||
// starting communication here..
|
||||
addon.port.emit('message'); // part 1
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</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 -->
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче