mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge fx-team to central, a=merge
This commit is contained in:
commit
fe6551955d
@ -2,13 +2,10 @@
|
||||
support-files = head.js
|
||||
|
||||
[browser_basic_functionality.js]
|
||||
skip-if = buildapp == "mulet" || e10s
|
||||
skip-if = buildapp == "mulet"
|
||||
[browser_first_download_panel.js]
|
||||
skip-if = os == "linux" # Bug 949434
|
||||
[browser_overflow_anchor.js]
|
||||
skip-if = os == "linux" # Bug 952422
|
||||
[browser_confirm_unblock_download.js]
|
||||
|
||||
[browser_iframe_gone_mid_download.js]
|
||||
skip-if = e10s
|
||||
|
||||
|
53
browser/components/extensions/ext-commands.js
Normal file
53
browser/components/extensions/ext-commands.js
Normal file
@ -0,0 +1,53 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
|
||||
var {
|
||||
PlatformInfo,
|
||||
} = ExtensionUtils;
|
||||
|
||||
// WeakMap[Extension -> Map[name => Command]]
|
||||
var commandsMap = new WeakMap();
|
||||
|
||||
function Command(description, shortcut) {
|
||||
this.description = description;
|
||||
this.shortcut = shortcut;
|
||||
}
|
||||
|
||||
/* eslint-disable mozilla/balanced-listeners */
|
||||
extensions.on("manifest_commands", (type, directive, extension, manifest) => {
|
||||
let commands = new Map();
|
||||
for (let name of Object.keys(manifest.commands)) {
|
||||
let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
|
||||
let manifestCommand = manifest.commands[name];
|
||||
let description = manifestCommand.description;
|
||||
let shortcut = manifestCommand.suggested_key[os] || manifestCommand.suggested_key.default;
|
||||
let command = new Command(description, shortcut);
|
||||
commands.set(name, command);
|
||||
}
|
||||
commandsMap.set(extension, commands);
|
||||
});
|
||||
|
||||
extensions.on("shutdown", (type, extension) => {
|
||||
commandsMap.delete(extension);
|
||||
});
|
||||
/* eslint-enable mozilla/balanced-listeners */
|
||||
|
||||
extensions.registerSchemaAPI("commands", null, (extension, context) => {
|
||||
return {
|
||||
commands: {
|
||||
getAll() {
|
||||
let commands = Array.from(commandsMap.get(extension), ([name, command]) => {
|
||||
return ({
|
||||
name,
|
||||
description: command.description,
|
||||
shortcut: command.shortcut,
|
||||
});
|
||||
});
|
||||
return Promise.resolve(commands);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
@ -5,6 +5,7 @@
|
||||
browser.jar:
|
||||
content/browser/extension.svg
|
||||
content/browser/ext-utils.js
|
||||
content/browser/ext-commands.js
|
||||
content/browser/ext-contextMenus.js
|
||||
content/browser/ext-browserAction.js
|
||||
content/browser/ext-pageAction.js
|
||||
|
@ -9,6 +9,7 @@ support-files =
|
||||
file_popup_api_injection_b.html
|
||||
|
||||
[browser_ext_simple.js]
|
||||
[browser_ext_commands.js]
|
||||
[browser_ext_currentWindow.js]
|
||||
[browser_ext_browserAction_simple.js]
|
||||
[browser_ext_browserAction_pageAction_icon.js]
|
||||
|
@ -0,0 +1,81 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
add_task(function* () {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"name": "Commands Extension",
|
||||
"commands": {
|
||||
"with-desciption": {
|
||||
"suggested_key": {
|
||||
"default": "Ctrl+Shift+Y",
|
||||
},
|
||||
"description": "should have a description",
|
||||
},
|
||||
"without-description": {
|
||||
"suggested_key": {
|
||||
"default": "Ctrl+Shift+D",
|
||||
},
|
||||
},
|
||||
"with-platform-info": {
|
||||
"suggested_key": {
|
||||
"mac": "Ctrl+Shift+M",
|
||||
"linux": "Ctrl+Shift+L",
|
||||
"windows": "Ctrl+Shift+W",
|
||||
"android": "Ctrl+Shift+A",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
background: function() {
|
||||
browser.test.onMessage.addListener((message, additionalScope) => {
|
||||
browser.commands.getAll((commands) => {
|
||||
let errorMessage = "getAll should return an array of commands";
|
||||
browser.test.assertEq(commands.length, 3, errorMessage);
|
||||
|
||||
let command = commands.find(c => c.name == "with-desciption");
|
||||
|
||||
errorMessage = "The description should match what is provided in the manifest";
|
||||
browser.test.assertEq("should have a description", command.description, errorMessage);
|
||||
|
||||
errorMessage = "The shortcut should match the default shortcut provided in the manifest";
|
||||
browser.test.assertEq("Ctrl+Shift+Y", command.shortcut, errorMessage);
|
||||
|
||||
command = commands.find(c => c.name == "without-description");
|
||||
|
||||
errorMessage = "The description should be empty when it is not provided";
|
||||
browser.test.assertEq(null, command.description, errorMessage);
|
||||
|
||||
errorMessage = "The shortcut should match the default shortcut provided in the manifest";
|
||||
browser.test.assertEq("Ctrl+Shift+D", command.shortcut, errorMessage);
|
||||
|
||||
let platformKeys = {
|
||||
macosx: "M",
|
||||
linux: "L",
|
||||
win: "W",
|
||||
android: "A",
|
||||
};
|
||||
|
||||
command = commands.find(c => c.name == "with-platform-info");
|
||||
let platformKey = platformKeys[additionalScope.platform];
|
||||
let shortcut = `Ctrl+Shift+${platformKey}`;
|
||||
errorMessage = `The shortcut should match the one provided in the manifest for OS='${additionalScope.platform}'`;
|
||||
browser.test.assertEq(shortcut, command.shortcut, errorMessage);
|
||||
|
||||
browser.test.notifyPass("commands");
|
||||
});
|
||||
});
|
||||
browser.test.sendMessage("ready");
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
extension.sendMessage("additional-scope", {platform: AppConstants.platform});
|
||||
yield extension.awaitFinish("commands");
|
||||
yield extension.unload();
|
||||
});
|
@ -546,6 +546,7 @@ BrowserGlue.prototype = {
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-utils.js");
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-browserAction.js");
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-pageAction.js");
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-commands.js");
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-contextMenus.js");
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-desktop-runtime.js");
|
||||
ExtensionManagement.registerScript("chrome://browser/content/ext-tabs.js");
|
||||
|
@ -2,18 +2,17 @@
|
||||
* 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/. */
|
||||
|
||||
/* global React */
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
loader.lazyRequireGetter(this, "AddonsTab",
|
||||
"devtools/client/aboutdebugging/components/addons-tab", true);
|
||||
loader.lazyRequireGetter(this, "TabMenu",
|
||||
"devtools/client/aboutdebugging/components/tab-menu", true);
|
||||
loader.lazyRequireGetter(this, "WorkersTab",
|
||||
"devtools/client/aboutdebugging/components/workers-tab", true);
|
||||
const Services = require("Services");
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { TabMenu } = require("./tab-menu");
|
||||
|
||||
loader.lazyRequireGetter(this, "AddonsTab", "./components/addons-tab", true);
|
||||
loader.lazyRequireGetter(this, "WorkersTab", "./components/workers-tab", true);
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
@ -38,13 +37,13 @@ exports.AboutDebuggingApp = React.createClass({
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.window.addEventListener("hashchange", this.onHashChange);
|
||||
window.addEventListener("hashchange", this.onHashChange);
|
||||
this.onHashChange();
|
||||
this.props.telemetry.toolOpened("aboutdebugging");
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.window.removeEventListener("hashchange", this.onHashChange);
|
||||
window.removeEventListener("hashchange", this.onHashChange);
|
||||
this.props.telemetry.toolClosed("aboutdebugging");
|
||||
this.props.telemetry.destroy();
|
||||
},
|
||||
@ -65,7 +64,7 @@ exports.AboutDebuggingApp = React.createClass({
|
||||
},
|
||||
|
||||
onHashChange() {
|
||||
let tabId = this.props.window.location.hash.substr(1);
|
||||
let tabId = window.location.hash.substr(1);
|
||||
|
||||
let isValid = tabs.some(t => t.id == tabId);
|
||||
if (isValid) {
|
||||
@ -78,7 +77,6 @@ exports.AboutDebuggingApp = React.createClass({
|
||||
},
|
||||
|
||||
selectTab(tabId) {
|
||||
let win = this.props.window;
|
||||
win.location.hash = "#" + tabId;
|
||||
window.location.hash = "#" + tabId;
|
||||
}
|
||||
});
|
||||
|
@ -2,18 +2,18 @@
|
||||
* 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/. */
|
||||
|
||||
/* global React */
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "Ci", "chrome", true);
|
||||
loader.lazyRequireGetter(this, "Cc", "chrome", true);
|
||||
loader.lazyRequireGetter(this, "React", "devtools/client/shared/vendor/react");
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
|
||||
loader.lazyImporter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
@ -58,11 +58,9 @@ exports.AddonsControls = React.createClass({
|
||||
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
|
||||
},
|
||||
|
||||
loadAddonFromFile(event) {
|
||||
let win = event.target.ownerDocument.defaultView;
|
||||
|
||||
loadAddonFromFile() {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(win,
|
||||
fp.init(window,
|
||||
Strings.GetStringFromName("selectAddonFromFile"),
|
||||
Ci.nsIFilePicker.modeOpen);
|
||||
let res = fp.show();
|
||||
@ -78,7 +76,7 @@ exports.AddonsControls = React.createClass({
|
||||
try {
|
||||
AddonManager.installTemporaryAddon(file);
|
||||
} catch (e) {
|
||||
win.alert("Error while installing the addon:\n" + e.message + "\n");
|
||||
window.alert("Error while installing the addon:\n" + e.message + "\n");
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
@ -2,22 +2,17 @@
|
||||
* 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/. */
|
||||
|
||||
/* global AddonManager, React */
|
||||
/* global React */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
loader.lazyRequireGetter(this, "TargetList",
|
||||
"devtools/client/aboutdebugging/components/target-list", true);
|
||||
loader.lazyRequireGetter(this, "TabHeader",
|
||||
"devtools/client/aboutdebugging/components/tab-header", true);
|
||||
loader.lazyRequireGetter(this, "AddonsControls",
|
||||
"devtools/client/aboutdebugging/components/addons-controls", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyImporter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { AddonsControls } = require("./addons-controls");
|
||||
const { TabHeader } = require("./tab-header");
|
||||
const { TargetList } = require("./target-list");
|
||||
|
||||
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
|
||||
const Strings = Services.strings.createBundle(
|
||||
|
@ -2,12 +2,9 @@
|
||||
* 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/. */
|
||||
|
||||
/* global React */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
exports.TabHeader = React.createClass({
|
||||
displayName: "TabHeader",
|
||||
|
@ -2,12 +2,9 @@
|
||||
* 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/. */
|
||||
|
||||
/* global React */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
exports.TabMenuEntry = React.createClass({
|
||||
displayName: "TabMenuEntry",
|
||||
|
@ -6,10 +6,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
loader.lazyRequireGetter(this, "TabMenuEntry",
|
||||
"devtools/client/aboutdebugging/components/tab-menu-entry", true);
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { TabMenuEntry } = require("./tab-menu-entry");
|
||||
|
||||
exports.TabMenu = React.createClass({
|
||||
displayName: "TabMenu",
|
||||
|
@ -6,14 +6,14 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
loader.lazyRequireGetter(this, "Target",
|
||||
"devtools/client/aboutdebugging/components/target", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
const Services = require("Services");
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { Target } = require("./target");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
|
||||
const LocaleCompare = (a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
};
|
||||
|
@ -2,23 +2,22 @@
|
||||
* 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/. */
|
||||
|
||||
/* global alert, BrowserToolboxProcess, gDevTools, React, TargetFactory,
|
||||
Toolbox */
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
loader.lazyRequireGetter(this, "TargetFactory",
|
||||
"devtools/client/framework/target", true);
|
||||
"devtools/client/framework/target", true);
|
||||
loader.lazyRequireGetter(this, "gDevTools",
|
||||
"devtools/client/framework/devtools", true);
|
||||
loader.lazyRequireGetter(this, "Toolbox",
|
||||
"devtools/client/framework/toolbox", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
"devtools/client/framework/toolbox", true);
|
||||
|
||||
loader.lazyImporter(this, "BrowserToolboxProcess",
|
||||
"resource://devtools/client/framework/ToolboxProcess.jsm");
|
||||
loader.lazyRequireGetter(this, "gDevTools",
|
||||
"devtools/client/framework/devtools", true);
|
||||
"resource://devtools/client/framework/ToolboxProcess.jsm");
|
||||
|
||||
const Services = require("Services");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
@ -26,47 +25,60 @@ const Strings = Services.strings.createBundle(
|
||||
exports.Target = React.createClass({
|
||||
displayName: "Target",
|
||||
|
||||
debug() {
|
||||
let { client, target } = this.props;
|
||||
switch (target.type) {
|
||||
case "extension":
|
||||
BrowserToolboxProcess.init({ addonID: target.addonID });
|
||||
break;
|
||||
case "serviceworker":
|
||||
// Fall through.
|
||||
case "sharedworker":
|
||||
// Fall through.
|
||||
case "worker":
|
||||
let workerActor = this.props.target.actorID;
|
||||
client.attachWorker(workerActor, (response, workerClient) => {
|
||||
gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
|
||||
"jsdebugger", Toolbox.HostType.WINDOW)
|
||||
.then(toolbox => {
|
||||
toolbox.once("destroy", () => workerClient.detach());
|
||||
});
|
||||
});
|
||||
break;
|
||||
default:
|
||||
alert("Not implemented yet!");
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
let { target, debugDisabled } = this.props;
|
||||
let isServiceWorker = (target.type === "serviceworker");
|
||||
let isRunning = (!isServiceWorker || target.workerActor);
|
||||
return React.createElement("div", { className: "target" },
|
||||
React.createElement("img", {
|
||||
className: "target-icon",
|
||||
role: "presentation",
|
||||
src: target.icon }),
|
||||
React.createElement("div", { className: "target-details" },
|
||||
React.createElement("div", { className: "target-name" }, target.name),
|
||||
React.createElement("div", { className: "target-url" }, target.url)
|
||||
React.createElement("div", { className: "target-name" }, target.name)
|
||||
),
|
||||
React.createElement("button", {
|
||||
className: "debug-button",
|
||||
onClick: this.debug,
|
||||
disabled: debugDisabled,
|
||||
}, Strings.GetStringFromName("debug"))
|
||||
(isRunning ?
|
||||
React.createElement("button", {
|
||||
className: "debug-button",
|
||||
onClick: this.debug,
|
||||
disabled: debugDisabled,
|
||||
}, Strings.GetStringFromName("debug")) :
|
||||
null
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
debug() {
|
||||
let { target } = this.props;
|
||||
switch (target.type) {
|
||||
case "extension":
|
||||
BrowserToolboxProcess.init({ addonID: target.addonID });
|
||||
break;
|
||||
case "serviceworker":
|
||||
if (target.workerActor) {
|
||||
this.openWorkerToolbox(target.workerActor);
|
||||
}
|
||||
break;
|
||||
case "sharedworker":
|
||||
this.openWorkerToolbox(target.workerActor);
|
||||
break;
|
||||
case "worker":
|
||||
this.openWorkerToolbox(target.workerActor);
|
||||
break;
|
||||
default:
|
||||
window.alert("Not implemented yet!");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
openWorkerToolbox(workerActor) {
|
||||
let { client } = this.props;
|
||||
client.attachWorker(workerActor, (response, workerClient) => {
|
||||
gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
|
||||
"jsdebugger", Toolbox.HostType.WINDOW)
|
||||
.then(toolbox => {
|
||||
toolbox.once("destroy", () => workerClient.detach());
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -2,21 +2,15 @@
|
||||
* 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/. */
|
||||
|
||||
/* global React */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "Ci",
|
||||
"chrome", true);
|
||||
loader.lazyRequireGetter(this, "React",
|
||||
"devtools/client/shared/vendor/react");
|
||||
loader.lazyRequireGetter(this, "TargetList",
|
||||
"devtools/client/aboutdebugging/components/target-list", true);
|
||||
loader.lazyRequireGetter(this, "TabHeader",
|
||||
"devtools/client/aboutdebugging/components/tab-header", true);
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
const { Ci } = require("chrome");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { TargetList } = require("./target-list");
|
||||
const { TabHeader } = require("./tab-header");
|
||||
|
||||
const Strings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/aboutdebugging.properties");
|
||||
@ -38,6 +32,7 @@ exports.WorkersTab = React.createClass({
|
||||
componentDidMount() {
|
||||
let client = this.props.client;
|
||||
client.addListener("workerListChanged", this.update);
|
||||
client.addListener("serviceWorkerRegistrationListChanged", this.update);
|
||||
client.addListener("processListChanged", this.update);
|
||||
this.update();
|
||||
},
|
||||
@ -45,6 +40,7 @@ exports.WorkersTab = React.createClass({
|
||||
componentWillUnmount() {
|
||||
let client = this.props.client;
|
||||
client.removeListener("processListChanged", this.update);
|
||||
client.removeListener("serviceWorkerRegistrationListChanged", this.update);
|
||||
client.removeListener("workerListChanged", this.update);
|
||||
},
|
||||
|
||||
@ -77,52 +73,90 @@ exports.WorkersTab = React.createClass({
|
||||
|
||||
update() {
|
||||
let workers = this.getInitialState().workers;
|
||||
|
||||
this.getWorkerForms().then(forms => {
|
||||
forms.forEach(form => {
|
||||
let worker = {
|
||||
name: form.url,
|
||||
forms.registrations.forEach(form => {
|
||||
workers.service.push({
|
||||
type: "serviceworker",
|
||||
icon: WorkerIcon,
|
||||
actorID: form.actor
|
||||
name: form.url,
|
||||
url: form.url,
|
||||
scope: form.scope,
|
||||
registrationActor: form.actor
|
||||
});
|
||||
});
|
||||
|
||||
forms.workers.forEach(form => {
|
||||
let worker = {
|
||||
type: "worker",
|
||||
icon: WorkerIcon,
|
||||
name: form.url,
|
||||
url: form.url,
|
||||
workerActor: form.actor
|
||||
};
|
||||
switch (form.type) {
|
||||
case Ci.nsIWorkerDebugger.TYPE_SERVICE:
|
||||
worker.type = "serviceworker";
|
||||
workers.service.push(worker);
|
||||
for (let registration of workers.service) {
|
||||
if (registration.scope === form.scope) {
|
||||
// XXX: Race, sometimes a ServiceWorkerRegistrationInfo doesn't
|
||||
// have a scriptSpec, but its associated WorkerDebugger does.
|
||||
if (!registration.url) {
|
||||
registration.name = registration.url = form.url;
|
||||
}
|
||||
registration.workerActor = form.actor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Ci.nsIWorkerDebugger.TYPE_SHARED:
|
||||
worker.type = "sharedworker";
|
||||
workers.shared.push(worker);
|
||||
break;
|
||||
default:
|
||||
worker.type = "worker";
|
||||
workers.other.push(worker);
|
||||
}
|
||||
});
|
||||
|
||||
// XXX: Filter out the service worker registrations for which we couldn't
|
||||
// find the scriptSpec.
|
||||
workers.service = workers.service.filter(reg => !!reg.url);
|
||||
|
||||
this.setState({ workers });
|
||||
});
|
||||
},
|
||||
|
||||
getWorkerForms: Task.async(function*() {
|
||||
let client = this.props.client;
|
||||
let registrations = [];
|
||||
let workers = [];
|
||||
|
||||
// List workers from the Parent process
|
||||
let result = yield client.mainRoot.listWorkers();
|
||||
let forms = result.workers;
|
||||
try {
|
||||
// List service worker registrations
|
||||
({ registrations } =
|
||||
yield client.mainRoot.listServiceWorkerRegistrations());
|
||||
|
||||
// And then from the Child processes
|
||||
let { processes } = yield client.mainRoot.listProcesses();
|
||||
for (let process of processes) {
|
||||
// Ignore parent process
|
||||
if (process.parent) {
|
||||
continue;
|
||||
// List workers from the Parent process
|
||||
({ workers } = yield client.mainRoot.listWorkers());
|
||||
|
||||
// And then from the Child processes
|
||||
let { processes } = yield client.mainRoot.listProcesses();
|
||||
for (let process of processes) {
|
||||
// Ignore parent process
|
||||
if (process.parent) {
|
||||
continue;
|
||||
}
|
||||
let { form } = yield client.getProcess(process.id);
|
||||
let processActor = form.actor;
|
||||
let response = yield client.request({
|
||||
to: processActor,
|
||||
type: "listWorkers"
|
||||
});
|
||||
workers = workers.concat(response.workers);
|
||||
}
|
||||
let { form } = yield client.getProcess(process.id);
|
||||
let processActor = form.actor;
|
||||
let { workers } = yield client.request({to: processActor,
|
||||
type: "listWorkers"});
|
||||
forms = forms.concat(workers);
|
||||
} catch (e) {
|
||||
// Something went wrong, maybe our client is disconnected?
|
||||
}
|
||||
|
||||
return forms;
|
||||
return { registrations, workers };
|
||||
}),
|
||||
});
|
||||
|
@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
/* global DebuggerClient, DebuggerServer, React */
|
||||
/* global DebuggerClient, DebuggerServer */
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -16,8 +16,14 @@ loader.lazyRequireGetter(this, "DebuggerServer",
|
||||
"devtools/server/main", true);
|
||||
loader.lazyRequireGetter(this, "Telemetry",
|
||||
"devtools/client/shared/telemetry");
|
||||
loader.lazyRequireGetter(this, "AboutDebuggingApp",
|
||||
"devtools/client/aboutdebugging/components/aboutdebugging", true);
|
||||
|
||||
const { BrowserLoader } = Components.utils.import(
|
||||
"resource://devtools/client/shared/browser-loader.js", {});
|
||||
const { require } =
|
||||
BrowserLoader("resource://devtools/client/aboutdebugging/", window);
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const { AboutDebuggingApp } = require("./components/aboutdebugging");
|
||||
|
||||
var AboutDebugging = {
|
||||
init() {
|
||||
@ -31,8 +37,9 @@ var AboutDebugging = {
|
||||
this.client.connect().then(() => {
|
||||
let client = this.client;
|
||||
let telemetry = new Telemetry();
|
||||
React.render(React.createElement(AboutDebuggingApp,
|
||||
{ client, telemetry, window }), document.querySelector("#body"));
|
||||
|
||||
let app = React.createElement(AboutDebuggingApp, { client, telemetry });
|
||||
React.render(app, document.querySelector("#body"));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -67,7 +67,8 @@ add_task(function* () {
|
||||
let names = [...document.querySelectorAll("#service-workers .target-name")];
|
||||
let name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
|
||||
ok(name, "Found the service worker in the list");
|
||||
let debugBtn = name.parentNode.parentNode.querySelector("button");
|
||||
let targetElement = name.parentNode.parentNode;
|
||||
let debugBtn = targetElement.querySelector(".debug-button");
|
||||
ok(debugBtn, "Found its debug button");
|
||||
|
||||
// Click on it and wait for the toolbox to be ready
|
||||
@ -88,16 +89,18 @@ add_task(function* () {
|
||||
});
|
||||
|
||||
assertHasWorker(true, document, "service-workers", SERVICE_WORKER);
|
||||
ok(targetElement.querySelector(".debug-button"),
|
||||
"The debug button is still there");
|
||||
|
||||
yield toolbox.destroy();
|
||||
toolbox = null;
|
||||
|
||||
// Now ensure that the worker is correctly destroyed
|
||||
// after we destroy the toolbox.
|
||||
// The list should update once it get destroyed.
|
||||
yield waitForMutation(serviceWorkersElement, { childList: true });
|
||||
|
||||
assertHasWorker(false, document, "service-workers", SERVICE_WORKER);
|
||||
// The DEBUG button should disappear once the worker is destroyed.
|
||||
yield waitForMutation(targetElement, { childList: true });
|
||||
ok(!targetElement.querySelector(".debug-button"),
|
||||
"The debug button was removed when the worker was killed");
|
||||
|
||||
// Finally, unregister the service worker itself
|
||||
// Use message manager to work with e10s
|
||||
@ -124,6 +127,11 @@ add_task(function* () {
|
||||
});
|
||||
ok(true, "Service worker registration unregistered");
|
||||
|
||||
// Now ensure that the worker registration is correctly removed.
|
||||
// The list should update once the registration is destroyed.
|
||||
yield waitForMutation(serviceWorkersElement, { childList: true });
|
||||
assertHasWorker(false, document, "service-workers", SERVICE_WORKER);
|
||||
|
||||
yield removeTab(swTab);
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
||||
|
@ -42,7 +42,6 @@ support-files =
|
||||
[browser_inspector_destroy-before-ready.js]
|
||||
[browser_inspector_expand-collapse.js]
|
||||
[browser_inspector_gcli-inspect-command.js]
|
||||
skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
|
||||
[browser_inspector_highlighter-01.js]
|
||||
[browser_inspector_highlighter-02.js]
|
||||
[browser_inspector_highlighter-03.js]
|
||||
@ -102,8 +101,6 @@ skip-if = e10s && debug && os == 'win' # Bug 1250058 - Docshell leak on win debu
|
||||
[browser_inspector_reload-01.js]
|
||||
[browser_inspector_reload-02.js]
|
||||
[browser_inspector_remove-iframe-during-load.js]
|
||||
[browser_inspector_scrolling.js]
|
||||
skip-if = e10s # Test synthesize scrolling events in content. Also, see bug 1035661.
|
||||
[browser_inspector_search-01.js]
|
||||
[browser_inspector_search-02.js]
|
||||
[browser_inspector_search-03.js]
|
||||
|
@ -1,45 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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";
|
||||
|
||||
// Test that highlighted nodes can be scrolled.
|
||||
// TODO: This doesn't test anything useful. See b.m.o 1035661.
|
||||
const IFRAME_SRC = "data:text/html;charset=utf-8," +
|
||||
"<div style='height:500px; width:500px; border:1px solid gray;'>" +
|
||||
"big div" +
|
||||
"</div>";
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8," +
|
||||
"<p>browser_inspector_scrolling.js</p>" +
|
||||
"<iframe src=\"" + IFRAME_SRC + "\" />";
|
||||
|
||||
add_task(function* () {
|
||||
let { inspector, toolbox } = yield openInspectorForURL(TEST_URI);
|
||||
|
||||
let iframe = getNode("iframe");
|
||||
let div = getNode("div", { document: iframe.contentDocument });
|
||||
let divFront = yield getNodeFrontInFrame("div", "iframe", inspector);
|
||||
|
||||
info("Waiting for highlighter box model to appear.");
|
||||
yield toolbox.highlighter.showBoxModel(divFront);
|
||||
|
||||
let scrolled = once(gBrowser.selectedBrowser, "scroll");
|
||||
|
||||
info("Scrolling iframe.");
|
||||
EventUtils.synthesizeWheel(div, 10, 10,
|
||||
{ deltaY: 50.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL },
|
||||
iframe.contentWindow);
|
||||
|
||||
info("Waiting for scroll event");
|
||||
yield scrolled;
|
||||
|
||||
let isRetina = devicePixelRatio === 2;
|
||||
is(iframe.contentDocument.body.scrollTop,
|
||||
isRetina ? 25 : 50, "inspected iframe scrolled");
|
||||
|
||||
info("Hiding box model.");
|
||||
yield toolbox.highlighter.hideBoxModel();
|
||||
});
|
@ -63,12 +63,18 @@ var Manager = {
|
||||
*
|
||||
* @param aWindow the main window.
|
||||
* @param aTab the tab targeted.
|
||||
* @returns {ResponsiveUI} the instance of ResponsiveUI for the current tab.
|
||||
*/
|
||||
runIfNeeded: function(aWindow, aTab) {
|
||||
runIfNeeded: Task.async(function*(aWindow, aTab) {
|
||||
let ui;
|
||||
if (!this.isActiveForTab(aTab)) {
|
||||
new ResponsiveUI(aWindow, aTab);
|
||||
ui = new ResponsiveUI(aWindow, aTab);
|
||||
yield ui.inited;
|
||||
} else {
|
||||
ui = this.getResponsiveUIForTab(aTab);
|
||||
}
|
||||
},
|
||||
return ui;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns true if responsive view is active for the provided tab.
|
||||
@ -97,9 +103,7 @@ var Manager = {
|
||||
handleGcliCommand: Task.async(function*(aWindow, aTab, aCommand, aArgs) {
|
||||
switch (aCommand) {
|
||||
case "resize to":
|
||||
this.runIfNeeded(aWindow, aTab);
|
||||
let ui = ActiveTabs.get(aTab);
|
||||
yield ui.inited;
|
||||
let ui = yield this.runIfNeeded(aWindow, aTab);
|
||||
ui.setSize(aArgs.width, aArgs.height);
|
||||
break;
|
||||
case "resize on":
|
||||
|
@ -18,40 +18,6 @@ const BROWSER_BASED_DIRS = [
|
||||
"resource://devtools/client/shared/redux"
|
||||
];
|
||||
|
||||
function clearCache() {
|
||||
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
|
||||
}
|
||||
|
||||
function hotReloadFile(window, require, loader, componentProxies, fileURI) {
|
||||
dump("Hot reloading: " + fileURI + "\n");
|
||||
|
||||
if (fileURI.match(/\.js$/)) {
|
||||
// Test for React proxy components
|
||||
const proxy = componentProxies.get(fileURI);
|
||||
if (proxy) {
|
||||
// Remove the old module and re-require the new one; the require
|
||||
// hook in the loader will take care of the rest
|
||||
delete loader.modules[fileURI];
|
||||
clearCache();
|
||||
require(fileURI);
|
||||
}
|
||||
} else if (fileURI.match(/\.css$/)) {
|
||||
const links = [...window.document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "link")];
|
||||
links.forEach(link => {
|
||||
if (link.href.indexOf(fileURI) === 0) {
|
||||
const parentNode = link.parentNode;
|
||||
const newLink = window.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
|
||||
newLink.rel = "stylesheet";
|
||||
newLink.type = "text/css";
|
||||
newLink.href = fileURI + "?s=" + Math.random();
|
||||
|
||||
parentNode.insertBefore(newLink, link);
|
||||
parentNode.removeChild(link);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a loader to be used in a browser environment. This evaluates
|
||||
* modules in their own environment, but sets window (the normal
|
||||
@ -78,6 +44,23 @@ function hotReloadFile(window, require, loader, componentProxies, fileURI) {
|
||||
* - require: a function to require modules with
|
||||
*/
|
||||
function BrowserLoader(baseURI, window) {
|
||||
const browserLoaderBuilder = new BrowserLoaderBuilder(baseURI, window);
|
||||
return {
|
||||
loader: browserLoaderBuilder.loader,
|
||||
require: browserLoaderBuilder.require
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Private class used to build the Loader instance and require method returned
|
||||
* by BrowserLoader(baseURI, window).
|
||||
*
|
||||
* @param string baseURI
|
||||
* Base path to load modules from.
|
||||
* @param Object window
|
||||
* The window instance to evaluate modules within
|
||||
*/
|
||||
function BrowserLoaderBuilder(baseURI, window) {
|
||||
const loaderOptions = devtools.require("@loader/options");
|
||||
const dynamicPaths = {};
|
||||
const componentProxies = new Map();
|
||||
@ -126,6 +109,13 @@ function BrowserLoader(baseURI, window) {
|
||||
define(factory) {
|
||||
factory(this.require, this.exports, this.module);
|
||||
},
|
||||
// Allow modules to use the DevToolsLoader lazy loading helpers.
|
||||
loader: {
|
||||
lazyGetter: devtools.lazyGetter,
|
||||
lazyImporter: devtools.lazyImporter,
|
||||
lazyServiceGetter: devtools.lazyServiceGetter,
|
||||
lazyRequireGetter: this.lazyRequireGetter.bind(this),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@ -155,29 +145,81 @@ function BrowserLoader(baseURI, window) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const mainModule = loaders.Module(baseURI, joinURI(baseURI, "main.js"));
|
||||
const mainLoader = loaders.Loader(opts);
|
||||
const require = loaders.Require(mainLoader, mainModule);
|
||||
this.loader = loaders.Loader(opts);
|
||||
this.require = loaders.Require(this.loader, mainModule);
|
||||
|
||||
if (hotReloadEnabled) {
|
||||
const watcher = devtools.require("devtools/client/shared/file-watcher");
|
||||
function onFileChanged(_, fileURI) {
|
||||
hotReloadFile(window, require, mainLoader, componentProxies, fileURI);
|
||||
}
|
||||
const onFileChanged = (_, fileURI) => {
|
||||
this.hotReloadFile(window, componentProxies, fileURI);
|
||||
};
|
||||
watcher.on("file-changed", onFileChanged);
|
||||
|
||||
window.addEventListener("unload", () => {
|
||||
watcher.off("file-changed", onFileChanged);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
loader: mainLoader,
|
||||
require: require
|
||||
};
|
||||
}
|
||||
|
||||
BrowserLoaderBuilder.prototype = {
|
||||
/**
|
||||
* Define a getter property on the given object that requires the given
|
||||
* module. This enables delaying importing modules until the module is
|
||||
* actually used.
|
||||
*
|
||||
* @param Object obj
|
||||
* The object to define the property on.
|
||||
* @param String property
|
||||
* The property name.
|
||||
* @param String module
|
||||
* The module path.
|
||||
* @param Boolean destructure
|
||||
* Pass true if the property name is a member of the module's exports.
|
||||
*/
|
||||
lazyRequireGetter: function(obj, property, module, destructure) {
|
||||
devtools.lazyGetter(obj, property, () => {
|
||||
return destructure
|
||||
? this.require(module)[property]
|
||||
: this.require(module || property);
|
||||
});
|
||||
},
|
||||
|
||||
clearCache: function() {
|
||||
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
|
||||
},
|
||||
|
||||
hotReloadFile: function(window, componentProxies, fileURI) {
|
||||
dump("Hot reloading: " + fileURI + "\n");
|
||||
|
||||
if (fileURI.match(/\.js$/)) {
|
||||
// Test for React proxy components
|
||||
const proxy = componentProxies.get(fileURI);
|
||||
if (proxy) {
|
||||
// Remove the old module and re-require the new one; the require
|
||||
// hook in the loader will take care of the rest
|
||||
delete this.loader.modules[fileURI];
|
||||
this.clearCache();
|
||||
this.require(fileURI);
|
||||
}
|
||||
} else if (fileURI.match(/\.css$/)) {
|
||||
const links = [...window.document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "link")];
|
||||
links.forEach(link => {
|
||||
if (link.href.indexOf(fileURI) === 0) {
|
||||
const parentNode = link.parentNode;
|
||||
const newLink = window.document.createElementNS("http://www.w3.org/1999/xhtml", "link");
|
||||
newLink.rel = "stylesheet";
|
||||
newLink.type = "text/css";
|
||||
newLink.href = fileURI + "?s=" + Math.random();
|
||||
|
||||
parentNode.insertBefore(newLink, link);
|
||||
parentNode.removeChild(link);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.BrowserLoader = BrowserLoader;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["BrowserLoader"];
|
||||
|
@ -945,11 +945,11 @@ StyleEditorUI.prototype = {
|
||||
* @param {object} options
|
||||
* Object with width or/and height properties.
|
||||
*/
|
||||
_launchResponsiveMode: function(options = {}) {
|
||||
_launchResponsiveMode: Task.async(function*(options = {}) {
|
||||
let tab = this._target.tab;
|
||||
let win = this._target.tab.ownerGlobal;
|
||||
|
||||
ResponsiveUIManager.runIfNeeded(win, tab);
|
||||
yield ResponsiveUIManager.runIfNeeded(win, tab);
|
||||
if (options.width && options.height) {
|
||||
ResponsiveUIManager.getResponsiveUIForTab(tab).setSize(options.width,
|
||||
options.height);
|
||||
@ -958,7 +958,7 @@ StyleEditorUI.prototype = {
|
||||
} else if (options.height) {
|
||||
ResponsiveUIManager.getResponsiveUIForTab(tab).setHeight(options.height);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Jump cursor to the editor for a stylesheet and line number for a rule.
|
||||
|
@ -7,25 +7,29 @@
|
||||
/* Tests responsive mode links for
|
||||
* @media sidebar width and height related conditions */
|
||||
|
||||
const {ResponsiveUIManager} =
|
||||
Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", {});
|
||||
const mgr = "resource://devtools/client/responsivedesign/responsivedesign.jsm";
|
||||
const {ResponsiveUIManager} = Cu.import(mgr, {});
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
|
||||
|
||||
waitForExplicitFinish();
|
||||
const responsiveModeToggleClass = ".media-responsive-mode-toggle";
|
||||
|
||||
add_task(function*() {
|
||||
let {ui} = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
let mediaEditor = ui.editors[1];
|
||||
yield openEditor(mediaEditor);
|
||||
let editor = ui.editors[1];
|
||||
yield openEditor(editor);
|
||||
|
||||
yield testLinkifiedConditions(mediaEditor, gBrowser.selectedTab, ui);
|
||||
let tab = gBrowser.selectedTab;
|
||||
testNumberOfLinks(editor);
|
||||
yield testMediaLink(editor, tab, ui, 2, "width", 400);
|
||||
yield testMediaLink(editor, tab, ui, 3, "height", 200);
|
||||
|
||||
yield closeRDM(tab, ui);
|
||||
doFinalChecks(editor);
|
||||
});
|
||||
|
||||
function* testLinkifiedConditions(editor, tab, ui) {
|
||||
function testNumberOfLinks(editor) {
|
||||
let sidebar = editor.details.querySelector(".stylesheet-sidebar");
|
||||
let conditions = sidebar.querySelectorAll(".media-rule-condition");
|
||||
let responsiveModeToggleClass = ".media-responsive-mode-toggle";
|
||||
|
||||
info("Testing if media rules have the appropriate number of links");
|
||||
ok(!conditions[0].querySelector(responsiveModeToggleClass),
|
||||
@ -35,36 +39,69 @@ function* testLinkifiedConditions(editor, tab, ui) {
|
||||
ok(conditions[2].querySelector(responsiveModeToggleClass),
|
||||
"There should be 1 responsive mode link in the media rule");
|
||||
is(conditions[3].querySelectorAll(responsiveModeToggleClass).length, 2,
|
||||
"There should be 2 resposnive mode links in the media rule");
|
||||
"There should be 2 responsive mode links in the media rule");
|
||||
}
|
||||
|
||||
function* testMediaLink(editor, tab, ui, itemIndex, type, value) {
|
||||
let sidebar = editor.details.querySelector(".stylesheet-sidebar");
|
||||
let conditions = sidebar.querySelectorAll(".media-rule-condition");
|
||||
|
||||
let onMediaChange = once("media-list-changed", ui);
|
||||
let ruiEvent = !ResponsiveUIManager.isActiveForTab(tab) ?
|
||||
once("on", ResponsiveUIManager) :
|
||||
once("contentResize", ResponsiveUIManager);
|
||||
|
||||
info("Launching responsive mode");
|
||||
conditions[2].querySelector(responsiveModeToggleClass).click();
|
||||
conditions[itemIndex].querySelector(responsiveModeToggleClass).click();
|
||||
|
||||
info("Waiting for the @media list to update");
|
||||
let onMediaChange = once("media-list-changed", ui);
|
||||
yield once("on", ResponsiveUIManager);
|
||||
yield ruiEvent;
|
||||
yield onMediaChange;
|
||||
|
||||
ResponsiveUIManager.getResponsiveUIForTab(tab).transitionsEnabled = false;
|
||||
|
||||
ok(ResponsiveUIManager.isActiveForTab(tab),
|
||||
"Responsive mode should be active.");
|
||||
conditions = sidebar.querySelectorAll(".media-rule-condition");
|
||||
ok(!conditions[2].classList.contains("media-condition-unmatched"),
|
||||
ok(!conditions[itemIndex].classList.contains("media-condition-unmatched"),
|
||||
"media rule should now be matched after responsive mode is active");
|
||||
|
||||
let dimension = (yield getSizing())[type];
|
||||
is(dimension, value, `${type} should be properly set.`);
|
||||
}
|
||||
|
||||
function* closeRDM(tab, ui) {
|
||||
info("Closing responsive mode");
|
||||
ResponsiveUIManager.toggle(window, tab);
|
||||
onMediaChange = once("media-list-changed", ui);
|
||||
let onMediaChange = once("media-list-changed", ui);
|
||||
yield once("off", ResponsiveUIManager);
|
||||
yield onMediaChange;
|
||||
|
||||
ok(!ResponsiveUIManager.isActiveForTab(tab),
|
||||
"Responsive mode should no longer be active.");
|
||||
}
|
||||
|
||||
function doFinalChecks(editor) {
|
||||
let sidebar = editor.details.querySelector(".stylesheet-sidebar");
|
||||
let conditions = sidebar.querySelectorAll(".media-rule-condition");
|
||||
conditions = sidebar.querySelectorAll(".media-rule-condition");
|
||||
ok(conditions[2].classList.contains("media-condition-unmatched"),
|
||||
"media rule should now be unmatched after responsive mode is closed");
|
||||
"The width condition should now be unmatched");
|
||||
ok(conditions[3].classList.contains("media-condition-unmatched"),
|
||||
"The height condition should now be unmatched");
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
function* getSizing() {
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let sizing = yield ContentTask.spawn(browser, {}, function*() {
|
||||
return {
|
||||
width: content.innerWidth,
|
||||
height: content.innerHeight
|
||||
};
|
||||
});
|
||||
return sizing;
|
||||
}
|
||||
|
||||
function once(event, target) {
|
||||
let deferred = promise.defer();
|
||||
target.once(event, () => {
|
||||
|
@ -219,7 +219,6 @@ skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
|
||||
[browser_webconsole_bug_589162_css_filter.js]
|
||||
[browser_webconsole_bug_592442_closing_brackets.js]
|
||||
[browser_webconsole_bug_593003_iframe_wrong_hud.js]
|
||||
skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
|
||||
[browser_webconsole_bug_594497_history_arrow_keys.js]
|
||||
[browser_webconsole_bug_595223_file_uri.js]
|
||||
[browser_webconsole_bug_595350_multiple_windows_and_tabs.js]
|
||||
@ -298,7 +297,6 @@ skip-if = e10s && os == 'win'
|
||||
[browser_webconsole_change_font_size.js]
|
||||
[browser_webconsole_chrome.js]
|
||||
[browser_webconsole_clickable_urls.js]
|
||||
skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
|
||||
[browser_webconsole_closure_inspection.js]
|
||||
skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
|
||||
[browser_webconsole_completion.js]
|
||||
|
@ -15,57 +15,54 @@ const TEST_IFRAME_URI = "http://example.com/browser/devtools/client/" +
|
||||
const TEST_DUMMY_URI = "http://example.com/browser/devtools/client/" +
|
||||
"webconsole/test/test-console.html";
|
||||
|
||||
var tab1, tab2;
|
||||
|
||||
function test() {
|
||||
loadTab(TEST_URI).then(({tab}) => {
|
||||
tab1 = tab;
|
||||
add_task(function*() {
|
||||
|
||||
let tab1 = (yield loadTab(TEST_URI)).tab;
|
||||
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
|
||||
content.console.log("FOO");
|
||||
openConsole().then(() => {
|
||||
tab2 = gBrowser.addTab(TEST_DUMMY_URI);
|
||||
gBrowser.selectedTab = tab2;
|
||||
gBrowser.selectedBrowser.addEventListener("load", tab2Loaded, true);
|
||||
});
|
||||
});
|
||||
yield openConsole();
|
||||
|
||||
let tab2 = (yield loadTab(TEST_DUMMY_URI)).tab;
|
||||
yield openConsole(gBrowser.selectedTab);
|
||||
|
||||
info("Reloading tab 1");
|
||||
yield reloadTab(tab1);
|
||||
|
||||
info("Checking for messages");
|
||||
yield checkMessages(tab1, tab2);
|
||||
|
||||
info("Cleaning up");
|
||||
yield closeConsole(tab1);
|
||||
yield closeConsole(tab2);
|
||||
});
|
||||
|
||||
function* reloadTab(tab) {
|
||||
let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.linkedBrowser.reload();
|
||||
yield loaded;
|
||||
}
|
||||
|
||||
function tab2Loaded(aEvent) {
|
||||
tab2.linkedBrowser.removeEventListener(aEvent.type, tab2Loaded, true);
|
||||
|
||||
openConsole(gBrowser.selectedTab).then(() => {
|
||||
tab1.linkedBrowser.addEventListener("load", tab1Reloaded, true);
|
||||
tab1.linkedBrowser.contentWindow.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function tab1Reloaded(aEvent) {
|
||||
tab1.linkedBrowser.removeEventListener(aEvent.type, tab1Reloaded, true);
|
||||
|
||||
let hud1 = HUDService.getHudByWindow(tab1.linkedBrowser.contentWindow);
|
||||
function* checkMessages(tab1, tab2) {
|
||||
let hud1 = yield openConsole(tab1);
|
||||
let outputNode1 = hud1.outputNode;
|
||||
|
||||
waitForMessages({
|
||||
info("Waiting for messages");
|
||||
yield waitForMessages({
|
||||
webconsole: hud1,
|
||||
messages: [{
|
||||
text: TEST_IFRAME_URI,
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
}).then(() => {
|
||||
let hud2 = HUDService.getHudByWindow(tab2.linkedBrowser.contentWindow);
|
||||
let outputNode2 = hud2.outputNode;
|
||||
|
||||
isnot(outputNode1, outputNode2,
|
||||
"the two HUD outputNodes must be different");
|
||||
|
||||
let msg = "Didn't find the iframe network request in tab2";
|
||||
testLogEntry(outputNode2, TEST_IFRAME_URI, msg, true, true);
|
||||
|
||||
closeConsole(tab2).then(() => {
|
||||
gBrowser.removeTab(tab2);
|
||||
tab1 = tab2 = null;
|
||||
executeSoon(finishTest);
|
||||
});
|
||||
}]
|
||||
});
|
||||
|
||||
let hud2 = yield openConsole(tab2);
|
||||
let outputNode2 = hud2.outputNode;
|
||||
|
||||
isnot(outputNode1, outputNode2,
|
||||
"the two HUD outputNodes must be different");
|
||||
|
||||
let msg = "Didn't find the iframe network request in tab2";
|
||||
testLogEntry(outputNode2, TEST_IFRAME_URI, msg, true, true);
|
||||
}
|
||||
|
@ -8,9 +8,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,Bug 1005909 - Clickable URLS";
|
||||
|
||||
var inputTests = [
|
||||
const inputTests = [
|
||||
|
||||
// 0: URL opens page when clicked.
|
||||
{
|
||||
@ -96,11 +94,10 @@ var inputTests = [
|
||||
|
||||
];
|
||||
|
||||
function test() {
|
||||
Task.spawn(function*() {
|
||||
let {tab} = yield loadTab(TEST_URI);
|
||||
let hud = yield openConsole(tab);
|
||||
yield checkOutputForInputs(hud, inputTests);
|
||||
inputTests = null;
|
||||
}).then(finishTest);
|
||||
}
|
||||
const url = "data:text/html;charset=utf8,Bug 1005909 - Clickable URLS";
|
||||
|
||||
add_task(function* () {
|
||||
yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
|
||||
let hud = yield openConsole();
|
||||
yield checkOutputForInputs(hud, inputTests);
|
||||
});
|
||||
|
@ -95,7 +95,7 @@ ChildProcessActor.prototype = {
|
||||
|
||||
onListWorkers: function () {
|
||||
if (!this._workerList) {
|
||||
this._workerList = new WorkerActorList({});
|
||||
this._workerList = new WorkerActorList(this.conn, {});
|
||||
}
|
||||
return this._workerList.getList().then(actors => {
|
||||
let pool = new ActorPool(this.conn);
|
||||
|
@ -131,8 +131,9 @@ function createRootActor(aConnection)
|
||||
{
|
||||
tabList: new BrowserTabList(aConnection),
|
||||
addonList: new BrowserAddonList(aConnection),
|
||||
workerList: new WorkerActorList({}),
|
||||
serviceWorkerRegistrationList: new ServiceWorkerRegistrationActorList(),
|
||||
workerList: new WorkerActorList(aConnection, {}),
|
||||
serviceWorkerRegistrationList:
|
||||
new ServiceWorkerRegistrationActorList(aConnection),
|
||||
processList: new ProcessActorList(),
|
||||
globalActorFactories: DebuggerServer.globalActorFactories,
|
||||
onShutdown: sendShutdownEvent
|
||||
@ -1098,7 +1099,7 @@ TabActor.prototype = {
|
||||
}
|
||||
|
||||
if (this._workerActorList === null) {
|
||||
this._workerActorList = new WorkerActorList({
|
||||
this._workerActorList = new WorkerActorList(this.conn, {
|
||||
type: Ci.nsIWorkerDebugger.TYPE_DEDICATED,
|
||||
window: this.window
|
||||
});
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
var { Ci, Cu } = require("chrome");
|
||||
var { DebuggerServer } = require("devtools/server/main");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { Arg, method, RetVal } = protocol;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
@ -35,31 +37,41 @@ function matchWorkerDebugger(dbg, options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function WorkerActor(dbg) {
|
||||
this._dbg = dbg;
|
||||
this._isAttached = false;
|
||||
this._threadActor = null;
|
||||
this._transport = null;
|
||||
}
|
||||
let WorkerActor = protocol.ActorClass({
|
||||
typeName: "worker",
|
||||
|
||||
WorkerActor.prototype = {
|
||||
actorPrefix: "worker",
|
||||
initialize: function (conn, dbg) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this._dbg = dbg;
|
||||
this._attached = false;
|
||||
this._threadActor = null;
|
||||
this._transport = null;
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
form: function () {
|
||||
return {
|
||||
form: function (detail) {
|
||||
if (detail === "actorid") {
|
||||
return this.actorID;
|
||||
}
|
||||
let form = {
|
||||
actor: this.actorID,
|
||||
consoleActor: this._consoleActor,
|
||||
url: this._dbg.url,
|
||||
type: this._dbg.type
|
||||
};
|
||||
if (this._dbg.type === Ci.nsIWorkerDebugger.TYPE_SERVICE) {
|
||||
let registration = this._getServiceWorkerRegistrationInfo();
|
||||
form.scope = registration.scope;
|
||||
}
|
||||
return form;
|
||||
},
|
||||
|
||||
onAttach: function () {
|
||||
attach: method(function () {
|
||||
if (this._dbg.isClosed) {
|
||||
return { error: "closed" };
|
||||
}
|
||||
|
||||
if (!this._isAttached) {
|
||||
if (!this._attached) {
|
||||
// Automatically disable their internal timeout that shut them down
|
||||
// Should be refactored by having actors specific to service workers
|
||||
if (this._dbg.type == Ci.nsIWorkerDebugger.TYPE_SERVICE) {
|
||||
@ -69,27 +81,33 @@ WorkerActor.prototype = {
|
||||
}
|
||||
}
|
||||
this._dbg.addListener(this);
|
||||
this._isAttached = true;
|
||||
this._attached = true;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "attached",
|
||||
url: this._dbg.url
|
||||
};
|
||||
},
|
||||
}, {
|
||||
request: {},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
onDetach: function () {
|
||||
if (!this._isAttached) {
|
||||
detach: method(function () {
|
||||
if (!this._attached) {
|
||||
return { error: "wrongState" };
|
||||
}
|
||||
|
||||
this._detach();
|
||||
|
||||
return { type: "detached" };
|
||||
},
|
||||
}, {
|
||||
request: {},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
onConnect: function (request) {
|
||||
if (!this._isAttached) {
|
||||
connect: method(function (options) {
|
||||
if (!this._attached) {
|
||||
return { error: "wrongState" };
|
||||
}
|
||||
|
||||
@ -101,7 +119,7 @@ WorkerActor.prototype = {
|
||||
}
|
||||
|
||||
return DebuggerServer.connectToWorker(
|
||||
this.conn, this._dbg, this.actorID, request.options
|
||||
this.conn, this._dbg, this.actorID, options
|
||||
).then(({ threadActor, transport, consoleActor }) => {
|
||||
this._threadActor = threadActor;
|
||||
this._transport = transport;
|
||||
@ -115,10 +133,15 @@ WorkerActor.prototype = {
|
||||
}, (error) => {
|
||||
return { error: error.toString() };
|
||||
});
|
||||
},
|
||||
}, {
|
||||
request: {
|
||||
options: Arg(0, "json"),
|
||||
},
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
onClose: function () {
|
||||
if (this._isAttached) {
|
||||
if (this._attached) {
|
||||
this._detach();
|
||||
}
|
||||
|
||||
@ -129,9 +152,13 @@ WorkerActor.prototype = {
|
||||
reportError("ERROR:" + filename + ":" + lineno + ":" + message + "\n");
|
||||
},
|
||||
|
||||
_getServiceWorkerRegistrationInfo() {
|
||||
return swm.getRegistrationByPrincipal(this._dbg.principal, this._dbg.url);
|
||||
},
|
||||
|
||||
_getServiceWorkerInfo: function () {
|
||||
let info = swm.getRegistrationByPrincipal(this._dbg.principal, this._dbg.url);
|
||||
return info.getWorkerByID(this._dbg.serviceWorkerID);
|
||||
let registration = this._getServiceWorkerRegistrationInfo();
|
||||
return registration.getWorkerByID(this._dbg.serviceWorkerID);
|
||||
},
|
||||
|
||||
_detach: function () {
|
||||
@ -156,19 +183,14 @@ WorkerActor.prototype = {
|
||||
}
|
||||
|
||||
this._dbg.removeListener(this);
|
||||
this._isAttached = false;
|
||||
this._attached = false;
|
||||
}
|
||||
};
|
||||
|
||||
WorkerActor.prototype.requestTypes = {
|
||||
"attach": WorkerActor.prototype.onAttach,
|
||||
"detach": WorkerActor.prototype.onDetach,
|
||||
"connect": WorkerActor.prototype.onConnect
|
||||
};
|
||||
});
|
||||
|
||||
exports.WorkerActor = WorkerActor;
|
||||
|
||||
function WorkerActorList(options) {
|
||||
function WorkerActorList(conn, options) {
|
||||
this._conn = conn;
|
||||
this._options = options;
|
||||
this._actors = new Map();
|
||||
this._onListChanged = null;
|
||||
@ -199,7 +221,7 @@ WorkerActorList.prototype = {
|
||||
// Create an actor for each debugger for which we don't have one.
|
||||
for (let dbg of dbgs) {
|
||||
if (!this._actors.has(dbg)) {
|
||||
this._actors.set(dbg, new WorkerActor(dbg));
|
||||
this._actors.set(dbg, new WorkerActor(this._conn, dbg));
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,22 +287,30 @@ WorkerActorList.prototype = {
|
||||
|
||||
exports.WorkerActorList = WorkerActorList;
|
||||
|
||||
function ServiceWorkerRegistrationActor(registration) {
|
||||
this._registration = registration;
|
||||
};
|
||||
let ServiceWorkerRegistrationActor = protocol.ActorClass({
|
||||
typeName: "serviceWorkerRegistration",
|
||||
|
||||
ServiceWorkerRegistrationActor.prototype = {
|
||||
actorPrefix: "serviceWorkerRegistration",
|
||||
initialize: function(conn, registration) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this._registration = registration;
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
form: function () {
|
||||
form: function(detail) {
|
||||
if (detail === "actorid") {
|
||||
return this.actorID;
|
||||
}
|
||||
return {
|
||||
actor: this.actorID,
|
||||
scope: this._registration.scope
|
||||
scope: this._registration.scope,
|
||||
url: this._registration.scriptSpec
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
function ServiceWorkerRegistrationActorList() {
|
||||
});
|
||||
|
||||
function ServiceWorkerRegistrationActorList(conn) {
|
||||
this._conn = conn;
|
||||
this._actors = new Map();
|
||||
this._onListChanged = null;
|
||||
this._mustNotify = false;
|
||||
@ -309,7 +339,7 @@ ServiceWorkerRegistrationActorList.prototype = {
|
||||
for (let registration of registrations) {
|
||||
if (!this._actors.has(registration)) {
|
||||
this._actors.set(registration,
|
||||
new ServiceWorkerRegistrationActor(registration));
|
||||
new ServiceWorkerRegistrationActor(this._conn, registration));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1416,6 +1416,9 @@ WorkerClient.prototype = {
|
||||
|
||||
detach: DebuggerClient.requester({ type: "detach" }, {
|
||||
after: function (aResponse) {
|
||||
if (this.thread) {
|
||||
this.client.unregisterClient(this.thread);
|
||||
}
|
||||
this.client.unregisterClient(this);
|
||||
return aResponse;
|
||||
},
|
||||
|
@ -384,9 +384,17 @@ GlobalManager = {
|
||||
injectAPI(api, browserObj);
|
||||
|
||||
let schemaApi = Management.generateAPIs(extension, context, Management.schemaApis);
|
||||
|
||||
// Add in any extra API namespaces which do not have implementations
|
||||
// outside of their schema file.
|
||||
schemaApi.extensionTypes = {};
|
||||
|
||||
function findPath(path) {
|
||||
let obj = schemaApi;
|
||||
for (let elt of path) {
|
||||
if (!(elt in obj)) {
|
||||
return null;
|
||||
}
|
||||
obj = obj[elt];
|
||||
}
|
||||
return obj;
|
||||
@ -423,6 +431,10 @@ GlobalManager = {
|
||||
return context.wrapPromise(promise || Promise.resolve(), callback);
|
||||
},
|
||||
|
||||
shouldInject(path, name) {
|
||||
return findPath(path) != null;
|
||||
},
|
||||
|
||||
getProperty(path, name) {
|
||||
return findPath(path)[name];
|
||||
},
|
||||
|
@ -120,6 +120,11 @@ var api = context => {
|
||||
return context.extension.localizeMessage(messageName, substitutions);
|
||||
},
|
||||
|
||||
getAcceptLanguages: function(callback) {
|
||||
let result = context.extension.localeData.acceptLanguages;
|
||||
return context.wrapPromise(Promise.resolve(result), callback);
|
||||
},
|
||||
|
||||
getUILanguage: function() {
|
||||
return context.extension.localeData.uiLocale;
|
||||
},
|
||||
|
@ -20,6 +20,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
|
||||
"resource:///modules/translation/LanguageDetector.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
|
||||
"resource://gre/modules/Locale.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
|
||||
"resource://gre/modules/Preferences.jsm");
|
||||
|
||||
function filterStack(error) {
|
||||
return String(error.stack).replace(/(^.*(Task\.jsm|Promise-backend\.js).*\n)+/gm, "<Promise Chain>\n");
|
||||
@ -429,6 +431,16 @@ LocaleData.prototype = {
|
||||
return result;
|
||||
},
|
||||
|
||||
get acceptLanguages() {
|
||||
let result = Preferences.get("intl.accept_languages", "", Ci.nsIPrefLocalizedString);
|
||||
result = result.split(",");
|
||||
result = result.map(lang => {
|
||||
return lang.replace(/-/g, "_").trim();
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
get uiLocale() {
|
||||
// Return the browser locale, but convert it to a Chrome-style
|
||||
// locale code.
|
||||
|
@ -1319,7 +1319,9 @@ this.Schemas = {
|
||||
for (let [namespace, ns] of this.namespaces) {
|
||||
let obj = Cu.createObjectIn(dest, {defineAs: namespace});
|
||||
for (let [name, entry] of ns) {
|
||||
entry.inject([namespace], name, obj, new Context(wrapperFuncs));
|
||||
if (wrapperFuncs.shouldInject([namespace], name)) {
|
||||
entry.inject([namespace], name, obj, new Context(wrapperFuncs));
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.keys(obj).length) {
|
||||
|
@ -2,14 +2,104 @@
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
|
||||
"resource://gre/modules/DownloadPaths.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
const {
|
||||
ignoreEvent,
|
||||
} = ExtensionUtils;
|
||||
|
||||
let currentId = 0;
|
||||
|
||||
extensions.registerSchemaAPI("downloads", "downloads", (extension, context) => {
|
||||
return {
|
||||
downloads: {
|
||||
download(options) {
|
||||
if (options.filename != null) {
|
||||
if (options.filename.length == 0) {
|
||||
return Promise.reject({message: "filename must not be empty"});
|
||||
}
|
||||
|
||||
let path = OS.Path.split(options.filename);
|
||||
if (path.absolute) {
|
||||
return Promise.reject({message: "filename must not be an absolute path"});
|
||||
}
|
||||
|
||||
if (path.components.some(component => component == "..")) {
|
||||
return Promise.reject({message: "filename must not contain back-references (..)"});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.conflictAction == "prompt") {
|
||||
// TODO
|
||||
return Promise.reject({message: "conflictAction prompt not yet implemented"});
|
||||
}
|
||||
|
||||
function createTarget(downloadsDir) {
|
||||
// TODO
|
||||
// if (options.saveAs) { }
|
||||
|
||||
let target;
|
||||
if (options.filename) {
|
||||
target = OS.Path.join(downloadsDir, options.filename);
|
||||
} else {
|
||||
let uri = NetUtil.newURI(options.url).QueryInterface(Ci.nsIURL);
|
||||
target = OS.Path.join(downloadsDir, uri.fileName);
|
||||
}
|
||||
|
||||
// This has a race, something else could come along and create
|
||||
// the file between this test and them time the download code
|
||||
// creates the target file. But we can't easily fix it without
|
||||
// modifying DownloadCore so we live with it for now.
|
||||
return OS.File.exists(target).then(exists => {
|
||||
if (exists) {
|
||||
switch (options.conflictAction) {
|
||||
case "uniquify":
|
||||
default:
|
||||
target = DownloadPaths.createNiceUniqueFile(new FileUtils.File(target)).path;
|
||||
break;
|
||||
|
||||
case "overwrite":
|
||||
break;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
});
|
||||
}
|
||||
|
||||
let download;
|
||||
return Downloads.getPreferredDownloadsDirectory()
|
||||
.then(downloadsDir => createTarget(downloadsDir))
|
||||
.then(target => Downloads.createDownload({
|
||||
source: options.url,
|
||||
target: target,
|
||||
})).then(dl => {
|
||||
download = dl;
|
||||
return Downloads.getList(Downloads.ALL);
|
||||
}).then(list => {
|
||||
list.add(download);
|
||||
|
||||
// This is necessary to make pause/resume work.
|
||||
download.tryToKeepPartialData = true;
|
||||
download.start();
|
||||
|
||||
// Without other chrome.downloads methods, we can't actually
|
||||
// do anything with the id so just return a dummy value for now.
|
||||
return currentId++;
|
||||
});
|
||||
},
|
||||
|
||||
// When we do open(), check for additional downloads.open permission.
|
||||
// i.e.:
|
||||
// open(downloadId) {
|
||||
|
@ -12,6 +12,11 @@ extensions.registerSchemaAPI("i18n", null, (extension, context) => {
|
||||
return extension.localizeMessage(messageName, substitutions);
|
||||
},
|
||||
|
||||
getAcceptLanguages: function() {
|
||||
let result = extension.localeData.acceptLanguages;
|
||||
return Promise.resolve(result);
|
||||
},
|
||||
|
||||
getUILanguage: function() {
|
||||
return extension.localeData.uiLocale;
|
||||
},
|
||||
|
@ -79,6 +79,7 @@ extensions.registerSchemaAPI("webNavigation", "webNavigation", (extension, conte
|
||||
onCompleted: new WebNavigationEventManager(context, "onCompleted").api(),
|
||||
onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
|
||||
onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
|
||||
onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
|
||||
onCreatedNavigationTarget: ignoreEvent(context, "webNavigation.onCreatedNavigationTarget"),
|
||||
getAllFrames(details) {
|
||||
let tab = TabManager.getTab(details.tabId);
|
||||
|
@ -19,4 +19,5 @@ DIRS += ['schemas']
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
|
||||
|
@ -22,7 +22,7 @@
|
||||
"id": "FilenameConflictAction",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"uniqify",
|
||||
"uniquify",
|
||||
"overwrite",
|
||||
"prompt"
|
||||
]
|
||||
@ -214,7 +214,7 @@
|
||||
{
|
||||
"name": "download",
|
||||
"type": "function",
|
||||
"unsupported": true,
|
||||
"async": "callback",
|
||||
"description": "Download a URL. If the URL uses the HTTP[S] protocol, then the request will include all cookies currently set for its hostname. If both <code>filename</code> and <code>saveAs</code> are specified, then the Save As dialog will be displayed, pre-populated with the specified <code>filename</code>. If the download started successfully, <code>callback</code> will be called with the new <a href='#type-DownloadItem'>DownloadItem</a>'s <code>downloadId</code>. If there was an error starting the download, then <code>callback</code> will be called with <code>downloadId=undefined</code> and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain a descriptive string. The error strings are not guaranteed to remain backwards compatible between releases. You must not parse it.",
|
||||
"parameters": [
|
||||
{
|
||||
@ -224,7 +224,8 @@
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "The URL to download.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "url"
|
||||
},
|
||||
"filename": {
|
||||
"description": "A file path relative to the Downloads directory to contain the downloaded file.",
|
||||
@ -236,11 +237,13 @@
|
||||
"optional": true
|
||||
},
|
||||
"saveAs": {
|
||||
"unsupported": true,
|
||||
"description": "Use a file-chooser to allow the user to select a filename.",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"method": {
|
||||
"unsupported": true,
|
||||
"description": "The HTTP method to use if the URL uses the HTTP[S] protocol.",
|
||||
"enum": [
|
||||
"GET",
|
||||
@ -250,6 +253,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"unsupported": true,
|
||||
"optional": true,
|
||||
"type": "array",
|
||||
"description": "Extra HTTP headers to send with the request if the URL uses the HTTP[s] protocol. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>, restricted to those allowed by XMLHttpRequest.",
|
||||
@ -268,6 +272,7 @@
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"unsupported": true,
|
||||
"description": "Post body.",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
|
@ -30,7 +30,6 @@
|
||||
"functions": [
|
||||
{
|
||||
"name": "getAcceptLanguages",
|
||||
"unsupported": true,
|
||||
"type": "function",
|
||||
"description": "Gets the accept-languages of the browser. This is different from the locale used by the browser; to get the locale, use $(ref:i18n.getUILanguage).",
|
||||
"async": "callback",
|
||||
|
@ -345,7 +345,6 @@
|
||||
},
|
||||
{
|
||||
"name": "onHistoryStateUpdated",
|
||||
"unsupported": true,
|
||||
"type": "function",
|
||||
"description": "Fired when the frame's history was updated to a new URL. All future events for that frame will use the updated URL.",
|
||||
"filters": [
|
||||
|
6
toolkit/components/extensions/test/mochitest/chrome.ini
Normal file
6
toolkit/components/extensions/test/mochitest/chrome.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
skip-if = os == 'android'
|
||||
support-files =
|
||||
file_download.txt
|
||||
|
||||
[test_chrome_ext_downloads_download.html]
|
@ -0,0 +1 @@
|
||||
This is a sample file used in download tests.
|
@ -0,0 +1,221 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebExtension test</title>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
interfaces: Ci,
|
||||
utils: Cu,
|
||||
} = Components;
|
||||
|
||||
/* global OS */
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Downloads.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const WINDOWS = (AppConstants.platform == "win");
|
||||
|
||||
const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
|
||||
const FILE_NAME = "file_download.txt";
|
||||
const FILE_URL = BASE + "/" + FILE_NAME;
|
||||
const FILE_NAME_UNIQUE = "file_download(1).txt";
|
||||
const FILE_LEN = 46;
|
||||
|
||||
let downloadDir;
|
||||
|
||||
function setup() {
|
||||
downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
|
||||
downloadDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
info(`Using download directory ${downloadDir.path}`);
|
||||
|
||||
Services.prefs.setIntPref("browser.download.folderList", 2);
|
||||
Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir);
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.download.folderList");
|
||||
Services.prefs.clearUserPref("browser.download.dir");
|
||||
});
|
||||
}
|
||||
|
||||
function backgroundScript() {
|
||||
browser.test.onMessage.addListener(function(msg) {
|
||||
if (msg == "download.request") {
|
||||
// download() throws on bad arguments, we can remove the extra
|
||||
// promise when bug 1250223 is fixed.
|
||||
return Promise.resolve().then(() => browser.downloads.download(arguments[1]))
|
||||
.then((id) => browser.test.sendMessage("download.done", {status: "success", id}))
|
||||
.catch(error => browser.test.sendMessage("download.done", {status: "error", errmsg: error.message}));
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
// This function is a bit of a sledgehammer, it looks at every download
|
||||
// the browser knows about and waits for all active downloads to complete.
|
||||
// But we only start one at a time and only do a handful in total, so
|
||||
// this lets us test download() without depending on anything else.
|
||||
function waitForDownloads() {
|
||||
return Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.getAll())
|
||||
.then(downloads => {
|
||||
let inprogress = downloads.filter(dl => !dl.stopped);
|
||||
return Promise.all(inprogress.map(dl => dl.whenSucceeded()));
|
||||
});
|
||||
}
|
||||
|
||||
// Create a file in the downloads directory.
|
||||
function touch(filename) {
|
||||
let file = downloadDir.clone();
|
||||
file.append(filename);
|
||||
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
|
||||
}
|
||||
|
||||
// Remove a file in the downloads directory.
|
||||
function remove(filename) {
|
||||
let file = downloadDir.clone();
|
||||
file.append(filename);
|
||||
file.remove(false);
|
||||
}
|
||||
|
||||
add_task(function* test_downloads() {
|
||||
setup();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["downloads"],
|
||||
},
|
||||
});
|
||||
|
||||
function download(options) {
|
||||
extension.sendMessage("download.request", options);
|
||||
return extension.awaitMessage("download.done");
|
||||
}
|
||||
|
||||
function testDownload(options, localFile, expectedSize, description) {
|
||||
return download(options).then(msg => {
|
||||
is(msg.status, "success", `downloads.download() works with ${description}`);
|
||||
return waitForDownloads();
|
||||
}).then(() => {
|
||||
let localPath = downloadDir.clone();
|
||||
localPath.append(localFile);
|
||||
is(localPath.fileSize, expectedSize, "Downloaded file has expected size");
|
||||
localPath.remove(false);
|
||||
});
|
||||
}
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
info("extension started");
|
||||
|
||||
// Call download() with just the url property.
|
||||
yield testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source");
|
||||
|
||||
// Call download() with a filename property.
|
||||
yield testDownload({
|
||||
url: FILE_URL,
|
||||
filename: "newpath.txt",
|
||||
}, "newpath.txt", FILE_LEN, "source and filename");
|
||||
|
||||
// Check conflictAction of "uniquify".
|
||||
touch(FILE_NAME);
|
||||
yield testDownload({
|
||||
url: FILE_URL,
|
||||
conflictAction: "uniquify",
|
||||
}, FILE_NAME_UNIQUE, FILE_LEN, "conflictAction=uniquify");
|
||||
// todo check that preexisting file was not modified?
|
||||
remove(FILE_NAME);
|
||||
|
||||
// Check conflictAction of "overwrite".
|
||||
touch(FILE_NAME);
|
||||
yield testDownload({
|
||||
url: FILE_URL,
|
||||
conflictAction: "overwrite",
|
||||
}, FILE_NAME, FILE_LEN, "conflictAction=overwrite");
|
||||
|
||||
// Try to download in invalid url
|
||||
yield download({url: "this is not a valid URL"}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with invalid url");
|
||||
ok(/not a valid URL/.test(msg.errmsg), "error message for invalid url is correct");
|
||||
});
|
||||
|
||||
// Try to download to an empty path.
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: "",
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with empty filename");
|
||||
is(msg.errmsg, "filename must not be empty", "error message for empty filename is correct");
|
||||
});
|
||||
|
||||
// Try to download to an absolute path.
|
||||
const absolutePath = OS.Path.join(WINDOWS ? "\\tmp" : "/tmp", "file_download.txt");
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: absolutePath,
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with absolute filename");
|
||||
is(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`);
|
||||
});
|
||||
|
||||
if (WINDOWS) {
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: "C:\\file_download.txt",
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with absolute filename");
|
||||
is(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct");
|
||||
});
|
||||
}
|
||||
|
||||
// Try to download to a relative path containing ..
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: OS.Path.join("..", "file_download.txt"),
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with back-references");
|
||||
is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
|
||||
});
|
||||
|
||||
// Try to download to a long relative path containing ..
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: OS.Path.join("foo", "..", "..", "file_download.txt"),
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with back-references");
|
||||
is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
|
||||
});
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
// check for leftover files in the download directory
|
||||
add_task(function*() {
|
||||
let entries = downloadDir.directoryEntries;
|
||||
while (entries.hasMoreElements()) {
|
||||
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
|
||||
ok(false, `Leftover file ${entry.path} in download directory`);
|
||||
entry.remove(false);
|
||||
}
|
||||
|
||||
downloadDir.remove(false);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -15,10 +15,8 @@
|
||||
|
||||
add_task(function* test_alarm_without_permissions() {
|
||||
function backgroundScript() {
|
||||
browser.test.log("running alarm script");
|
||||
|
||||
browser.test.assertTrue(!browser.alarms,
|
||||
"alarm API should not be available if the alarm permission is not required");
|
||||
"alarm API is not available when the alarm permission is not required");
|
||||
browser.test.notifyPass("alarms_permission");
|
||||
}
|
||||
|
||||
@ -30,25 +28,22 @@ add_task(function* test_alarm_without_permissions() {
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
info("extension loaded");
|
||||
yield extension.awaitFinish("alarms_permission");
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_alarm_fires() {
|
||||
function backgroundScript() {
|
||||
let ALARM_NAME = "test_ext_alarms";
|
||||
browser.test.log("running alarm script");
|
||||
|
||||
chrome.alarms.onAlarm.addListener(function(alarm) {
|
||||
browser.test.assertEq(alarm.name, ALARM_NAME, "alarm should have the correct name");
|
||||
browser.alarms.onAlarm.addListener(alarm => {
|
||||
browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the correct name");
|
||||
browser.test.notifyPass("alarms");
|
||||
});
|
||||
chrome.alarms.create(ALARM_NAME, {delayInMinutes: 0.02});
|
||||
browser.alarms.create(ALARM_NAME, {delayInMinutes: 0.02});
|
||||
setTimeout(() => {
|
||||
browser.test.notifyFail("alarms test failed, took too long");
|
||||
browser.test.fail("alarm fired within expected time");
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
@ -60,33 +55,146 @@ add_task(function* test_alarm_fires() {
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
info("extension loaded");
|
||||
yield extension.awaitFinish("alarms");
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_cleared_alarm_does_not_fire() {
|
||||
function backgroundScript() {
|
||||
let ALARM_NAME = "test_ext_alarms";
|
||||
|
||||
browser.alarms.onAlarm.addListener(alarm => {
|
||||
browser.test.fail("cleared alarm does not fire");
|
||||
});
|
||||
browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
|
||||
browser.alarms.clear(ALARM_NAME, wasCleared => {
|
||||
browser.test.assertTrue(wasCleared, "alarm was cleared");
|
||||
setTimeout(() => {
|
||||
browser.test.notifyPass("alarms");
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["alarms"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("alarms");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_alarm_fires_with_when() {
|
||||
function backgroundScript() {
|
||||
let ALARM_NAME = "test_ext_alarms";
|
||||
|
||||
browser.alarms.onAlarm.addListener(alarm => {
|
||||
browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name");
|
||||
browser.test.notifyPass("alarms");
|
||||
});
|
||||
browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
|
||||
setTimeout(() => {
|
||||
browser.test.fail("alarm fired within expected time");
|
||||
browser.alarms.clear(ALARM_NAME, (wasCleared) => {
|
||||
browser.test.assertTrue(wasCleared, "alarm was cleared");
|
||||
});
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["alarms"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("alarms");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_alarm_clear_non_matching_name() {
|
||||
function backgroundScript() {
|
||||
let ALARM_NAME = "test_ext_alarms";
|
||||
|
||||
browser.alarms.create(ALARM_NAME, {when: Date.now() + 2000});
|
||||
|
||||
browser.alarms.clear(ALARM_NAME + "1", wasCleared => {
|
||||
browser.test.assertFalse(wasCleared, "alarm was not cleared");
|
||||
browser.alarms.getAll(alarms => {
|
||||
browser.test.assertEq(1, alarms.length, "alarm was not removed");
|
||||
browser.test.notifyPass("alarms");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["alarms"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("alarms");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_alarm_get_and_clear_single_argument() {
|
||||
function backgroundScript() {
|
||||
browser.alarms.create({when: Date.now() + 2000});
|
||||
|
||||
browser.alarms.get(alarm => {
|
||||
browser.test.assertEq("", alarm.name, "expected alarm returned");
|
||||
browser.alarms.clear(wasCleared => {
|
||||
browser.test.assertTrue(wasCleared, "alarm was cleared");
|
||||
browser.alarms.getAll(alarms => {
|
||||
browser.test.assertEq(0, alarms.length, "alarm was removed");
|
||||
browser.test.notifyPass("alarms");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["alarms"],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("alarms");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_periodic_alarm_fires() {
|
||||
function backgroundScript() {
|
||||
const ALARM_NAME = "test_ext_alarms";
|
||||
browser.test.log("running alarm script");
|
||||
|
||||
let count = 0;
|
||||
chrome.alarms.onAlarm.addListener(function(alarm) {
|
||||
browser.test.assertEq(alarm.name, ALARM_NAME, "alarm should have the correct name");
|
||||
browser.alarms.onAlarm.addListener(alarm => {
|
||||
browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name");
|
||||
if (count++ === 3) {
|
||||
chrome.alarms.clear(ALARM_NAME, (wasCleared) => {
|
||||
browser.test.assertTrue(wasCleared, "alarm should be cleared");
|
||||
browser.alarms.clear(ALARM_NAME, (wasCleared) => {
|
||||
browser.test.assertTrue(wasCleared, "alarm was cleared");
|
||||
browser.test.notifyPass("alarms");
|
||||
});
|
||||
}
|
||||
});
|
||||
chrome.alarms.create(ALARM_NAME, {periodInMinutes: 0.02});
|
||||
browser.alarms.create(ALARM_NAME, {periodInMinutes: 0.02});
|
||||
setTimeout(() => {
|
||||
browser.test.notifyFail("alarms test failed, took too long");
|
||||
chrome.alarms.clear(ALARM_NAME, (wasCleared) => {
|
||||
browser.test.assertTrue(wasCleared, "alarm should be cleared");
|
||||
browser.test.notify("alarm fired within expected time");
|
||||
browser.alarms.clear(ALARM_NAME, (wasCleared) => {
|
||||
browser.test.assertTrue(wasCleared, "alarm was cleared");
|
||||
});
|
||||
}, 30000);
|
||||
}
|
||||
@ -99,10 +207,8 @@ add_task(function* test_periodic_alarm_fires() {
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
info("extension loaded");
|
||||
yield extension.awaitFinish("alarms");
|
||||
yield extension.unload();
|
||||
info("extension unloaded");
|
||||
});
|
||||
|
||||
|
||||
@ -129,13 +235,13 @@ add_task(function* test_get_get_all_clear_all_alarms() {
|
||||
promiseAlarms.getAll().then(alarms => {
|
||||
browser.test.assertEq(suffixes.length, alarms.length);
|
||||
alarms.forEach((alarm, index) => {
|
||||
browser.test.assertEq(ALARM_NAME + index, alarm.name, "expected alarm returned");
|
||||
browser.test.assertEq(ALARM_NAME + index, alarm.name, "alarm has the expected name");
|
||||
});
|
||||
|
||||
return Promise.all(
|
||||
suffixes.map(suffix => {
|
||||
return promiseAlarms.get(ALARM_NAME + suffix).then(alarm => {
|
||||
browser.test.assertEq(ALARM_NAME + suffix, alarm.name, "expected alarm returned");
|
||||
browser.test.assertEq(ALARM_NAME + suffix, alarm.name, "alarm has the expected name");
|
||||
browser.test.sendMessage(`get-${suffix}`);
|
||||
});
|
||||
}));
|
||||
@ -150,7 +256,7 @@ add_task(function* test_get_get_all_clear_all_alarms() {
|
||||
|
||||
return promiseAlarms.get(ALARM_NAME + suffixes[0]);
|
||||
}).then(alarm => {
|
||||
browser.test.assertEq(undefined, alarm, "non-existent alarm should be undefined");
|
||||
browser.test.assertEq(undefined, alarm, "non-existent alarm is undefined");
|
||||
browser.test.sendMessage(`get-invalid`);
|
||||
|
||||
return promiseAlarms.clearAll();
|
||||
|
@ -14,6 +14,7 @@
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => { SpecialPowers.clearUserPref("intl.accept_languages"); });
|
||||
SimpleTest.registerCleanupFunction(() => { SpecialPowers.clearUserPref("general.useragent.locale"); });
|
||||
|
||||
add_task(function* test_i18n() {
|
||||
@ -163,6 +164,85 @@ add_task(function* test_i18n() {
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
add_task(function* test_get_accept_languages() {
|
||||
function background() {
|
||||
function checkResults(source, results, expected) {
|
||||
browser.test.assertEq(
|
||||
expected.length,
|
||||
results.length,
|
||||
`got expected number of languages in ${source}`);
|
||||
results.forEach((lang, index) => {
|
||||
browser.test.assertEq(
|
||||
expected[index],
|
||||
lang,
|
||||
`got expected language in ${source}`);
|
||||
});
|
||||
}
|
||||
|
||||
let tabId;
|
||||
|
||||
browser.tabs.query({currentWindow: true, active: true}, tabs => {
|
||||
tabId = tabs[0].id;
|
||||
browser.test.sendMessage("ready");
|
||||
});
|
||||
|
||||
browser.test.onMessage.addListener(([msg, expected]) => {
|
||||
Promise.all([
|
||||
new Promise(
|
||||
resolve => browser.tabs.sendMessage(tabId, "get-results", resolve)),
|
||||
browser.i18n.getAcceptLanguages(),
|
||||
]).then(([contentResults, backgroundResults]) => {
|
||||
checkResults("contentScript", contentResults, expected);
|
||||
checkResults("background", backgroundResults, expected);
|
||||
|
||||
browser.test.sendMessage("done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function content() {
|
||||
browser.runtime.onMessage.addListener((msg, sender, respond) => {
|
||||
browser.i18n.getAcceptLanguages(respond);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"content_scripts": [{
|
||||
"matches": ["http://mochi.test/*/file_sample.html"],
|
||||
"run_at": "document_start",
|
||||
"js": ["content_script.js"],
|
||||
}],
|
||||
},
|
||||
|
||||
background: `(${background})()`,
|
||||
|
||||
files: {
|
||||
"content_script.js": `(${content})()`,
|
||||
},
|
||||
});
|
||||
|
||||
let win = window.open("file_sample.html");
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
|
||||
let expectedLangs = ["en_US", "en"];
|
||||
extension.sendMessage(["expect-results", expectedLangs]);
|
||||
yield extension.awaitMessage("done");
|
||||
|
||||
expectedLangs = ["en_US", "en", "fr_CA", "fr"];
|
||||
SpecialPowers.setCharPref("intl.accept_languages", expectedLangs.toString());
|
||||
extension.sendMessage(["expect-results", expectedLangs]);
|
||||
yield extension.awaitMessage("done");
|
||||
SpecialPowers.clearUserPref("intl.accept_languages");
|
||||
|
||||
win.close();
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
add_task(function* test_get_ui_language() {
|
||||
function getResults() {
|
||||
return {
|
||||
|
@ -16,18 +16,20 @@
|
||||
add_task(function* testEmptySchema() {
|
||||
function background() {
|
||||
browser.test.assertTrue(!("manifest" in browser), "browser.manifest is not defined");
|
||||
browser.test.assertTrue("storage" in browser, "browser.storage should be defined");
|
||||
browser.test.assertTrue(!("contextMenus" in browser), "browser.contextMenus should not be defined");
|
||||
browser.test.notifyPass("schema");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${background})()`,
|
||||
manifest: {
|
||||
permissions: ["storage"],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitFinish("schema");
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
|
@ -24,6 +24,7 @@ function backgroundScript() {
|
||||
"onCompleted",
|
||||
"onErrorOccurred",
|
||||
"onReferenceFragmentUpdated",
|
||||
"onHistoryStateUpdated",
|
||||
];
|
||||
|
||||
let expectedTabId = -1;
|
||||
@ -61,13 +62,14 @@ function backgroundScript() {
|
||||
browser.webNavigation[event].addListener(listeners[event]);
|
||||
}
|
||||
|
||||
browser.test.sendMessage("ready", browser.webRequest.ResourceType);
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
const BASE = "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest";
|
||||
const URL = BASE + "/file_WebNavigation_page1.html";
|
||||
const FRAME = BASE + "/file_WebNavigation_page2.html";
|
||||
const FRAME2 = BASE + "/file_WebNavigation_page3.html";
|
||||
const FRAME_PUSHSTATE = BASE + "/file_WebNavigation_page3_pushState.html";
|
||||
|
||||
const REQUIRED = [
|
||||
"onBeforeNavigate",
|
||||
@ -155,10 +157,71 @@ add_task(function* webnav_ordering() {
|
||||
|
||||
checkRequired(FRAME2);
|
||||
|
||||
yield loadAndWait(win, "onReferenceFragmentUpdated", FRAME2 + "#ref",
|
||||
() => { win.frames[0].document.getElementById("elt").click(); });
|
||||
let navigationSequence = [
|
||||
{
|
||||
action: () => { win.frames[0].document.getElementById("elt").click(); },
|
||||
waitURL: `${FRAME2}#ref`,
|
||||
expectedEvent: "onReferenceFragmentUpdated",
|
||||
description: "clicked an anchor link",
|
||||
},
|
||||
{
|
||||
action: () => { win.frames[0].history.pushState({}, "History PushState", `${FRAME2}#ref2`); },
|
||||
waitURL: `${FRAME2}#ref2`,
|
||||
expectedEvent: "onReferenceFragmentUpdated",
|
||||
description: "history.pushState, same pathname, different hash",
|
||||
},
|
||||
{
|
||||
action: () => { win.frames[0].history.pushState({}, "History PushState", `${FRAME2}#ref2`); },
|
||||
waitURL: `${FRAME2}#ref2`,
|
||||
expectedEvent: "onHistoryStateUpdated",
|
||||
description: "history.pushState, same pathname, same hash",
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
win.frames[0].history.pushState({}, "History PushState", `${FRAME2}?query_param1=value#ref2`);
|
||||
},
|
||||
waitURL: `${FRAME2}?query_param1=value#ref2`,
|
||||
expectedEvent: "onHistoryStateUpdated",
|
||||
description: "history.pushState, same pathname, same hash, different query params",
|
||||
},
|
||||
{
|
||||
action: () => {
|
||||
win.frames[0].history.pushState({}, "History PushState", `${FRAME2}?query_param2=value#ref3`);
|
||||
},
|
||||
waitURL: `${FRAME2}?query_param2=value#ref3`,
|
||||
expectedEvent: "onHistoryStateUpdated",
|
||||
description: "history.pushState, same pathname, different hash, different query params",
|
||||
},
|
||||
{
|
||||
action: () => { win.frames[0].history.pushState(null, "History PushState", FRAME_PUSHSTATE); },
|
||||
waitURL: FRAME_PUSHSTATE,
|
||||
expectedEvent: "onHistoryStateUpdated",
|
||||
description: "history.pushState, different pathname",
|
||||
},
|
||||
];
|
||||
|
||||
info("Received onReferenceFragmentUpdated from FRAME2");
|
||||
for (let navigation of navigationSequence) {
|
||||
let {expectedEvent, waitURL, action, description} = navigation;
|
||||
info(`Waiting ${expectedEvent} from ${waitURL} - ${description}`);
|
||||
yield loadAndWait(win, expectedEvent, waitURL, action);
|
||||
info(`Received ${expectedEvent} from ${waitURL} - ${description}`);
|
||||
}
|
||||
|
||||
for (let i = navigationSequence.length - 1; i > 0; i--) {
|
||||
let {waitURL: fromURL, expectedEvent} = navigationSequence[i];
|
||||
let {waitURL} = navigationSequence[i - 1];
|
||||
info(`Waiting ${expectedEvent} from ${waitURL} - history.back() from ${fromURL} to ${waitURL}`);
|
||||
yield loadAndWait(win, expectedEvent, waitURL, () => { win.frames[0].history.back(); });
|
||||
info(`Received ${expectedEvent} from ${waitURL} - history.back() from ${fromURL} to ${waitURL}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < navigationSequence.length - 1; i++) {
|
||||
let {waitURL: fromURL} = navigationSequence[i];
|
||||
let {waitURL, expectedEvent} = navigationSequence[i + 1];
|
||||
info(`Waiting ${expectedEvent} from ${waitURL} - history.forward() from ${fromURL} to ${waitURL}`);
|
||||
yield loadAndWait(win, expectedEvent, waitURL, () => { win.frames[0].history.forward(); });
|
||||
info(`Received ${expectedEvent} from ${waitURL} - history.forward() from ${fromURL} to ${waitURL}`);
|
||||
}
|
||||
|
||||
win.close();
|
||||
|
||||
|
@ -281,6 +281,18 @@ let json = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
namespace: "inject",
|
||||
properties: {
|
||||
PROP1: {value: "should inject"},
|
||||
},
|
||||
},
|
||||
{
|
||||
namespace: "do-not-inject",
|
||||
properties: {
|
||||
PROP1: {value: "should not inject"},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let tallied = null;
|
||||
@ -322,6 +334,11 @@ let wrapper = {
|
||||
tally("call", ns, name, args);
|
||||
},
|
||||
|
||||
shouldInject(path) {
|
||||
let ns = path.join(".");
|
||||
return ns != "do-not-inject";
|
||||
},
|
||||
|
||||
getProperty(path, name) {
|
||||
let ns = path.join(".");
|
||||
tally("get", ns, name);
|
||||
@ -358,6 +375,9 @@ add_task(function* () {
|
||||
do_check_eq(root.testing.type1.VALUE1, "value1", "enum type");
|
||||
do_check_eq(root.testing.type1.VALUE2, "value2", "enum type");
|
||||
|
||||
do_check_eq("inject" in root, true, "namespace 'inject' should be injected");
|
||||
do_check_eq("do-not-inject" in root, false, "namespace 'do-not-inject' should not be injected");
|
||||
|
||||
root.testing.foo(11, true);
|
||||
verify("call", "testing", "foo", [11, true]);
|
||||
|
||||
|
@ -842,6 +842,10 @@ this.FormHistory = {
|
||||
},
|
||||
|
||||
update : function formHistoryUpdate(aChanges, aCallbacks) {
|
||||
if (!Prefs.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Used to keep track of how many searches have been started. When that number
|
||||
// are finished, updateFormHistoryWrite can be called.
|
||||
let numSearches = 0;
|
||||
@ -856,13 +860,6 @@ this.FormHistory = {
|
||||
if (!("length" in aChanges))
|
||||
aChanges = [aChanges];
|
||||
|
||||
let isRemoveOperation = aChanges.every(change => change && change.op && change.op == "remove");
|
||||
if (!Prefs.enabled && !isRemoveOperation) {
|
||||
throw Components.Exception(
|
||||
"Form history is disabled, only remove operations are allowed",
|
||||
Cr.NS_ERROR_ILLEGAL_VALUE);
|
||||
}
|
||||
|
||||
for (let change of aChanges) {
|
||||
switch (change.op) {
|
||||
case "remove":
|
||||
|
@ -405,36 +405,6 @@ add_task(function* ()
|
||||
|
||||
yield promiseCountEntries(null, null, num => do_check_eq(num, 4));
|
||||
|
||||
// ===== 21 =====
|
||||
// Check update throws if form history is disabled and the operation is not a
|
||||
// pure removal.
|
||||
testnum++;
|
||||
Services.prefs.setBoolPref("browser.formfill.enable", false);
|
||||
Assert.throws(() => promiseUpdate(
|
||||
{ op : "bump", fieldname: "field5", value: "value5" }),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => promiseUpdate(
|
||||
{ op : "add", fieldname: "field5", value: "value5" }),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => promiseUpdate([
|
||||
{ op : "update", fieldname: "field5", value: "value5" },
|
||||
{ op : "remove", fieldname: "field5", value: "value5" }
|
||||
]),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => promiseUpdate([
|
||||
null,
|
||||
undefined,
|
||||
"",
|
||||
1,
|
||||
{},
|
||||
{ op : "remove", fieldname: "field5", value: "value5" }
|
||||
]),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
// Remove should work though.
|
||||
yield promiseUpdate([{ op: "remove", fieldname: "field5", value: null },
|
||||
{ op: "remove", fieldname: null, value: null }]);
|
||||
Services.prefs.clearUserPref("browser.formfill.enable");
|
||||
|
||||
} catch (e) {
|
||||
throw "FAILED in test #" + testnum + " -- " + e;
|
||||
}
|
||||
|
@ -99,8 +99,11 @@ var Manager = {
|
||||
|
||||
onLocationChange(browser, data) {
|
||||
let url = data.location;
|
||||
if (data.flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
|
||||
|
||||
if (data.isReferenceFragmentUpdated) {
|
||||
this.fire("onReferenceFragmentUpdated", browser, data, {url});
|
||||
} else if (data.isHistoryStateUpdated) {
|
||||
this.fire("onHistoryStateUpdated", browser, data, {url});
|
||||
} else {
|
||||
this.fire("onCommitted", browser, data, {url});
|
||||
}
|
||||
@ -142,9 +145,8 @@ const EVENTS = [
|
||||
"onCompleted",
|
||||
"onErrorOccurred",
|
||||
"onReferenceFragmentUpdated",
|
||||
|
||||
"onHistoryStateUpdated",
|
||||
// "onCreatedNavigationTarget",
|
||||
// "onHistoryStateUpdated",
|
||||
];
|
||||
|
||||
var WebNavigation = {};
|
||||
|
@ -25,6 +25,19 @@ addMessageListener("Extension:DisableWebNavigation", () => {
|
||||
|
||||
var WebProgressListener = {
|
||||
init: function() {
|
||||
// This WeakMap (DOMWindow -> nsIURI) keeps track of the pathname and hash
|
||||
// of the previous location for all the existent docShells.
|
||||
this.previousURIMap = new WeakMap();
|
||||
|
||||
// Populate the above previousURIMap by iterating over the docShells tree.
|
||||
for (let currentDocShell of WebNavigationFrames.iterateDocShellTree(docShell)) {
|
||||
let win = currentDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
let {currentURI} = currentDocShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
|
||||
this.previousURIMap.set(win, currentURI);
|
||||
}
|
||||
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
|
||||
@ -48,6 +61,7 @@ var WebProgressListener = {
|
||||
status,
|
||||
stateFlags,
|
||||
};
|
||||
|
||||
sendAsyncMessage("Extension:StateChange", data);
|
||||
|
||||
if (webProgress.DOMWindow.top != webProgress.DOMWindow) {
|
||||
@ -56,24 +70,51 @@ var WebProgressListener = {
|
||||
// For some reason we don't fire onLocationChange for the
|
||||
// initial navigation of a sub-frame. So we need to simulate
|
||||
// it here.
|
||||
let data = {
|
||||
location: request.QueryInterface(Ci.nsIChannel).URI.spec,
|
||||
windowId: webProgress.DOMWindowID,
|
||||
parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
|
||||
flags: 0,
|
||||
};
|
||||
sendAsyncMessage("Extension:LocationChange", data);
|
||||
this.onLocationChange(webProgress, request, request.QueryInterface(Ci.nsIChannel).URI, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
|
||||
let {DOMWindow, loadType} = webProgress;
|
||||
|
||||
// Get the previous URI loaded in the DOMWindow.
|
||||
let previousURI = this.previousURIMap.get(DOMWindow);
|
||||
|
||||
// Update the URI in the map with the new locationURI.
|
||||
this.previousURIMap.set(DOMWindow, locationURI);
|
||||
|
||||
let isSameDocument = (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
|
||||
let isHistoryStateUpdated = false;
|
||||
let isReferenceFragmentUpdated = false;
|
||||
|
||||
if (isSameDocument) {
|
||||
let pathChanged = !(previousURI && locationURI.equalsExceptRef(previousURI));
|
||||
let hashChanged = !(previousURI && previousURI.ref == locationURI.ref);
|
||||
|
||||
// When the location changes but the document is the same:
|
||||
// - path not changed and hash changed -> |onReferenceFragmentUpdated|
|
||||
// (even if it changed using |history.pushState|)
|
||||
// - path not changed and hash not changed -> |onHistoryStateUpdated|
|
||||
// (only if it changes using |history.pushState|)
|
||||
// - path changed -> |onHistoryStateUpdated|
|
||||
|
||||
if (!pathChanged && hashChanged) {
|
||||
isReferenceFragmentUpdated = true;
|
||||
} else if (loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE) {
|
||||
isHistoryStateUpdated = true;
|
||||
} else if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) {
|
||||
isHistoryStateUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
let data = {
|
||||
isHistoryStateUpdated, isReferenceFragmentUpdated,
|
||||
location: locationURI ? locationURI.spec : "",
|
||||
windowId: webProgress.DOMWindowID,
|
||||
parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
|
||||
flags,
|
||||
};
|
||||
|
||||
sendAsyncMessage("Extension:LocationChange", data);
|
||||
},
|
||||
|
||||
|
@ -97,6 +97,8 @@ function findFrame(windowId, rootDocShell) {
|
||||
}
|
||||
|
||||
var WebNavigationFrames = {
|
||||
iterateDocShellTree,
|
||||
|
||||
getFrame(docShell, frameId) {
|
||||
if (frameId == 0) {
|
||||
return convertDocShellToFrameDetail(docShell);
|
||||
|
Loading…
Reference in New Issue
Block a user