68.13 - devtools

This commit is contained in:
Fedor 2023-05-31 17:42:42 +03:00
parent ed530faa4c
commit f6176dd28b
1171 changed files with 43853 additions and 42086 deletions

View File

@ -7,7 +7,6 @@ module.exports = {
"globals": {
"exports": true,
"isWorker": true,
"isReplaying": true,
"loader": true,
"module": true,
"reportError": true,
@ -199,7 +198,7 @@ module.exports = {
// Require camel case names
"camelcase": ["error", { "properties": "never" }],
// Warn about cyclomatic complexity in functions.
"complexity": ["error", 53],
"complexity": ["error", 40],
// Don't warn for inconsistent naming when capturing this (not so important
// with auto-binding fat arrow functions).
"consistent-this": "off",

View File

@ -3,7 +3,7 @@
## What is about:debugging-new
The purpose of about:debugging is to be a debugging hub to start inspecting your addons, processes, tabs and workers. This new version of about:debugging will also allow you to debug remote devices (Firefox for Android on a smartphone). The user should be able to connect either via USB or WiFi. This solution is supposed to replace the various existing remote debugging solutions available in Firefox DevTools, WebIDE and the Connect page.
To try about:debugging-new, the preference `devtools.aboutdebugging.new-enabled` needs to be set to true in `about:config`. After that, the UI is available by typing `about:debugging` in the Firefox URL bar.
To try out about:debugging, type `about:debugging` in the Firefox URL bar.
## Technical overview
@ -16,7 +16,7 @@ The about:debugging-new UI is built using React and Redux. The various React/Red
The folder `devtools/client/aboutdebugging-new/src/modules` contains various helpers and classes that are not related to React/Redux. For instance modules/usb-runtimes.js provides an abstraction layer to enable USB runtimes scanning, to list USB runtimes etc...
### Firefox Component Registration
about:debugging-new is an "about" page registered via a component manifest that is located in `/devtools/startup/aboutdebugging.manifest`. The component registration code is at `/devtools/startup/aboutdebugging-registration.js` and mostly contains the logic to switch between the old and the new about:debugging UI, based on the value of the preference `devtools.aboutdebugging.new-enabled`.
about:debugging-new is an "about" page registered via a component manifest that is located in `/devtools/startup/aboutdebugging.manifest`. The component registration code is at `/devtools/startup/aboutdebugging-registration.js`.
### Actions
Actions should cover all user or external events that change the UI.

View File

@ -76,7 +76,10 @@ const AboutDebugging = {
const width = this.getRoundedViewportWidth();
this.actions.recordTelemetryEvent("open_adbg", { width });
await l10n.init();
await l10n.init([
"branding/brand.ftl",
"devtools/aboutdebugging.ftl",
]);
this.actions.createThisFirefoxRuntime();

View File

@ -182,7 +182,7 @@ function connectRuntime(id) {
if (runtime.type !== RUNTIMES.THIS_FIREFOX) {
// `closed` event will be emitted when disabling remote debugging
// on the connected remote runtime.
clientWrapper.addOneTimeListener(
clientWrapper.once(
"closed",
onRemoteDebuggerClientClosed
);
@ -239,7 +239,7 @@ function disconnectRuntime(id, shouldRedirect = false) {
}
if (runtime.type !== RUNTIMES.THIS_FIREFOX) {
clientWrapper.removeListener("closed", onRemoteDebuggerClientClosed);
clientWrapper.off("closed", onRemoteDebuggerClientClosed);
}
await clientWrapper.close();
if (shouldRedirect) {
@ -525,7 +525,7 @@ function removeRuntimeListeners() {
for (const runtime of remoteRuntimes) {
if (runtime.runtimeDetails) {
const { clientWrapper } = runtime.runtimeDetails;
clientWrapper.removeListener("closed", onRemoteDebuggerClientClosed);
clientWrapper.off("closed", onRemoteDebuggerClientClosed);
}
}
};

View File

@ -30,13 +30,13 @@ function debugTargetListenerMiddleware(store) {
const { clientWrapper } = runtime.runtimeDetails;
// Tabs
clientWrapper.addListener("tabListChanged", onTabsUpdated);
clientWrapper.on("tabListChanged", onTabsUpdated);
// Addons
clientWrapper.addListener("addonListChanged", onExtensionsUpdated);
clientWrapper.on("addonListChanged", onExtensionsUpdated);
// Workers
clientWrapper.addListener("workersUpdated", onWorkersUpdated);
clientWrapper.on("workersUpdated", onWorkersUpdated);
break;
}
case UNWATCH_RUNTIME_START: {
@ -44,13 +44,13 @@ function debugTargetListenerMiddleware(store) {
const { clientWrapper } = runtime.runtimeDetails;
// Tabs
clientWrapper.removeListener("tabListChanged", onTabsUpdated);
clientWrapper.off("tabListChanged", onTabsUpdated);
// Addons
clientWrapper.removeListener("addonListChanged", onExtensionsUpdated);
clientWrapper.off("addonListChanged", onExtensionsUpdated);
// Workers
clientWrapper.removeListener("workersUpdated", onWorkersUpdated);
clientWrapper.off("workersUpdated", onWorkersUpdated);
break;
}
}

View File

@ -9,7 +9,8 @@ const {
} = require("devtools/client/shared/remote-debugging/version-checker");
const { RUNTIME_PREFERENCE } = require("../constants");
const { WorkersListener } = require("./workers-listener");
const { WorkersListener } =
require("devtools/client/shared/workers-listener");
const PREF_TYPES = {
BOOL: "BOOL",
@ -37,31 +38,31 @@ class ClientWrapper {
this.workersListener = new WorkersListener(client.mainRoot);
}
addOneTimeListener(evt, listener) {
once(evt, listener) {
if (MAIN_ROOT_EVENTS.includes(evt)) {
this.client.mainRoot.once(evt, listener);
} else {
this.client.addOneTimeListener(evt, listener);
this.client.once(evt, listener);
}
}
addListener(evt, listener) {
on(evt, listener) {
if (evt === "workersUpdated") {
this.workersListener.addListener(listener);
} else if (MAIN_ROOT_EVENTS.includes(evt)) {
this.client.mainRoot.on(evt, listener);
} else {
this.client.addListener(evt, listener);
this.client.on(evt, listener);
}
}
removeListener(evt, listener) {
off(evt, listener) {
if (evt === "workersUpdated") {
this.workersListener.removeListener(listener);
} else if (MAIN_ROOT_EVENTS.includes(evt)) {
this.client.mainRoot.off(evt, listener);
} else {
this.client.removeListener(evt, listener);
this.client.off(evt, listener);
}
}

View File

@ -4,46 +4,7 @@
"use strict";
const Services = require("Services");
const { FluentL10n } = require("devtools/client/shared/fluent-l10n/fluent-l10n");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const { L10nRegistry } = require("resource://gre/modules/L10nRegistry.jsm");
class L10n {
async init() {
const locales = Services.locale.appLocalesAsBCP47;
const generator = L10nRegistry.generateBundles(locales, [
"branding/brand.ftl",
"devtools/aboutdebugging.ftl",
]);
this._bundles = [];
for await (const bundle of generator) {
this._bundles.push(bundle);
}
this._reactLocalization = new FluentReact.ReactLocalization(this._bundles);
}
/**
* Returns the fluent bundles generated for about:debugging.
*/
getBundles() {
return this._bundles;
}
/**
* Returns the localized string for the provided id, formatted using args.
*/
getString(id, args, fallback) {
// Forward arguments via .apply() so that the original method can:
// - perform asserts based on the number of arguments
// - add new arguments
return this._reactLocalization.getString.apply(
this._reactLocalization,
arguments
);
}
}
// Export a singleton that will be shared by all aboutdebugging modules.
exports.l10n = new L10n();
// exports a singleton, which will be used across all aboutdebugging-new modules
exports.l10n = new FluentL10n();

View File

@ -12,5 +12,4 @@ DevToolsModules(
'runtime-client-factory.js',
'runtimes-state-helper.js',
'usb-runtimes.js',
'workers-listener.js',
)

View File

@ -54,10 +54,8 @@ add_task(async () => {
info("Check whether the element displays correctly");
const sourceList = panelWin.document.querySelector(".sources-list");
ok(sourceList, "Source list element displays correctly");
ok(
sourceList.textContent.includes("moz-extension"),
"moz-extension displays correctly"
);
ok(sourceList.textContent.includes("temporary-web-extension"),
"Extension name displays correctly");
await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
await removeTemporaryExtension(EXTENSION_NAME, document);

View File

@ -39,13 +39,6 @@ registerCleanupFunction(async function() {
await remoteClientManager.removeAllClients();
});
/**
* Enable the new about:debugging panel.
*/
async function enableNewAboutDebugging() {
await pushPref("devtools.aboutdebugging.new-enabled", true);
}
async function openAboutDebugging({
enableWorkerUpdates,
enableLocalTabs = true,
@ -61,8 +54,6 @@ async function openAboutDebugging({
enableLocalTabs
);
await enableNewAboutDebugging();
info("opening about:debugging");
const tab = await addTab("about:debugging");

View File

@ -51,8 +51,7 @@ function installRegularExtension(pathOrFile) {
*/
async function installTemporaryExtension(pathOrFile, name, document) {
const { Management } = ChromeUtils.import(
"resource://gre/modules/Extension.jsm",
null
"resource://gre/modules/Extension.jsm"
);
info("Install temporary extension named " + name);
@ -80,8 +79,7 @@ async function installTemporaryExtension(pathOrFile, name, document) {
function createTemporaryXPI(xpiData) {
const { ExtensionTestCommon } = ChromeUtils.import(
"resource://testing-common/ExtensionTestCommon.jsm",
{}
"resource://testing-common/ExtensionTestCommon.jsm"
);
const { background, files, id, name, extraProperties } = xpiData;

View File

@ -224,12 +224,12 @@ const silenceWorkerUpdates = function() {
};
setMockedModule(
mock,
"devtools/client/aboutdebugging-new/src/modules/workers-listener"
"devtools/client/shared/workers-listener"
);
registerCleanupFunction(() => {
removeMockedModule(
"devtools/client/aboutdebugging-new/src/modules/workers-listener"
"devtools/client/shared/workers-listener"
);
});
};

View File

@ -33,23 +33,23 @@ function createClientMock() {
_preferences: {},
contentProcessFronts: [],
serviceWorkerRegistrationFronts: [],
addOneTimeListener: (evt, listener) => {
once: (evt, listener) => {
eventEmitter.once(evt, listener);
},
addListener: (evt, listener) => {
on: (evt, listener) => {
eventEmitter.on(evt, listener);
},
removeListener: (evt, listener) => {
off: (evt, listener) => {
eventEmitter.off(evt, listener);
},
client: {
addOneTimeListener: (evt, listener) => {
once: (evt, listener) => {
eventEmitter.once(evt, listener);
},
addListener: (evt, listener) => {
on: (evt, listener) => {
eventEmitter.on(evt, listener);
},
removeListener: (evt, listener) => {
off: (evt, listener) => {
eventEmitter.off(evt, listener);
},
},

View File

@ -13,10 +13,8 @@ let registration;
let subscription;
const registerServiceWorker = async function() {
await new Promise(resolve => {
const perm = { type: "desktop-notification", allow: true, context: document };
SpecialPowers.pushPermissions([perm], resolve);
});
const perm = { type: "desktop-notification", allow: true, context: document };
await SpecialPowers.pushPermissions([perm]);
try {
registration = await navigator.serviceWorker.register("push-sw.js");

View File

@ -6,7 +6,8 @@
"node": ">=8.9.4"
},
"scripts": {
"test": "jest"
"test": "jest",
"test-ci": "jest --json"
},
"dependencies": {
"jest": "^23.0.0",

View File

@ -1,395 +0,0 @@
/* 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/. */
html, body {
height: 100%;
width: 100%;
}
h2, h3, h4 {
margin-bottom: 10px;
}
button {
padding-left: 20px;
padding-right: 20px;
min-width: 100px;
margin: 0 4px;
}
/* Category panels */
#categories {
display: flex;
flex-direction: column;
}
.category {
align-items: center;
/* Override a `background-color` set on all buttons by common.inc.css */
background-color: transparent;
display: flex;
flex-direction: row;
/* Override button min-width set by common.inc.css for compact width case */
min-width: initial;
}
.category.selected {
/* Override a `color: inherit !important` forced on all buttons by common.inc.css */
color: var(--in-content-category-text-selected) !important;
}
.category-name {
cursor: default;
}
.app {
height: 100%;
width: 100%;
display: flex;
flex-direction: row;
}
.main-content {
flex: 1;
}
.panel {
max-width: 800px;
margin-bottom: 35px;
}
/* Targets */
.target-icon,
.addons-tip-icon,
.warning {
-moz-context-properties: fill;
fill: currentColor;
}
.target-list {
margin: 0;
padding: 0;
}
.target-container {
margin-top: 5px;
min-height: 34px;
display: flex;
flex-direction: row;
align-items: start;
}
.target-icon {
height: 24px;
width: 24px;
margin-inline-end: 5px;
}
.target-icon:not([src]) {
display: none;
}
.inverted-icons .target-icon {
filter: invert(30%);
}
.target {
flex: 1;
margin-top: 2px;
/* This is silly: https://bugzilla.mozilla.org/show_bug.cgi?id=1086218#c4. */
min-width: 0;
}
.target-details {
margin: 0;
padding: 0;
list-style-type: none
}
.target-detail {
display: flex;
font-size: 12px;
margin-top: 7px;
margin-bottom: 7px;
}
.target-detail a {
cursor: pointer;
white-space: nowrap;
}
.target-detail strong {
white-space: nowrap;
}
.target-detail span {
/* Truncate items that are too long (e.g. URLs that would break the UI). */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.target-detail > :not(:first-child) {
margin-inline-start: 8px;
}
.target-status {
box-sizing: border-box;
display: inline-block;
min-width: 50px;
margin-top: 4px;
margin-inline-end: 5px;
padding: 2px;
border-width: 1px;
border-style: solid;
font-size: 0.6em;
text-align: center;
}
.target-status-stopped {
color: var(--grey-90);
border-color: grey;
background-color: lightgrey;
}
.target-status-running {
color: var(--grey-90);
border-color: limegreen;
background-color: palegreen;
}
.target-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.addons-controls {
display: flex;
flex-direction: row;
}
.addons-install-error,
.service-worker-multi-process {
padding: 5px 10px;
margin-top: 5px;
margin-inline-end: 4px;
}
.addons-install-error {
align-items: center;
background-color: rgb(222, 33, 33, 0.3);
display: flex;
justify-content: space-between;
}
.service-worker-multi-process {
background-color: rgb(255, 191, 0, 0.3);
line-height: 1.5em;
}
.service-worker-multi-process .update-button {
margin: 5px 0;
}
.warning {
display: inline-block;
width: 12px;
height: 12px;
vertical-align: 0;
margin-inline-end: 8px;
background-image: url(chrome://devtools/skin/images/alert.svg);
background-repeat: no-repeat;
background-size: contain;
}
.addons-install-error__additional-errors {
font-family: monospace;
font-size: 13px;
margin-block: 8px;
}
.addons-options {
flex: 1;
}
.service-worker-disabled-label,
.addons-debugging-label,
.addons-web-ext-tip {
display: inline-block;
margin-inline-end: 1ch;
}
.addons-tip {
display: flex;
align-items: center;
margin-top: 1em;
margin-bottom: 1em;
height: 24px;
}
.addons-tip-icon {
width: 24px;
height: 24px;
background-image: url(chrome://devtools/skin/images/help.svg);
background-repeat: no-repeat;
flex-shrink: 0;
margin-inline-end: 8px;
}
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
}
.error-page .error-page-details {
color: gray;
}
#addons-panel h2 {
font-size: 1.5rem;
font-weight: bold;
}
.addon-target-container {
list-style-type: none;
margin: 16px 0;
}
.target-card-heading {
margin: 0;
}
.target-card-heading {
align-items: center;
display: flex;
margin: 0;
padding: 0 0 16px;
font-size: 15px;
font-weight: 600;
}
.target-card-heading-icon {
height: 24px;
width: 24px;
margin-inline-end: 16px;
}
.target-card-actions {
border-top: 1px solid var(--in-content-border-color);
padding-top: 16px;
}
.target-card-action-link {
background: none;
border: none;
/* !important overrides the common.css button color */
color: var(--in-content-link-color) !important;
font-size: 13px;
/* Use some negative top margin to account for the padding and keep the
* button vertically centred. */
margin: -4px 0 0 12px;
min-width: auto;
padding: 4px;
}
.target-card-action-link:first-of-type {
/* Other action buttons will have 12px of margin to space between each other,
* but for the first button subtract the start padding to align with the border. */
margin-inline-start: -4px;
}
.target-card-action-link:active,
.target-card-action-link:hover,
.target-card-action-link:enabled:hover:active {
background: none;
}
.target-card-action-link:disabled {
color: #999;
opacity: 1;
}
.target-card-action-link:enabled:focus,
.target-card-action-link:enabled:hover {
background: none;
color: var(--in-content-link-color-hover) !important;
cursor: pointer;
text-decoration: underline;
}
.target-card-action-link:enabled:hover:active {
color: var(--in-content-link-color-active) !important;
text-decoration: none;
}
.addon-target-icon.target-icon {
margin-inline-end: 16px;
}
.addon-target-info {
display: grid;
grid-template-columns: 128px 1fr;
}
.addon-target-info-label {
padding-inline-end: 8px;
padding-bottom: 8px;
}
.addon-target-info-label:last-of-type {
padding-bottom: 16px;
}
.addon-target-info-content {
margin: 0;
}
.addon-target-info-more {
padding-left: 1ch;
}
.addon-target-messages {
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
margin-bottom: 16px;
padding: 0 0 12px 0;
}
.addon-target-message {
list-style-type: none;
padding: 4px 0;
}
.addon-target-message:first-of-type {
padding-top: 0;
}
.addon-target-warning-message {
color: #a47f00;
}
/* We want the ellipsis on the left-hand-side, so make the parent RTL
* with an ellipsis and the child can be LTR. */
.file-path {
direction: rtl;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-path-inner {
direction: ltr;
unicode-bidi: plaintext;
}

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> %htmlDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD;
<!ENTITY % aboutdebuggingDTD SYSTEM "chrome://devtools/locale/aboutdebugging.dtd"> %aboutdebuggingDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&aboutDebugging.fullTitle;</title>
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/content/aboutdebugging/aboutdebugging.css" type="text/css"/>
<script src="resource://devtools/client/aboutdebugging/initializer.js"></script>
</head>
<body id="body" dir="&locale.dir;">
<div id="root"></div>
</body>
</html>

View File

@ -1,145 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const {
createFactory,
Component,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
const PanelMenu = createFactory(require("./PanelMenu"));
loader.lazyGetter(this, "AddonsPanel", () =>
createFactory(require("./addons/Panel"))
);
loader.lazyGetter(this, "TabsPanel", () =>
createFactory(require("./tabs/Panel"))
);
loader.lazyGetter(this, "WorkersPanel", () =>
createFactory(require("./workers/Panel"))
);
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
const panels = [
{
id: "addons",
name: Strings.GetStringFromName("addons"),
icon: "chrome://devtools/skin/images/debugging-addons.svg",
component: AddonsPanel,
},
{
id: "tabs",
name: Strings.GetStringFromName("tabs"),
icon: "chrome://devtools/skin/images/debugging-tabs.svg",
component: TabsPanel,
},
{
id: "workers",
name: Strings.GetStringFromName("workers"),
icon: "chrome://devtools/skin/images/debugging-workers.svg",
component: WorkersPanel,
},
];
const defaultPanelId = "addons";
class AboutDebuggingApp extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
connect: PropTypes.object.isRequired,
telemetry: PropTypes.instanceOf(Telemetry).isRequired,
};
}
constructor(props) {
super(props);
this.state = {
selectedPanelId: window.location.hash.substr(1) || defaultPanelId,
};
this.onHashChange = this.onHashChange.bind(this);
this.selectPanel = this.selectPanel.bind(this);
}
componentDidMount() {
window.addEventListener("hashchange", this.onHashChange);
// aboutdebugging is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this.props.telemetry.toolOpened("aboutdebugging", -1, this);
}
componentWillUnmount() {
window.removeEventListener("hashchange", this.onHashChange);
// aboutdebugging is not connected with a toolbox so we pass -1 as the
// toolbox session id.
this.props.telemetry.toolClosed("aboutdebugging", -1, this);
}
onHashChange() {
this.setState({
selectedPanelId: window.location.hash.substr(1) || defaultPanelId,
});
}
selectPanel(panelId) {
window.location.hash = "#" + panelId;
}
render() {
const { client, connect } = this.props;
const { selectedPanelId } = this.state;
const selectPanel = this.selectPanel;
const selectedPanel = panels.find(p => p.id == selectedPanelId);
let panel;
if (selectedPanel) {
panel = selectedPanel.component({
client,
connect,
id: selectedPanel.id,
});
} else {
panel = dom.div(
{ className: "error-page" },
dom.h1(
{ className: "header-name" },
Strings.GetStringFromName("pageNotFound")
),
dom.h4(
{ className: "error-page-details" },
Strings.formatStringFromName("doesNotExist", [selectedPanelId], 1)
)
);
}
return dom.div(
{ className: "app" },
PanelMenu({ panels, selectedPanelId, selectPanel }),
dom.div({ className: "main-content" }, panel)
);
}
}
module.exports = AboutDebuggingApp;

View File

@ -1,29 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
class PanelHeader extends Component {
static get propTypes() {
return {
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
};
}
render() {
const { name, id } = this.props;
return dom.div(
{ className: "header" },
dom.h1({ id, className: "header-name" }, name)
);
}
}
module.exports = PanelHeader;

View File

@ -1,50 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
Component,
createFactory,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PanelMenuEntry = createFactory(require("./PanelMenuEntry"));
class PanelMenu extends Component {
static get propTypes() {
return {
panels: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
component: PropTypes.func.isRequired,
})
).isRequired,
selectPanel: PropTypes.func.isRequired,
selectedPanelId: PropTypes.string,
};
}
render() {
const { panels, selectedPanelId, selectPanel } = this.props;
const panelLinks = panels.map(({ id, name, icon }) => {
const selected = id == selectedPanelId;
return PanelMenuEntry({
key: id,
id,
name,
icon,
selected,
selectPanel,
});
});
// "categories" id used for styling purposes
return dom.div({ id: "categories", role: "tablist" }, panelLinks);
}
}
module.exports = PanelMenu;

View File

@ -1,52 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
class PanelMenuEntry extends Component {
static get propTypes() {
return {
icon: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
selected: PropTypes.bool,
selectPanel: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
this.props.selectPanel(this.props.id);
}
render() {
const { id, name, icon, selected } = this.props;
// Here .category, .category-icon, .category-name classnames are used to
// apply common styles defined.
const className = "category" + (selected ? " selected" : "");
return dom.button(
{
"aria-selected": selected,
"aria-controls": id + "-panel",
className,
onClick: this.onClick,
tabIndex: "0",
role: "tab",
},
dom.img({ className: "category-icon", src: icon, role: "presentation" }),
dom.div({ className: "category-name" }, name)
);
}
}
module.exports = PanelMenuEntry;

View File

@ -1,83 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
const LocaleCompare = (a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
};
class TargetList extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
connect: PropTypes.object,
debugDisabled: PropTypes.bool,
error: PropTypes.node,
id: PropTypes.string.isRequired,
name: PropTypes.string,
sort: PropTypes.bool,
targetClass: PropTypes.func.isRequired,
targets: PropTypes.arrayOf(PropTypes.object).isRequired,
};
}
render() {
let {
client,
connect,
debugDisabled,
error,
targetClass,
targets,
sort,
} = this.props;
if (sort) {
targets = targets.sort(LocaleCompare);
}
targets = targets.map((target, index) => {
return targetClass({
key: target.addonID || target.url || index,
client,
connect,
target,
debugDisabled,
});
});
let content = "";
if (error) {
content = error;
} else if (targets.length > 0) {
content = dom.ul({ className: "target-list" }, targets);
} else {
content = dom.p(null, Strings.GetStringFromName("nothing"));
}
return dom.div(
{ id: this.props.id, className: "targets" },
dom.h2(null, this.props.name),
content
);
}
}
module.exports = TargetList;

View File

@ -1,137 +0,0 @@
/* 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/. */
/* eslint-env browser */
/* globals AddonManager */
"use strict";
loader.lazyImporter(
this,
"AddonManager",
"resource://gre/modules/AddonManager.jsm"
);
const {
createFactory,
Component,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const {
openTemporaryExtension,
} = require("devtools/client/aboutdebugging/modules/addon");
const Services = require("Services");
const AddonsInstallError = createFactory(require("./InstallError"));
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
const MORE_INFO_URL =
"https://developer.mozilla.org/docs/Tools" +
"/about:debugging#Enabling_add-on_debugging";
class AddonsControls extends Component {
static get propTypes() {
return {
debugDisabled: PropTypes.bool,
};
}
constructor(props) {
super(props);
this.state = {
installError: null,
};
this.onEnableAddonDebuggingChange = this.onEnableAddonDebuggingChange.bind(
this
);
this.loadAddonFromFile = this.loadAddonFromFile.bind(this);
this.retryInstall = this.retryInstall.bind(this);
this.installAddon = this.installAddon.bind(this);
}
onEnableAddonDebuggingChange(event) {
const enabled = event.target.checked;
Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
}
async loadAddonFromFile() {
const message = Strings.GetStringFromName("selectAddonFromFile2");
const file = await openTemporaryExtension(window, message);
this.installAddon(file);
}
retryInstall() {
this.setState({ installError: null });
this.installAddon(this.state.lastInstallErrorFile);
}
installAddon(file) {
AddonManager.installTemporaryAddon(file)
.then(() => {
this.setState({ lastInstallErrorFile: null });
})
.catch(e => {
console.error(e);
this.setState({ installError: e, lastInstallErrorFile: file });
});
}
render() {
const { debugDisabled } = this.props;
const isXpinstallEnabled = Services.prefs.getBoolPref(
"xpinstall.enabled",
true
);
return dom.div(
{ className: "addons-top" },
dom.div(
{ className: "addons-controls" },
dom.div(
{ className: "addons-options toggle-container-with-text" },
dom.input({
id: "enable-addon-debugging",
type: "checkbox",
checked: !debugDisabled,
onChange: this.onEnableAddonDebuggingChange,
role: "checkbox",
}),
dom.label(
{
className: "addons-debugging-label",
htmlFor: "enable-addon-debugging",
title: Strings.GetStringFromName("addonDebugging.tooltip"),
},
Strings.GetStringFromName("addonDebugging.label")
),
dom.a(
{ href: MORE_INFO_URL, target: "_blank" },
Strings.GetStringFromName("addonDebugging.learnMore")
)
),
isXpinstallEnabled
? dom.button(
{
id: "load-addon-from-file",
onClick: this.loadAddonFromFile,
},
Strings.GetStringFromName("loadTemporaryAddon2")
)
: null
),
AddonsInstallError({
error: this.state.installError,
retryInstall: this.retryInstall,
})
);
}
}
module.exports = AddonsControls;

View File

@ -1,60 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
class AddonsInstallError extends Component {
static get propTypes() {
return {
error: PropTypes.object,
retryInstall: PropTypes.func,
};
}
render() {
const { error } = this.props;
if (!this.props.error) {
return null;
}
const text = Strings.formatStringFromName(
"addonInstallError",
[error.message],
1
);
const additionalErrors = Array.isArray(error.additionalErrors)
? dom.div(
{ className: "addons-install-error__additional-errors" },
error.additionalErrors.join(" ")
)
: null;
return dom.div(
{ className: "addons-install-error" },
dom.span(
{},
dom.div({ className: "warning" }),
dom.span({}, text),
additionalErrors
),
dom.button(
{ className: "addons-install-retry", onClick: this.props.retryInstall },
Strings.GetStringFromName("retryTemporaryInstall")
)
);
}
}
module.exports = AddonsInstallError;

View File

@ -1,271 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
const { Management } = ChromeUtils.import(
"resource://gre/modules/Extension.jsm",
null
);
const {
createFactory,
Component,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
const AddonsControls = createFactory(require("./Controls"));
const AddonTarget = createFactory(require("./Target"));
const PanelHeader = createFactory(require("../PanelHeader"));
const TargetList = createFactory(require("../TargetList"));
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
const ExtensionIcon = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
const SYSTEM_ENABLED_PREF = "devtools.aboutdebugging.showHiddenAddons";
const WEB_EXT_URL =
"https://developer.mozilla.org/Add-ons" +
"/WebExtensions/Getting_started_with_web-ext";
class AddonsPanel extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
connect: PropTypes.object,
id: PropTypes.string.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
extensions: [],
debugDisabled: false,
showSystemAddons: false,
};
this.updateDebugStatus = this.updateDebugStatus.bind(this);
this.updateShowSystemStatus = this.updateShowSystemStatus.bind(this);
this.updateAddonsList = this.updateAddonsList.bind(this);
this.onInstalled = this.onInstalled.bind(this);
this.onUninstalled = this.onUninstalled.bind(this);
this.onEnabled = this.onEnabled.bind(this);
this.onDisabled = this.onDisabled.bind(this);
}
componentDidMount() {
AddonManager.addAddonListener(this);
// Listen to startup since that's when errors and warnings
// get populated on the extension.
Management.on("startup", this.updateAddonsList);
Services.prefs.addObserver(CHROME_ENABLED_PREF, this.updateDebugStatus);
Services.prefs.addObserver(REMOTE_ENABLED_PREF, this.updateDebugStatus);
Services.prefs.addObserver(
SYSTEM_ENABLED_PREF,
this.updateShowSystemStatus
);
this.updateDebugStatus();
this.updateShowSystemStatus();
this.updateAddonsList();
}
componentWillUnmount() {
AddonManager.removeAddonListener(this);
Management.off("startup", this.updateAddonsList);
Services.prefs.removeObserver(CHROME_ENABLED_PREF, this.updateDebugStatus);
Services.prefs.removeObserver(REMOTE_ENABLED_PREF, this.updateDebugStatus);
Services.prefs.removeObserver(
SYSTEM_ENABLED_PREF,
this.updateShowSystemStatus
);
}
updateDebugStatus() {
const debugDisabled =
!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
!Services.prefs.getBoolPref(REMOTE_ENABLED_PREF);
this.setState({ debugDisabled });
}
updateShowSystemStatus() {
const showSystemAddons = Services.prefs.getBoolPref(
SYSTEM_ENABLED_PREF,
false
);
this.setState({ showSystemAddons });
}
updateAddonsList() {
this.props.client.mainRoot.listAddons().then(
addons => {
const extensions = addons
.filter(addon => addon.debuggable)
.map(addon => {
return {
addonTargetFront: addon,
addonID: addon.id,
icon: addon.iconURL || ExtensionIcon,
isSystem: addon.isSystem,
manifestURL: addon.manifestURL,
name: addon.name,
temporarilyInstalled: addon.temporarilyInstalled,
url: addon.url,
warnings: addon.warnings,
};
});
this.setState({ extensions });
const { AboutDebugging } = window;
AboutDebugging.emit("addons-updated");
},
error => {
throw new Error("Client error while listing addons: " + error);
}
);
}
/**
* Mandatory callback as AddonManager listener.
*/
onInstalled() {
this.updateAddonsList();
}
/**
* Mandatory callback as AddonManager listener.
*/
onUninstalled() {
this.updateAddonsList();
}
/**
* Mandatory callback as AddonManager listener.
*/
onEnabled() {
this.updateAddonsList();
}
/**
* Mandatory callback as AddonManager listener.
*/
onDisabled() {
this.updateAddonsList();
}
render() {
const { client, connect, id } = this.props;
const { debugDisabled, extensions: targets, showSystemAddons } = this.state;
const installedName = Strings.GetStringFromName("extensions");
const temporaryName = Strings.GetStringFromName("temporaryExtensions");
const systemName = Strings.GetStringFromName("systemExtensions");
const targetClass = AddonTarget;
const installedTargets = targets.filter(
target => !target.isSystem && !target.temporarilyInstalled
);
const temporaryTargets = targets.filter(
target => target.temporarilyInstalled
);
const systemTargets =
showSystemAddons && targets.filter(target => target.isSystem);
// Don't show the temporary addons category if users can't install addons.
const isXpinstallEnabled = Services.prefs.getBoolPref(
"xpinstall.enabled",
true
);
return dom.div(
{
id: id + "-panel",
className: "panel",
role: "tabpanel",
"aria-labelledby": id + "-header",
},
PanelHeader({
id: id + "-header",
name: Strings.GetStringFromName("addons"),
}),
AddonsControls({ debugDisabled }),
isXpinstallEnabled
? dom.div(
{ id: "temporary-addons" },
TargetList({
id: "temporary-extensions",
name: temporaryName,
targets: temporaryTargets,
client,
connect,
debugDisabled,
targetClass,
sort: true,
}),
dom.div(
{ className: "addons-tip" },
dom.div({ className: "addons-tip-icon" }),
dom.span(
{
className: "addons-web-ext-tip",
},
Strings.GetStringFromName("webExtTip")
),
dom.a(
{ href: WEB_EXT_URL, target: "_blank" },
Strings.GetStringFromName("webExtTip.learnMore")
)
)
)
: null,
dom.div(
{ id: "addons" },
TargetList({
id: "extensions",
name: installedName,
targets: installedTargets,
client,
connect,
debugDisabled,
targetClass,
sort: true,
})
),
showSystemAddons
? dom.div(
{ id: "system-addons" },
TargetList({
id: "system-extensions",
name: systemName,
targets: systemTargets,
client,
connect,
debugDisabled,
targetClass,
sort: true,
})
)
: null
);
}
}
module.exports = AddonsPanel;

View File

@ -1,281 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const {
debugAddon,
getExtensionUuid,
isTemporaryID,
parseFileUri,
uninstallAddon,
} = require("../../modules/addon");
const Services = require("Services");
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
const TEMP_ID_URL =
"https://developer.mozilla.org/Add-ons" +
"/WebExtensions/WebExtensions_and_the_Add-on_ID";
const LEGACY_WARNING_URL =
"https://wiki.mozilla.org/Add-ons/Future_of_Bootstrap";
function filePathForTarget(target) {
// Only show file system paths, and only for temporarily installed add-ons.
if (
!target.temporarilyInstalled ||
!target.url ||
!target.url.startsWith("file://")
) {
return [];
}
const path = parseFileUri(target.url);
return [
dom.dt(
{ className: "addon-target-info-label" },
Strings.GetStringFromName("location")
),
// Wrap the file path in a span so we can do some RTL/LTR swapping to get
// the ellipsis on the left.
dom.dd(
{ className: "addon-target-info-content file-path" },
dom.span({ className: "file-path-inner", title: path }, path)
),
];
}
function addonIDforTarget(target) {
return [
dom.dt(
{ className: "addon-target-info-label" },
Strings.GetStringFromName("extensionID")
),
dom.dd(
{ className: "addon-target-info-content extension-id" },
dom.span({ title: target.addonID }, target.addonID)
),
];
}
function internalIDForTarget(target) {
const uuid = getExtensionUuid(target);
if (!uuid) {
return [];
}
return [
dom.dt(
{ className: "addon-target-info-label" },
Strings.GetStringFromName("internalUUID")
),
dom.dd(
{ className: "addon-target-info-content internal-uuid" },
dom.span({ title: uuid }, uuid),
dom.span(
{ className: "addon-target-info-more" },
dom.a(
{
href: target.manifestURL,
target: "_blank",
className: "manifest-url",
},
Strings.GetStringFromName("manifestURL")
)
)
),
];
}
function showMessages(target) {
const messages = [...warningMessages(target), ...infoMessages(target)];
if (messages.length > 0) {
return dom.ul({ className: "addon-target-messages" }, ...messages);
}
return null;
}
function infoMessages(target) {
const messages = [];
if (isTemporaryID(target.addonID)) {
messages.push(
dom.li(
{ className: "addon-target-info-message addon-target-message" },
Strings.GetStringFromName("temporaryID"),
" ",
dom.a(
{
href: TEMP_ID_URL,
className: "temporary-id-url",
target: "_blank",
},
Strings.GetStringFromName("temporaryID.learnMore")
)
)
);
}
return messages;
}
function warningMessages(target) {
let messages = [];
if (target.addonTargetFront.isLegacyTemporaryExtension()) {
messages.push(
dom.li(
{
className: "addon-target-warning-message addon-target-message",
},
Strings.GetStringFromName("legacyExtensionWarning"),
" ",
dom.a(
{
href: LEGACY_WARNING_URL,
target: "_blank",
},
Strings.GetStringFromName("legacyExtensionWarning.learnMore")
)
)
);
}
const warnings = target.warnings || [];
messages = messages.concat(
warnings.map(warning => {
return dom.li(
{ className: "addon-target-warning-message addon-target-message" },
warning
);
})
);
return messages;
}
class AddonTarget extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
connect: PropTypes.object,
debugDisabled: PropTypes.bool,
target: PropTypes.shape({
addonTargetFront: PropTypes.object.isRequired,
addonID: PropTypes.string.isRequired,
icon: PropTypes.string,
name: PropTypes.string.isRequired,
temporarilyInstalled: PropTypes.bool,
url: PropTypes.string,
warnings: PropTypes.array,
}).isRequired,
};
}
constructor(props) {
super(props);
this.debug = this.debug.bind(this);
this.uninstall = this.uninstall.bind(this);
this.reload = this.reload.bind(this);
}
debug() {
const { client, target } = this.props;
debugAddon(target.addonID, client);
}
uninstall() {
const { target } = this.props;
uninstallAddon(target.addonID);
}
async reload() {
const { target } = this.props;
const { AboutDebugging } = window;
try {
await target.addonTargetFront.reload();
AboutDebugging.emit("addon-reload");
} catch (e) {
throw new Error(
"Error reloading addon " + target.addonID + ": " + e.message
);
}
}
render() {
const { target, debugDisabled } = this.props;
return dom.li(
{
className: "card addon-target-container",
"data-addon-id": target.addonID,
},
dom.div(
{ className: "target-card-heading target" },
dom.img({
className: "target-icon addon-target-icon",
role: "presentation",
src: target.icon,
}),
dom.span(
{ className: "target-name addon-target-name", title: target.name },
target.name
)
),
showMessages(target),
dom.dl(
{ className: "addon-target-info" },
...filePathForTarget(target),
...addonIDforTarget(target),
...internalIDForTarget(target)
),
dom.div(
{ className: "target-card-actions" },
dom.button(
{
className:
"target-card-action-link debug-button addon-target-button",
onClick: this.debug,
disabled: debugDisabled,
},
Strings.GetStringFromName("debug")
),
target.temporarilyInstalled
? dom.button(
{
className:
"target-card-action-link reload-button addon-target-button",
onClick: this.reload,
},
Strings.GetStringFromName("reload")
)
: null,
target.temporarilyInstalled
? dom.button(
{
className:
"target-card-action-link uninstall-button addon-target-button",
onClick: this.uninstall,
},
Strings.GetStringFromName("remove")
)
: null
)
);
}
}
module.exports = AddonTarget;

View File

@ -1,17 +0,0 @@
# 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/.
DIRS += [
'addons',
'tabs',
'workers',
]
DevToolsModules(
'Aboutdebugging.js',
'PanelHeader.js',
'PanelMenu.js',
'PanelMenuEntry.js',
'TargetList.js',
)

View File

@ -1,111 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const {
Component,
createFactory,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
const PanelHeader = createFactory(require("../PanelHeader"));
const TargetList = createFactory(require("../TargetList"));
const TabTarget = createFactory(require("./Target"));
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
class TabsPanel extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
connect: PropTypes.object,
id: PropTypes.string.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
tabs: [],
};
this.update = this.update.bind(this);
}
componentDidMount() {
const { client } = this.props;
client.mainRoot.on("tabListChanged", this.update);
this.update();
}
componentWillUnmount() {
const { client } = this.props;
client.mainRoot.off("tabListChanged", this.update);
}
async update() {
const tabs = await this.props.client.mainRoot.listTabs({ favicons: true });
for (const tab of tabs) {
if (tab.favicon) {
const base64Favicon = btoa(
String.fromCharCode.apply(String, tab.favicon)
);
tab.icon = "data:image/png;base64," + base64Favicon;
} else {
tab.icon =
"chrome://devtools/skin/images/aboutdebugging-globe-icon.svg";
}
}
this.setState({ tabs });
}
render() {
const { client, connect, id } = this.props;
const { tabs } = this.state;
return dom.div(
{
id: id + "-panel",
className: "panel",
role: "tabpanel",
"aria-labelledby": id + "-header",
},
PanelHeader({
id: id + "-header",
name: Strings.GetStringFromName("tabs"),
}),
dom.div(
{},
TargetList({
client,
connect,
id: "tabs",
name: Strings.GetStringFromName("tabs"),
sort: false,
targetClass: TabTarget,
targets: tabs,
})
)
);
}
}
module.exports = TabsPanel;

View File

@ -1,78 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
class TabTarget extends Component {
static get propTypes() {
return {
connect: PropTypes.object,
target: PropTypes.shape({
icon: PropTypes.string,
outerWindowID: PropTypes.number.isRequired,
title: PropTypes.string,
url: PropTypes.string.isRequired,
}).isRequired,
};
}
constructor(props) {
super(props);
this.debug = this.debug.bind(this);
}
debug() {
const { target, connect } = this.props;
let url = "about:devtools-toolbox?type=tab&id=" + target.outerWindowID;
if (connect.type == "REMOTE") {
const { host, port } = connect.params;
url += `&host=${encodeURIComponent(host)}&port=${encodeURIComponent(
port
)}`;
}
window.open(url);
}
render() {
const { target } = this.props;
return dom.div(
{ className: "target-container" },
dom.img({
className: "target-icon",
role: "presentation",
src: target.icon,
}),
dom.div(
{ className: "target" },
// If the title is empty, display the url instead.
dom.div(
{ className: "target-name", title: target.url },
target.title || target.url
)
),
dom.button(
{
className: "debug-button",
onClick: this.debug,
},
Strings.GetStringFromName("debug")
)
);
}
}
module.exports = TabTarget;

View File

@ -1,64 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
const { Ci } = require("chrome");
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
const MULTI_OPT_OUT_PREF = "dom.ipc.multiOptOut";
class multiE10SWarning extends Component {
constructor(props) {
super(props);
this.onUpdatePreferenceClick = this.onUpdatePreferenceClick.bind(this);
}
onUpdatePreferenceClick() {
const message = Strings.GetStringFromName(
"multiProcessWarningConfirmUpdate2"
);
if (window.confirm(message)) {
// Disable multi until at least the next experiment.
Services.prefs.setIntPref(
MULTI_OPT_OUT_PREF,
Services.appinfo.E10S_MULTI_EXPERIMENT
);
// Restart the browser.
Services.startup.quit(
Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
);
}
}
render() {
return dom.div(
{
className: "service-worker-multi-process",
},
dom.div(
{},
dom.div({ className: "warning" }),
dom.b({}, Strings.GetStringFromName("multiProcessWarningTitle"))
),
dom.div({}, Strings.GetStringFromName("multiProcessWarningMessage2")),
dom.button(
{
className: "update-button",
onClick: this.onUpdatePreferenceClick,
},
Strings.GetStringFromName("multiProcessWarningUpdateLink2")
)
);
}
}
module.exports = multiE10SWarning;

View File

@ -1,250 +0,0 @@
/* 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/. */
/* globals window */
"use strict";
loader.lazyImporter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
const {
Component,
createFactory,
} = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
const {
addMultiE10sListener,
isMultiE10s,
removeMultiE10sListener,
} = require("devtools/shared/multi-e10s-helper");
const PanelHeader = createFactory(require("../PanelHeader"));
const TargetList = createFactory(require("../TargetList"));
const WorkerTarget = createFactory(require("./Target"));
const MultiE10SWarning = createFactory(require("./MultiE10sWarning"));
const ServiceWorkerTarget = createFactory(require("./ServiceWorkerTarget"));
loader.lazyImporter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
const WorkerIcon = "chrome://devtools/skin/images/debugging-workers.svg";
const MORE_INFO_URL =
"https://developer.mozilla.org/en-US/docs/Tools/about%3Adebugging" +
"#Service_workers_not_compatible";
class WorkersPanel extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
id: PropTypes.string.isRequired,
};
}
constructor(props) {
super(props);
this.updateMultiE10S = this.updateMultiE10S.bind(this);
this.updateWorkers = this.updateWorkers.bind(this);
this.isE10S = this.isE10S.bind(this);
this.renderServiceWorkersError = this.renderServiceWorkersError.bind(this);
this.state = this.initialState;
}
componentDidMount() {
const client = this.props.client;
// When calling RootFront.listAllWorkers, ContentProcessTargetActor are created
// for each content process, which sends `workerListChanged` events.
client.mainRoot.onFront("contentProcessTarget", front => {
front.on("workerListChanged", this.updateWorkers);
this.state.contentProcessFronts.push(front);
});
client.mainRoot.onFront("serviceWorkerRegistration", front => {
this.state.serviceWorkerRegistrationFronts.push(front);
front.on("push-subscription-modified", this.updateWorkers);
front.on("registration-changed", this.updateWorkers);
});
client.mainRoot.on("workerListChanged", this.updateWorkers);
client.mainRoot.on(
"serviceWorkerRegistrationListChanged",
this.updateWorkers
);
client.mainRoot.on("processListChanged", this.updateWorkers);
addMultiE10sListener(this.updateMultiE10S);
this.updateMultiE10S();
this.updateWorkers();
}
componentWillUnmount() {
const client = this.props.client;
client.mainRoot.off("processListChanged", this.updateWorkers);
client.mainRoot.off(
"serviceWorkerRegistrationListChanged",
this.updateWorkers
);
client.mainRoot.off("workerListChanged", this.updateWorkers);
for (const front of this.state.contentProcessFronts) {
front.off("workerListChanged", this.updateWorkers);
}
for (const front of this.state.serviceWorkerRegistrationFronts) {
front.off("push-subscription-modified", this.updateWorkers);
front.off("registration-changed", this.updateWorkers);
}
removeMultiE10sListener(this.updateMultiE10S);
}
get initialState() {
return {
workers: {
service: [],
shared: [],
other: [],
},
isMultiE10S: isMultiE10s(),
// List of ContentProcessTargetFront registered from componentWillMount
// from which we listen for worker list changes
contentProcessFronts: [],
serviceWorkerRegistrationFronts: [],
};
}
updateMultiE10S() {
this.setState({ isMultiE10S: isMultiE10s() });
}
updateWorkers() {
const workers = this.initialState.workers;
this.props.client.mainRoot
.listAllWorkers()
.then(({ service, other, shared }) => {
workers.service = service.map(f =>
Object.assign({ icon: WorkerIcon }, f)
);
workers.other = other.map(f => Object.assign({ icon: WorkerIcon }, f));
workers.shared = shared.map(f =>
Object.assign({ icon: WorkerIcon }, f)
);
// 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 });
});
}
isE10S() {
return Services.appinfo.browserTabsRemoteAutostart;
}
renderServiceWorkersError() {
const isWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(window);
const isPrivateBrowsingMode = PrivateBrowsingUtils.permanentPrivateBrowsing;
const isServiceWorkerDisabled = !Services.prefs.getBoolPref(
"dom.serviceWorkers.enabled"
);
const isDisabled =
isWindowPrivate || isPrivateBrowsingMode || isServiceWorkerDisabled;
if (!isDisabled) {
return "";
}
return dom.p(
{
className: "service-worker-disabled",
},
dom.div({ className: "warning" }),
dom.span(
{
className: "service-worker-disabled-label",
},
Strings.GetStringFromName("configurationIsNotCompatible.label")
),
dom.a(
{
href: MORE_INFO_URL,
target: "_blank",
},
Strings.GetStringFromName("configurationIsNotCompatible.learnMore")
)
);
}
render() {
const { client, id } = this.props;
const { workers, isMultiE10S } = this.state;
return dom.div(
{
id: id + "-panel",
className: "panel",
role: "tabpanel",
"aria-labelledby": id + "-header",
},
PanelHeader({
id: id + "-header",
name: Strings.GetStringFromName("workers"),
}),
isMultiE10S ? MultiE10SWarning() : "",
dom.div(
{
id: "workers",
className: "inverted-icons",
},
TargetList({
client,
debugDisabled: isMultiE10S,
error: this.renderServiceWorkersError(),
id: "service-workers",
name: Strings.GetStringFromName("serviceWorkers"),
sort: true,
targetClass: ServiceWorkerTarget,
targets: workers.service,
}),
TargetList({
client,
id: "shared-workers",
name: Strings.GetStringFromName("sharedWorkers"),
sort: true,
targetClass: WorkerTarget,
targets: workers.shared,
}),
TargetList({
client,
id: "other-workers",
name: Strings.GetStringFromName("otherWorkers"),
sort: true,
targetClass: WorkerTarget,
targets: workers.other,
})
)
);
}
}
module.exports = WorkersPanel;

View File

@ -1,273 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
loader.lazyRequireGetter(
this,
"gDevToolsBrowser",
"devtools/client/framework/devtools-browser",
true
);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
class ServiceWorkerTarget extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
debugDisabled: PropTypes.bool,
target: PropTypes.shape({
active: PropTypes.bool,
fetch: PropTypes.bool.isRequired,
icon: PropTypes.string,
name: PropTypes.string.isRequired,
url: PropTypes.string,
scope: PropTypes.string.isRequired,
// registrationFront can be missing in e10s.
registrationFront: PropTypes.object,
workerTargetFront: PropTypes.object,
}).isRequired,
};
}
constructor(props) {
super(props);
this.state = {
pushSubscription: null,
};
this.debug = this.debug.bind(this);
this.push = this.push.bind(this);
this.start = this.start.bind(this);
this.unregister = this.unregister.bind(this);
this.updatePushSubscription = this.updatePushSubscription.bind(this);
this.isRunning = this.isRunning.bind(this);
this.isActive = this.isActive.bind(this);
this.getServiceWorkerStatus = this.getServiceWorkerStatus.bind(this);
this.renderButtons = this.renderButtons.bind(this);
this.renderUnregisterLink = this.renderUnregisterLink.bind(this);
}
componentDidMount() {
this.updatePushSubscription();
}
componentDidUpdate(oldProps, oldState) {
// The parent component listens to "push-subscription-modified" events,
// so we should update the push subscription after each update.
this.updatePushSubscription();
}
debug() {
if (!this.isRunning()) {
// If the worker is not running, we can't debug it.
return;
}
const { workerTargetFront } = this.props.target;
gDevToolsBrowser.openWorkerToolbox(workerTargetFront);
}
push() {
if (!this.isActive() || !this.isRunning()) {
// If the worker is not running, we can't push to it.
// If the worker is not active, the registration might be unavailable and the
// push will not succeed.
return;
}
const { workerTargetFront } = this.props.target;
workerTargetFront.push();
}
start() {
if (!this.isActive() || this.isRunning()) {
// If the worker is not active or if it is already running, we can't start it.
return;
}
const { registrationFront } = this.props.target;
registrationFront.start();
}
unregister() {
const { registrationFront } = this.props.target;
registrationFront.unregister();
}
async updatePushSubscription() {
const { registrationFront } = this.props.target;
if (!registrationFront) {
// A valid registrationFront is needed to retrieve the push subscription.
return;
}
try {
const subscription = await registrationFront.getPushSubscription();
this.setState({ pushSubscription: subscription });
} catch (e) {
// The registration might be destroyed before the request reaches the server.
}
}
isRunning() {
// We know the target is running if it has a worker actor.
return !!this.props.target.workerTargetFront;
}
isActive() {
return this.props.target.active;
}
getServiceWorkerStatus() {
if (this.isActive() && this.isRunning()) {
return "running";
} else if (this.isActive()) {
return "stopped";
}
// We cannot get service worker registrations unless the registration is in
// ACTIVE state. Unable to know the actual state ("installing", "waiting"), we
// display a custom state "registering" for now. See Bug 1153292.
return "registering";
}
renderButtons() {
const pushButton = dom.button(
{
className: "push-button",
onClick: this.push,
disabled: this.props.debugDisabled,
},
Strings.GetStringFromName("push")
);
const debugButton = dom.button(
{
className: "debug-button",
onClick: this.debug,
disabled: this.props.debugDisabled,
},
Strings.GetStringFromName("debug")
);
const startButton = dom.button(
{
className: "start-button",
onClick: this.start,
disabled: this.props.debugDisabled,
},
Strings.GetStringFromName("start")
);
if (this.isRunning()) {
if (this.isActive()) {
return [pushButton, debugButton];
}
// Only debug button is available if the service worker is not active.
return debugButton;
}
return startButton;
}
renderUnregisterLink() {
if (!this.isActive()) {
// If not active, there might be no registrationFront available.
return null;
}
return dom.a(
{
onClick: this.unregister,
className: "unregister-link",
},
Strings.GetStringFromName("unregister")
);
}
render() {
const { target } = this.props;
const { pushSubscription } = this.state;
const status = this.getServiceWorkerStatus();
const fetch = target.fetch
? Strings.GetStringFromName("listeningForFetchEvents")
: Strings.GetStringFromName("notListeningForFetchEvents");
return dom.div(
{ className: "target-container" },
dom.img({
className: "target-icon",
role: "presentation",
src: target.icon,
}),
dom.span(
{ className: `target-status target-status-${status}` },
Strings.GetStringFromName(status)
),
dom.div(
{ className: "target" },
dom.div({ className: "target-name", title: target.name }, target.name),
dom.ul(
{ className: "target-details" },
pushSubscription
? dom.li(
{ className: "target-detail" },
dom.strong(null, Strings.GetStringFromName("pushService")),
dom.span(
{
className: "service-worker-push-url",
title: pushSubscription.endpoint,
},
pushSubscription.endpoint
)
)
: null,
dom.li(
{ className: "target-detail" },
dom.strong(null, Strings.GetStringFromName("fetch")),
dom.span(
{
className: "service-worker-fetch-flag",
title: fetch,
},
fetch
)
),
dom.li(
{ className: "target-detail" },
dom.strong(null, Strings.GetStringFromName("scope")),
dom.span(
{
className: "service-worker-scope",
title: target.scope,
},
target.scope
),
this.renderUnregisterLink()
)
)
),
this.renderButtons()
);
}
}
module.exports = ServiceWorkerTarget;

View File

@ -1,80 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const Services = require("Services");
loader.lazyRequireGetter(
this,
"DebuggerClient",
"devtools/shared/client/debugger-client",
true
);
loader.lazyRequireGetter(
this,
"gDevToolsBrowser",
"devtools/client/framework/devtools-browser",
true
);
const Strings = Services.strings.createBundle(
"chrome://devtools/locale/aboutdebugging.properties"
);
class WorkerTarget extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
debugDisabled: PropTypes.bool,
target: PropTypes.shape({
icon: PropTypes.string,
name: PropTypes.string.isRequired,
workerTargetFront: PropTypes.object,
}).isRequired,
};
}
constructor(props) {
super(props);
this.debug = this.debug.bind(this);
}
debug() {
const { workerTargetFront } = this.props.target;
gDevToolsBrowser.openWorkerToolbox(workerTargetFront);
}
render() {
const { target, debugDisabled } = this.props;
return dom.li(
{ className: "target-container" },
dom.img({
className: "target-icon",
role: "presentation",
src: target.icon,
}),
dom.div(
{ className: "target" },
dom.div({ className: "target-name", title: target.name }, target.name)
),
dom.button(
{
className: "debug-button",
onClick: this.debug,
disabled: debugDisabled,
},
Strings.GetStringFromName("debug")
)
);
}
}
module.exports = WorkerTarget;

View File

@ -1,81 +0,0 @@
/* 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/. */
/* eslint-env browser */
/* globals Telemetry */
"use strict";
const { loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
const { BrowserLoader } = ChromeUtils.import(
"resource://devtools/client/shared/browser-loader.js"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
const { require } = BrowserLoader({
baseURI: "resource://devtools/client/aboutdebugging/",
window,
});
const { createFactory } = require("devtools/client/shared/vendor/react");
const {
render,
unmountComponentAtNode,
} = require("devtools/client/shared/vendor/react-dom");
const EventEmitter = require("devtools/shared/event-emitter");
const AboutDebuggingApp = createFactory(require("./components/Aboutdebugging"));
const { createClient } = require("./modules/connect");
var AboutDebugging = {
async init() {
if (!Services.prefs.getBoolPref("devtools.enabled", true)) {
// If DevTools are disabled, navigate to about:devtools.
window.location = "about:devtools?reason=AboutDebugging";
return;
}
const { connect, client } = await createClient();
this.client = client;
await this.client.connect();
const telemetry = new Telemetry();
render(
AboutDebuggingApp({ client, connect, telemetry }),
document.querySelector("#root")
);
},
destroy() {
unmountComponentAtNode(document.querySelector("#root"));
if (this.client) {
this.client.close();
this.client = null;
}
},
};
// Used to track async requests in tests. See bug 1444424 for better ideas.
EventEmitter.decorate(AboutDebugging);
window.addEventListener(
"DOMContentLoaded",
function() {
AboutDebugging.init();
},
{ once: true }
);
window.addEventListener(
"unload",
function() {
AboutDebugging.destroy();
},
{ once: true }
);

View File

@ -1,40 +0,0 @@
/* 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";
loader.lazyImporter(
this,
"AddonManagerPrivate",
"resource://gre/modules/AddonManager.jsm"
);
const {
debugAddon,
getExtensionUuid,
openTemporaryExtension,
parseFileUri,
uninstallAddon,
} = require("devtools/client/aboutdebugging-new/src/modules/extensions-helper");
/**
* Most of the implementation for this module has been moved to
* devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
* The only methods implemented here are the ones used in the old aboutdebugging only.
*/
exports.isTemporaryID = function(addonID) {
return AddonManagerPrivate.isTemporaryInstallID(addonID);
};
/**
* See JSDoc in devtools/client/aboutdebugging-new/src/modules/extensions-helper for all
* the methods exposed below.
*/
exports.debugAddon = debugAddon;
exports.getExtensionUuid = getExtensionUuid;
exports.openTemporaryExtension = openTemporaryExtension;
exports.parseFileUri = parseFileUri;
exports.uninstallAddon = uninstallAddon;

View File

@ -1,75 +0,0 @@
/* 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/. */
/* eslint-env browser */
"use strict";
/**
* The connect module creates a connection to a debugger server based on the current
* context (e.g. URL parameters).
*/
const { clientFromURL } = require("devtools/client/framework/target-from-url");
const { DebuggerServer } = require("devtools/server/main");
// Supported connection types
const TYPE = {
// Default, connected to the current instance of Firefox
LOCAL: "LOCAL",
// Connected to a remote instance of Firefox via host&port settings.
REMOTE: "REMOTE",
};
/**
* Create a plain object containing the connection information relevant to aboutdebugging
* components.
*
* @returns {Object}
* - type: {String} from TYPE ("LOCAL", "REMOTE")
* - params: {Object} additional metadata depending on the type.
* - if type === "LOCAL", empty object
* - if type === "REMOTE", {host: {String}, port: {String}}
*/
function createDescriptorFromURL(url) {
const params = url.searchParams;
const host = params.get("host");
const port = params.get("port");
let descriptor;
if (host && port) {
descriptor = {
type: TYPE.REMOTE,
params: { host, port },
};
} else {
descriptor = {
type: TYPE.LOCAL,
params: {},
};
}
return descriptor;
}
/**
* Returns a promise that resolves after creating a debugger client corresponding to the
* provided options.
*
* @returns Promise that resolves an object with the following properties:
* - client: a DebuggerClient instance
* - connect: a connection descriptor, see doc for createDescriptorFromURL(url).
*/
exports.createClient = async function() {
const href = window.location.href;
const url = new window.URL(href.replace("about:", "http://"));
const connect = createDescriptorFromURL(url);
const client = await clientFromURL(url);
DebuggerServer.allowChromeProcess = true;
return { client, connect };
};

View File

@ -1,19 +0,0 @@
# 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/.
DIRS += [
'components',
'modules',
]
DevToolsModules(
'initializer.js'
)
BROWSER_CHROME_MANIFESTS += [
'test/browser.ini'
]
with Files('**'):
BUG_COMPONENT = ('DevTools', 'about:debugging')

View File

@ -1,23 +0,0 @@
"use strict";
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../.eslintrc.mochitests.js",
// All globals made available in aboutdebugging head.js file.
"globals": {
"AddonManager": true,
"addTab": true,
"assertHasTarget": true,
"CHROME_ROOT": true,
"closeAboutDebugging": true,
"getServiceWorkerList": true,
"getSupportsFile": true,
"installAddon": true,
"openAboutDebugging": true,
"removeTab": true,
"uninstallAddon": true,
"unregisterServiceWorker": true,
"waitForInitialAddonList": true,
"waitForServiceWorkerRegistered": true
}
};

View File

@ -1 +0,0 @@
this is not valid json

View File

@ -1,10 +0,0 @@
{
"manifest_version": 2,
"name": "test-devtools",
"version": "1.0",
"applications": {
"gecko": {
"id": "test-devtools@mozilla.org"
}
}
}

View File

@ -1,55 +0,0 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
head.js
addons/unpacked/manifest.json
addons/bad/manifest.json
addons/bug1273184.xpi
addons/test-devtools-webextension/*
addons/test-devtools-webextension-nobg/*
addons/test-devtools-webextension-noid/*
addons/test-devtools-webextension-unknown-prop/*
service-workers/delay-sw.html
service-workers/delay-sw.js
service-workers/empty-sw.html
service-workers/empty-sw.js
service-workers/fetch-sw.html
service-workers/fetch-sw.js
service-workers/push-sw.html
service-workers/push-sw.js
!/devtools/client/shared/test/shared-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
[browser_addons_debug_info.js]
[browser_addons_debug_webextension.js]
tags = webextensions
[browser_addons_debug_webextension_inspector.js]
tags = webextensions
[browser_addons_debug_webextension_nobg.js]
tags = webextensions
[browser_addons_debug_webextension_popup.js]
skip-if = (verify && debug) || (debug && os == "linux" && bits == 64) # verify: crashes on shutdown, timeouts linux debug Bug 1299001
tags = webextensions
[browser_addons_debugging_initial_state.js]
[browser_addons_install.js]
skip-if = verify && debug
[browser_addons_reload.js]
[browser_addons_remove.js]
[browser_addons_toggle_debug.js]
[browser_page_not_found.js]
[browser_service_workers.js]
[browser_service_workers_fetch_flag.js]
skip-if = os == 'mac' # bug 1333759
[browser_service_workers_multi_content_process.js]
skip-if = !e10s || serviceworker_e10s # This test is only valid in e10s, and is useless with serviceworker e10s refactor
[browser_service_workers_not_compatible.js]
[browser_service_workers_push.js]
[browser_service_workers_push_service.js]
skip-if = !e10s # Bug 1424895
[browser_service_workers_start.js]
[browser_service_workers_status.js]
[browser_service_workers_timeout.js]
skip-if = true # Bug 1232931
[browser_service_workers_unregister.js]
[browser_tabs.js]

View File

@ -1,176 +0,0 @@
"use strict";
const { Preferences } = ChromeUtils.import(
"resource://gre/modules/Preferences.jsm"
);
const UUID_REGEX = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
const SHOW_SYSTEM_ADDONS_PREF = "devtools.aboutdebugging.showHiddenAddons";
function testFilePath(container, expectedFilePath) {
// Verify that the path to the install location is shown next to its label.
const filePath = container.querySelector(".file-path");
ok(filePath, "file path is in DOM");
ok(
filePath.textContent.endsWith(expectedFilePath),
"file path is set correctly"
);
is(
filePath.previousElementSibling.textContent,
"Location",
"file path has label"
);
}
add_task(async function testWebExtension() {
const addonId = "test-devtools-webextension-nobg@mozilla.org";
const addonName = "test-devtools-webextension-nobg";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
const addonFile = ExtensionTestCommon.generateXPI({
manifest: {
name: addonName,
applications: {
gecko: { id: addonId },
},
},
});
registerCleanupFunction(() => addonFile.remove(false));
await installAddon({
document,
file: addonFile,
name: addonName,
});
const container = document.querySelector(`[data-addon-id="${addonId}"]`);
testFilePath(container, addonFile.leafName);
const extensionID = container.querySelector(".extension-id span");
ok(extensionID.textContent === "test-devtools-webextension-nobg@mozilla.org");
const internalUUID = container.querySelector(".internal-uuid span");
ok(internalUUID.textContent.match(UUID_REGEX), "internalUUID is correct");
const manifestURL = container.querySelector(".manifest-url");
ok(
manifestURL.href.startsWith("moz-extension://"),
"href for manifestURL exists"
);
await uninstallAddon({ document, id: addonId, name: addonName });
await closeAboutDebugging(tab);
});
add_task(async function testTemporaryWebExtension() {
const addonName = "test-devtools-webextension-noid";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
const addonFile = ExtensionTestCommon.generateXPI({
manifest: {
name: addonName,
},
});
registerCleanupFunction(() => addonFile.remove(false));
await installAddon({
document,
file: addonFile,
name: addonName,
});
const addons = document.querySelectorAll(
"#temporary-extensions .addon-target-container"
);
// Assuming that our temporary add-on is now at the top.
const container = addons[addons.length - 1];
const addonId = container.dataset.addonId;
const extensionID = container.querySelector(".extension-id span");
ok(extensionID.textContent.endsWith("@temporary-addon"));
const temporaryID = container.querySelector(".temporary-id-url");
ok(temporaryID, "Temporary ID message does appear");
await uninstallAddon({ document, id: addonId, name: addonName });
await closeAboutDebugging(tab);
});
add_task(async function testUnknownManifestProperty() {
const addonId = "test-devtools-webextension-unknown-prop@mozilla.org";
const addonName = "test-devtools-webextension-unknown-prop";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
const addonFile = ExtensionTestCommon.generateXPI({
manifest: {
name: addonName,
applications: {
gecko: { id: addonId },
},
wrong_manifest_property_name: {},
},
});
registerCleanupFunction(() => addonFile.remove(false));
await installAddon({
document,
file: addonFile,
name: addonName,
});
info("Wait until the addon appears in about:debugging");
const container = await waitUntilAddonContainer(addonName, document);
info("Wait until the installation message appears for the new addon");
await waitUntilElement(".addon-target-messages", container);
const messages = container.querySelectorAll(".addon-target-message");
ok(messages.length === 1, "there is one message");
ok(
messages[0].textContent.match(
/Error processing wrong_manifest_property_name/
),
"the message is helpful"
);
ok(
messages[0].classList.contains("addon-target-warning-message"),
"the message is a warning"
);
await uninstallAddon({ document, id: addonId, name: addonName });
await closeAboutDebugging(tab);
});
add_task(async function testSystemAddonsHidden() {
await pushPref(SHOW_SYSTEM_ADDONS_PREF, false);
const { document } = await openAboutDebugging("addons");
const systemAddonsShown = () =>
!!document.getElementById("system-extensions");
await waitForInitialAddonList(document);
ok(!systemAddonsShown(), "System extensions are hidden");
Preferences.set(SHOW_SYSTEM_ADDONS_PREF, true);
await waitUntil(systemAddonsShown);
ok(systemAddonsShown(), "System extensions are now shown");
Preferences.set(SHOW_SYSTEM_ADDONS_PREF, false);
await waitUntil(() => !systemAddonsShown());
ok(!systemAddonsShown(), "System extensions are hidden again");
});

View File

@ -1,89 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = ChromeUtils.import(
"resource://testing-common/PromiseTestUtils.jsm"
);
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_ID = "test-devtools-webextension@mozilla.org";
const ADDON_NAME = "test-devtools-webextension";
/**
* This test file ensures that the webextension addon developer toolbox:
* - when the debug button is clicked on a webextension, the opened toolbox
* has a working webconsole with the background page as default target;
*/
add_task(async function testWebExtensionsToolboxWebConsole() {
const addonFile = ExtensionTestCommon.generateXPI({
background: function() {
window.myWebExtensionAddonFunction = function() {
console.log(
"Background page function called",
this.browser.runtime.getManifest()
);
};
},
manifest: {
name: ADDON_NAME,
applications: {
gecko: { id: ADDON_ID },
},
},
});
registerCleanupFunction(() => addonFile.remove(false));
const { tab, document, debugBtn } = await setupTestAboutDebuggingWebExtension(
ADDON_NAME,
addonFile
);
const onToolboxReady = gDevTools.once("toolbox-ready");
const onToolboxClose = gDevTools.once("toolbox-destroyed");
debugBtn.click();
const toolbox = await onToolboxReady;
testScript(toolbox);
await onToolboxClose;
ok(true, "Addon toolbox closed");
await uninstallAddon({ document, id: ADDON_ID, name: ADDON_NAME });
await closeAboutDebugging(tab);
});
const testScript = function(toolbox) {
function findMessages(hud, text, selector = ".message") {
const messages = hud.ui.outputNode.querySelectorAll(selector);
const elements = Array.prototype.filter.call(messages, el =>
el.textContent.includes(text)
);
return elements;
}
async function waitFor(condition) {
while (!condition()) {
await new Promise(done => window.setTimeout(done, 1000));
}
}
toolbox
.selectTool("webconsole")
.then(async console => {
const { hud } = console;
const { jsterm } = hud;
const onMessage = waitFor(() => {
return findMessages(hud, "Background page function called").length > 0;
});
await jsterm.execute("myWebExtensionAddonFunction()");
await onMessage;
await toolbox.destroy();
})
.catch(e => dump("Exception from browser toolbox process: " + e + "\n"));
};

View File

@ -1,92 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = ChromeUtils.import(
"resource://testing-common/PromiseTestUtils.jsm"
);
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_ID = "test-devtools-webextension@mozilla.org";
const ADDON_NAME = "test-devtools-webextension";
/**
* This test file ensures that the webextension addon developer toolbox:
* - the webextension developer toolbox has a working Inspector panel, with the
* background page as default target;
*/
add_task(async function testWebExtensionsToolboxInspector() {
const addonFile = ExtensionTestCommon.generateXPI({
background: function() {
document.body.innerText = "Background Page Body Test Content";
},
manifest: {
name: ADDON_NAME,
applications: {
gecko: { id: ADDON_ID },
},
},
});
registerCleanupFunction(() => addonFile.remove(false));
const { tab, document, debugBtn } = await setupTestAboutDebuggingWebExtension(
ADDON_NAME,
addonFile
);
const onToolboxReady = gDevTools.once("toolbox-ready");
const onToolboxClose = gDevTools.once("toolbox-destroyed");
debugBtn.click();
const toolbox = await onToolboxReady;
testScript(toolbox);
await onToolboxClose;
ok(true, "Addon toolbox closed");
await uninstallAddon({ document, id: ADDON_ID, name: ADDON_NAME });
await closeAboutDebugging(tab);
});
const testScript = function(toolbox) {
toolbox
.selectTool("inspector")
.then(inspector => {
return inspector.walker.querySelector(inspector.walker.rootNode, "body");
})
.then(nodeActor => {
if (!nodeActor) {
throw new Error("nodeActor not found");
}
dump("Got a nodeActor\n");
if (!nodeActor.inlineTextChild) {
throw new Error("inlineTextChild not found");
}
dump("Got a nodeActor with an inline text child\n");
const expectedValue = "Background Page Body Test Content";
const actualValue = nodeActor.inlineTextChild._form.nodeValue;
if (String(actualValue).trim() !== String(expectedValue).trim()) {
throw new Error(
`mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
);
}
dump("Got the expected inline text content in the selected node\n");
return Promise.resolve();
})
.then(() => toolbox.destroy())
.catch(error => {
dump("Error while running code in the browser toolbox process:\n");
dump(error + "\n");
dump("stack:\n" + error.stack + "\n");
});
};

View File

@ -1,88 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = ChromeUtils.import(
"resource://testing-common/PromiseTestUtils.jsm"
);
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
/**
* This test file ensures that the webextension addon developer toolbox:
* - the webextension developer toolbox is connected to a fallback page when the
* background page is not available (and in the fallback page document body contains
* the expected message, which warns the user that the current page is not a real
* webextension context);
*/
add_task(async function testWebExtensionsToolboxNoBackgroundPage() {
const addonFile = ExtensionTestCommon.generateXPI({
manifest: {
name: ADDON_NOBG_NAME,
applications: {
gecko: { id: ADDON_NOBG_ID },
},
},
});
registerCleanupFunction(() => addonFile.remove(false));
const { tab, document, debugBtn } = await setupTestAboutDebuggingWebExtension(
ADDON_NOBG_NAME,
addonFile
);
const onToolboxReady = gDevTools.once("toolbox-ready");
const onToolboxClose = gDevTools.once("toolbox-destroyed");
debugBtn.click();
const toolbox = await onToolboxReady;
testScript(toolbox);
await onToolboxClose;
ok(true, "Addon toolbox closed");
await uninstallAddon({ document, id: ADDON_NOBG_ID, name: ADDON_NOBG_NAME });
await closeAboutDebugging(tab);
});
const testScript = function(toolbox) {
toolbox
.selectTool("inspector")
.then(async inspector => {
let nodeActor;
dump(`Wait the fallback window to be fully loaded\n`);
await asyncWaitUntil(async () => {
nodeActor = await inspector.walker.querySelector(
inspector.walker.rootNode,
"h1"
);
return nodeActor && nodeActor.inlineTextChild;
});
dump("Got a nodeActor with an inline text child\n");
const expectedValue = "Your addon does not have any document opened yet.";
const actualValue = nodeActor.inlineTextChild._form.nodeValue;
if (actualValue !== expectedValue) {
throw new Error(
`mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
);
}
dump("Got the expected inline text content in the selected node\n");
await toolbox.destroy();
})
.catch(error => {
dump("Error while running code in the browser toolbox process:\n");
dump(error + "\n");
dump("stack:\n" + error.stack + "\n");
});
};

View File

@ -1,258 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// There are shutdown issues for which multiple rejections are left uncaught.
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = ChromeUtils.import(
"resource://testing-common/PromiseTestUtils.jsm"
);
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_ID = "test-devtools-webextension@mozilla.org";
const ADDON_NAME = "test-devtools-webextension";
/**
* This test file ensures that the webextension addon developer toolbox:
* - when the debug button is clicked on a webextension, the opened toolbox
* has a working webconsole with the background page as default target;
* - the webextension developer toolbox has a working Inspector panel, with the
* background page as default target;
* - the webextension developer toolbox is connected to a fallback page when the
* background page is not available (and in the fallback page document body contains
* the expected message, which warns the user that the current page is not a real
* webextension context);
* - the webextension developer toolbox has a frame list menu and the noautohide toolbar
* toggle button, and they can be used to switch the current target to the extension
* popup page.
*/
/**
* Returns the widget id for an extension with the passed id.
*/
function makeWidgetId(id) {
id = id.toLowerCase();
return id.replace(/[^a-z0-9_-]/g, "_");
}
add_task(async function testWebExtensionsToolboxSwitchToPopup() {
const addonFile = ExtensionTestCommon.generateXPI({
background: function() {
const { browser } = this;
window.myWebExtensionShowPopup = function() {
browser.test.sendMessage("readyForOpenPopup");
};
},
manifest: {
name: ADDON_NAME,
applications: {
gecko: { id: ADDON_ID },
},
browser_action: {
default_title: "WebExtension Popup Debugging",
default_popup: "popup.html",
},
},
files: {
"popup.html": `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="popup.js"></script>
</head>
<body>
Background Page Body Test Content
</body>
</html>
`,
"popup.js": function() {
const { browser } = this;
window.myWebExtensionPopupAddonFunction = function() {
browser.test.sendMessage(
"popupPageFunctionCalled",
browser.runtime.getManifest()
);
};
},
},
});
registerCleanupFunction(() => addonFile.remove(false));
let onReadyForOpenPopup;
let onPopupCustomMessage;
is(
Services.prefs.getBoolPref("ui.popup.disable_autohide"),
false,
"disable_autohide shoult be initially false"
);
Management.on("startup", function listener(event, extension) {
if (extension.name != ADDON_NAME) {
return;
}
Management.off("startup", listener);
function waitForExtensionTestMessage(expectedMessage) {
return new Promise(done => {
extension.on("test-message", function testLogListener(evt, ...args) {
const [message] = args;
if (message !== expectedMessage) {
return;
}
extension.off("test-message", testLogListener);
done(args);
});
});
}
// Wait for the test script running in the browser toolbox process
// to be ready for selecting the popup page in the frame list selector.
onReadyForOpenPopup = waitForExtensionTestMessage("readyForOpenPopup");
// Wait for a notification sent by a script evaluated the test addon via
// the web console.
onPopupCustomMessage = waitForExtensionTestMessage(
"popupPageFunctionCalled"
);
});
const { tab, document, debugBtn } = await setupTestAboutDebuggingWebExtension(
ADDON_NAME,
addonFile
);
const onToolboxReady = gDevTools.once("toolbox-ready");
const onToolboxClose = gDevTools.once("toolbox-destroyed");
debugBtn.click();
const toolbox = await onToolboxReady;
testScript(toolbox);
await onReadyForOpenPopup;
const browserActionId = makeWidgetId(ADDON_ID) + "-browser-action";
const browserActionEl = window.document.getElementById(browserActionId);
ok(browserActionEl, "Got the browserAction button from the browser UI");
browserActionEl.click();
info("Clicked on the browserAction button");
const args = await onPopupCustomMessage;
ok(true, "Received console message from the popup page function as expected");
is(args[0], "popupPageFunctionCalled", "Got the expected console message");
is(
args[1] && args[1].name,
ADDON_NAME,
"Got the expected manifest from WebExtension API"
);
await onToolboxClose;
info("Addon toolbox closed");
is(
Services.prefs.getBoolPref("ui.popup.disable_autohide"),
false,
"disable_autohide should be reset to false when the toolbox is closed"
);
await uninstallAddon({ document, id: ADDON_ID, name: ADDON_NAME });
await closeAboutDebugging(tab);
});
const testScript = function(toolbox) {
let jsterm;
const popupFramePromise = new Promise(resolve => {
const listener = data => {
if (data.frames.some(({ url }) => url && url.endsWith("popup.html"))) {
toolbox.target.off("frame-update", listener);
resolve();
}
};
toolbox.target.on("frame-update", listener);
});
const waitForFrameListUpdate = toolbox.target.once("frame-update");
toolbox
.selectTool("webconsole")
.then(async console => {
const clickNoAutoHideMenu = () => {
return new Promise(resolve => {
toolbox.doc.getElementById("toolbox-meatball-menu-button").click();
toolbox.doc.addEventListener(
"popupshown",
() => {
const menuItem = toolbox.doc.getElementById(
"toolbox-meatball-menu-noautohide"
);
menuItem.click();
resolve();
},
{ once: true }
);
});
};
dump(`Clicking the menu button\n`);
await clickNoAutoHideMenu();
dump(`Clicked the menu button\n`);
jsterm = console.hud.jsterm;
jsterm.execute("myWebExtensionShowPopup()");
await Promise.all([
// Wait the initial frame update (which list the background page).
waitForFrameListUpdate,
// Wait the new frame update (once the extension popup has been opened).
popupFramePromise,
]);
dump(`Clicking the frame list button\n`);
const btn = toolbox.doc.getElementById("command-button-frames");
btn.click();
const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
const frames = Array.from(menuList.querySelectorAll(".command"));
if (frames.length != 2) {
throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
}
const popupFrameBtn = frames
.filter(frame => {
return frame
.querySelector(".label")
.textContent.endsWith("popup.html");
})
.pop();
if (!popupFrameBtn) {
throw Error("Extension Popup frame not found in the listed frames");
}
const waitForNavigated = toolbox.target.once("navigate");
popupFrameBtn.click();
// Clicking the menu item may do highlighting.
await waitUntil(() => toolbox.highlighter);
await Promise.race([
toolbox.highlighter.once("node-highlight"),
wait(1000),
]);
await waitForNavigated;
await jsterm.execute("myWebExtensionPopupAddonFunction()");
await toolbox.destroy();
})
.catch(error => {
dump("Error while running code in the browser toolbox process:\n");
dump(error + "\n");
dump("stack:\n" + error.stack + "\n");
});
};

View File

@ -1,83 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that addons debugging controls are properly enabled/disabled depending
// on the values of the relevant preferences:
// - devtools.chrome.enabled
// - devtools.debugger.remote-enabled
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
const TEST_DATA = [
{
chromeEnabled: false,
debuggerRemoteEnable: false,
expected: false,
},
{
chromeEnabled: false,
debuggerRemoteEnable: true,
expected: false,
},
{
chromeEnabled: true,
debuggerRemoteEnable: false,
expected: false,
},
{
chromeEnabled: true,
debuggerRemoteEnable: true,
expected: true,
},
];
add_task(async function() {
for (const testData of TEST_DATA) {
await testCheckboxState(testData);
}
});
async function testCheckboxState(testData) {
info("Set preferences as defined by the current test data.");
await new Promise(resolve => {
const options = {
set: [
["devtools.chrome.enabled", testData.chromeEnabled],
["devtools.debugger.remote-enabled", testData.debuggerRemoteEnable],
],
};
SpecialPowers.pushPrefEnv(options, resolve);
});
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
info("Install a test addon.");
await installAddon({
document,
path: "addons/unpacked/manifest.json",
name: ADDON_NAME,
});
info("Test checkbox checked state.");
const addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
is(
addonDebugCheckbox.checked,
testData.expected,
"Addons debugging checkbox should be in expected state."
);
info("Test debug buttons disabled state.");
const debugButtons = [...document.querySelectorAll("#addons .debug-button")];
ok(
debugButtons.every(b => b.disabled != testData.expected),
"Debug buttons should be in the expected state"
);
info("Uninstall test addon installed earlier.");
await uninstallAddon({ document, id: ADDON_ID, name: ADDON_NAME });
await closeAboutDebugging(tab);
}

View File

@ -1,138 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
loader.lazyImporter(
this,
"AddonTestUtils",
"resource://testing-common/AddonTestUtils.jsm"
);
AddonTestUtils.initMochitest(this);
function mockFilePicker(window, file) {
// Mock the file picker to select a test addon
const MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
MockFilePicker.setFiles([file]);
}
/**
* Write out an extension with a manifest.json to dir.
*
* @param {object} manfiest
* The manifest file as an object.
* @param {nsIFile} dir
* An nsIFile for representing the output folder.
* @return {Promise} Promise that resolves to the output folder when done.
*/
function promiseWriteWebManifestForExtension(manifest, dir) {
const files = {
"manifest.json": JSON.stringify(manifest),
};
return AddonTestUtils.promiseWriteFilesToExtension(
dir.path,
manifest.applications.gecko.id,
files,
true
);
}
add_task(async function testWebextensionInstallSuccess() {
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
// Install this add-on, and verify that it appears in the about:debugging UI
await installAddon({
document,
path: "addons/unpacked/manifest.json",
name: "test-devtools",
});
// Install the add-on, and verify that it disappears in the about:debugging UI
await uninstallAddon({
document,
id: "test-devtools@mozilla.org",
name: "test-devtools",
});
await closeAboutDebugging(tab);
});
add_task(async function testWebextensionInstallError() {
const { tab, document, window } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
// Trigger the file picker by clicking on the button
mockFilePicker(window, getSupportsFile("addons/bad/manifest.json").file);
document.getElementById("load-addon-from-file").click();
info("wait for the install error to appear");
const top = document.querySelector(".addons-top");
await waitUntilElement(".addons-install-error", top);
await closeAboutDebugging(tab);
});
add_task(async function testWebextensionInstallErrorRetry() {
const { tab, document, window } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
const tempdir = AddonTestUtils.tempDir.clone();
const addonId = "invalid-addon-install-retry@mozilla.org";
const addonName = "invalid-addon-install-retry";
const manifest = {
name: addonName,
description: "test invalid-addon-install-retry",
// eslint-disable-next-line camelcase
manifest_version: 2,
version: "1.0",
applications: { gecko: { id: addonId } },
// These should all be wrapped in arrays.
// eslint-disable-next-line camelcase
content_scripts: { matches: "http://*/", js: "foo.js" },
};
await promiseWriteWebManifestForExtension(manifest, tempdir);
// Mock the file picker to select a test addon.
const manifestFile = tempdir.clone();
manifestFile.append(addonId, "manifest.json");
mockFilePicker(window, manifestFile);
// Trigger the file picker by clicking on the button.
document.getElementById("load-addon-from-file").click();
info("wait for the install error to appear");
const top = document.querySelector(".addons-top");
await waitUntilElement(".addons-install-error", top);
const retryButton = document.querySelector("button.addons-install-retry");
is(retryButton.textContent, "Retry", "Retry button has a good label");
// Fix the manifest so the add-on will install.
// eslint-disable-next-line camelcase
manifest.content_scripts = [
{
matches: ["http://*/"],
js: ["foo.js"],
},
];
await promiseWriteWebManifestForExtension(manifest, tempdir);
const addonEl = document.querySelector(`[data-addon-id="${addonId}"]`);
// Verify this add-on isn't installed yet.
ok(!addonEl, "Addon is not installed yet");
// Retry the install.
retryButton.click();
info("Wait for the add-on to be shown");
await waitUntilElement(`[data-addon-id="${addonId}"]`, document);
info("Addon is installed");
// Install the add-on, and verify that it disappears in the about:debugging UI
await uninstallAddon({ document, id: addonId, name: addonName });
await closeAboutDebugging(tab);
});

View File

@ -1,187 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ADDON_ID = "test-devtools@mozilla.org";
const PACKAGED_ADDON_ID = "bug1273184@tests";
const PACKAGED_ADDON_NAME = "bug 1273184";
function getReloadButton(document, addonName) {
const names = getInstalledAddonNames(document);
const name = names.filter(element => element.textContent === addonName)[0];
ok(name, `Found ${addonName} add-on in the list`);
const targetElement = name.parentNode.parentNode;
const reloadButton = targetElement.querySelector(".reload-button");
info(`Found reload button for ${addonName}`);
return reloadButton;
}
/**
* Creates a web extension from scratch in a temporary location.
* The object must be removed when you're finished working with it.
*/
class TempWebExt {
constructor(addonId) {
this.addonId = addonId;
this.tmpDir = FileUtils.getDir("TmpD", ["browser_addons_reload"]);
if (!this.tmpDir.exists()) {
this.tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
}
this.sourceDir = this.tmpDir.clone();
this.sourceDir.append(this.addonId);
if (!this.sourceDir.exists()) {
this.sourceDir.create(
Ci.nsIFile.DIRECTORY_TYPE,
FileUtils.PERMS_DIRECTORY
);
}
}
writeManifest(manifestData) {
const manifest = this.sourceDir.clone();
manifest.append("manifest.json");
if (manifest.exists()) {
manifest.remove(true);
}
const fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
Ci.nsIFileOutputStream
);
fos.init(
manifest,
FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
FileUtils.PERMS_FILE,
0
);
const manifestString = JSON.stringify(manifestData);
fos.write(manifestString, manifestString.length);
fos.close();
}
remove() {
return this.tmpDir.remove(true);
}
}
add_task(async function reloadButtonReloadsAddon() {
const ADDON_NAME = "test-devtools";
const { tab, document, window } = await openAboutDebugging("addons");
const { AboutDebugging } = window;
await waitForInitialAddonList(document);
await installAddon({
document,
path: "addons/unpacked/manifest.json",
name: ADDON_NAME,
});
const reloadButton = getReloadButton(document, ADDON_NAME);
is(reloadButton.title, "", "Reload button should not have a tooltip");
const onInstalled = promiseAddonEvent("onInstalled");
const reloaded = once(AboutDebugging, "addon-reload");
// The list is updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
reloadButton.click();
await reloaded;
await onListUpdated;
const [reloadedAddon] = await onInstalled;
is(
reloadedAddon.name,
ADDON_NAME,
"Add-on was reloaded: " + reloadedAddon.name
);
await tearDownAddon(AboutDebugging, reloadedAddon);
await closeAboutDebugging(tab);
});
add_task(async function reloadButtonRefreshesMetadata() {
const { tab, document, window } = await openAboutDebugging("addons");
const { AboutDebugging } = window;
await waitForInitialAddonList(document);
const manifestBase = {
manifest_version: 2,
name: "Temporary web extension",
version: "1.0",
applications: {
gecko: {
id: ADDON_ID,
},
},
};
const tempExt = new TempWebExt(ADDON_ID);
tempExt.writeManifest(manifestBase);
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
let onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
const onInstalled = promiseAddonEvent("onInstalled");
await AddonManager.installTemporaryAddon(tempExt.sourceDir);
await onListUpdated;
info("Wait until addon onInstalled event is received");
await onInstalled;
info("Wait until addon appears in about:debugging#addons");
await waitUntilAddonContainer("Temporary web extension", document);
const newName = "Temporary web extension (updated)";
tempExt.writeManifest(Object.assign({}, manifestBase, { name: newName }));
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
// Wait for the add-on list to be updated with the reloaded name.
const onReInstall = promiseAddonEvent("onInstalled");
const reloadButton = getReloadButton(document, manifestBase.name);
const reloaded = once(AboutDebugging, "addon-reload");
reloadButton.click();
await reloaded;
await onListUpdated;
info("Wait until addon onInstalled event is received again");
const [reloadedAddon] = await onReInstall;
info("Wait until addon name is updated in about:debugging#addons");
await waitUntilAddonContainer(newName, document);
await tearDownAddon(AboutDebugging, reloadedAddon);
tempExt.remove();
await closeAboutDebugging(tab);
});
add_task(async function onlyTempInstalledAddonsCanBeReloaded() {
const { tab, document, window } = await openAboutDebugging("addons");
const { AboutDebugging } = window;
await waitForInitialAddonList(document);
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
await installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
await onListUpdated;
info("Wait until addon appears in about:debugging#addons");
await waitUntilAddonContainer(PACKAGED_ADDON_NAME, document);
info("Retrieved the installed addon from the addon manager");
const addon = await getAddonByID(PACKAGED_ADDON_ID);
is(addon.name, PACKAGED_ADDON_NAME, "Addon name is correct");
const reloadButton = getReloadButton(document, addon.name);
ok(!reloadButton, "There should not be a reload button");
await tearDownAddon(AboutDebugging, addon);
await closeAboutDebugging(tab);
});

View File

@ -1,73 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const PACKAGED_ADDON_NAME = "bug 1273184";
function getTargetEl(document, id) {
return document.querySelector(`[data-addon-id="${id}"]`);
}
function getRemoveButton(document, id) {
return document.querySelector(`[data-addon-id="${id}"] .uninstall-button`);
}
add_task(async function removeWebextension() {
const addonID = "test-devtools-webextension@mozilla.org";
const addonName = "test-devtools-webextension";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
const addonFile = ExtensionTestCommon.generateXPI({
manifest: {
name: addonName,
applications: {
gecko: { id: addonID },
},
},
});
registerCleanupFunction(() => addonFile.remove(false));
// Install this add-on, and verify that it appears in the about:debugging UI
await installAddon({
document,
file: addonFile,
name: addonName,
});
ok(getTargetEl(document, addonID), "add-on is shown");
info(
"Click on the remove button and wait until the addon container is removed"
);
getRemoveButton(document, addonID).click();
await waitUntil(() => !getTargetEl(document, addonID), 100);
info("add-on is not shown");
await closeAboutDebugging(tab);
});
add_task(async function onlyTempInstalledAddonsCanBeRemoved() {
const { tab, document, window } = await openAboutDebugging("addons");
const { AboutDebugging } = window;
await waitForInitialAddonList(document);
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
await installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
await onListUpdated;
const addon = await getAddonByID("bug1273184@tests");
info("Wait until addon appears in about:debugging#addons");
await waitUntilAddonContainer(PACKAGED_ADDON_NAME, document);
const removeButton = getRemoveButton(document, addon.id);
ok(!removeButton, "remove button is not shown");
await tearDownAddon(AboutDebugging, addon);
await closeAboutDebugging(tab);
});

View File

@ -1,74 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that individual Debug buttons are disabled when "Addons debugging"
// is disabled.
// Test that the buttons are updated dynamically if the preference changes.
const ADDON_ID = "test-devtools@mozilla.org";
const ADDON_NAME = "test-devtools";
add_task(async function() {
info("Turn off addon debugging.");
await new Promise(resolve => {
const options = {
set: [
["devtools.chrome.enabled", false],
["devtools.debugger.remote-enabled", false],
],
};
SpecialPowers.pushPrefEnv(options, resolve);
});
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
info("Install a test addon.");
await installAddon({
document,
path: "addons/unpacked/manifest.json",
name: ADDON_NAME,
});
const addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
ok(!addonDebugCheckbox.checked, "Addons debugging should be disabled.");
info("Check all debug buttons are disabled.");
const debugButtons = [...document.querySelectorAll("#addons .debug-button")];
ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
info("Click on 'Enable addons debugging' checkbox.");
addonDebugCheckbox.click();
info("Wait until all debug buttons are enabled.");
waitUntil(
() => addonDebugCheckbox.checked && areDebugButtonsEnabled(document),
100
);
info("Addons debugging should be enabled and debug buttons are enabled");
info("Click again on 'Enable addons debugging' checkbox.");
addonDebugCheckbox.click();
info("Wait until all debug buttons are enabled.");
waitUntil(() => areDebugButtonsDisabled(document), 100);
info("All debug buttons are disabled again.");
info("Uninstall addon installed earlier.");
await uninstallAddon({ document, id: ADDON_ID, name: ADDON_NAME });
await closeAboutDebugging(tab);
});
function getDebugButtons(document) {
return [...document.querySelectorAll("#addons .debug-button")];
}
function areDebugButtonsEnabled(document) {
return getDebugButtons(document).every(b => !b.disabled);
}
function areDebugButtonsDisabled(document) {
return getDebugButtons(document).every(b => b.disabled);
}

View File

@ -1,41 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that navigating to a about:debugging#invalid-hash should show up an
// error page.
// Every url navigating including #invalid-hash should be kept in history and
// navigate back as expected.
add_task(async function() {
const { tab, document } = await openAboutDebugging("invalid-hash");
let element = document.querySelector(".header-name");
is(element.textContent, "Page not found", "Show error page");
info("Opening addons-panel panel");
document.querySelector("[aria-controls='addons-panel']").click();
await waitUntilElement("#addons-panel", document);
await waitForInitialAddonList(document);
element = document.querySelector(".header-name");
is(element.textContent, "Add-ons", "Show Addons");
info("Opening about:debugging#invalid-hash");
window.openTrustedLinkIn("about:debugging#invalid-hash", "current");
await waitUntilElement(".error-page", document);
element = document.querySelector(".header-name");
is(element.textContent, "Page not found", "Show error page");
gBrowser.goBack();
await waitUntilElement("#addons-panel", document);
await waitForInitialAddonList(document);
element = document.querySelector(".header-name");
is(element.textContent, "Add-ons", "Show Addons");
gBrowser.goBack();
await waitUntilElement(".error-page", document);
element = document.querySelector(".header-name");
is(element.textContent, "Page not found", "Show error page");
await closeAboutDebugging(tab);
});

View File

@ -1,45 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Service workers can't be loaded from chrome://,
// but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
add_task(async function() {
await enableServiceWorkerDebugging();
const { tab, document } = await openAboutDebugging("workers");
const swTab = await addTab(TAB_URL);
const serviceWorkersElement = getServiceWorkerList(document);
await waitUntil(() => {
// Check that the service worker appears in the UI
let names = [...document.querySelectorAll("#service-workers .target-name")];
names = names.map(element => element.textContent);
return names.includes(SERVICE_WORKER);
});
info("The service worker url appears in the list");
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
// Check that the service worker disappeared from the UI
let names = [...document.querySelectorAll("#service-workers .target-name")];
names = names.map(element => element.textContent);
ok(
!names.includes(SERVICE_WORKER),
"The service worker url is no longer in the list: " + names
);
await removeTab(swTab);
await closeAboutDebugging(tab);
});

View File

@ -1,51 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Service workers can't be loaded from chrome://,
// but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
const EMPTY_SW_TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
const FETCH_SW_TAB_URL = URL_ROOT + "service-workers/fetch-sw.html";
async function testBody(url, expecting) {
await enableServiceWorkerDebugging();
const { tab, document } = await openAboutDebugging("workers");
const swTab = await addTab(url);
const serviceWorkersElement = getServiceWorkerList(document);
info("Wait for fetch flag.");
await waitUntil(() => {
let fetchFlags = [
...document.querySelectorAll(
"#service-workers .service-worker-fetch-flag"
),
];
fetchFlags = fetchFlags.map(element => element.textContent);
return fetchFlags.includes(expecting);
}, 100);
info("Found correct fetch flag.");
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
// Check that the service worker disappeared from the UI
let names = [...document.querySelectorAll("#service-workers .target-name")];
names = names.map(element => element.textContent);
ok(names.length == 0, "All service workers were removed from the list.");
await removeTab(swTab);
await closeAboutDebugging(tab);
}
add_task(async function() {
await testBody(FETCH_SW_TAB_URL, "Listening for fetch events.");
await testBody(EMPTY_SW_TAB_URL, "Not listening for fetch events.");
});

View File

@ -1,82 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Service worker debugging is unavailable when multi-e10s is enabled.
// Check that the appropriate warning panel is displayed when there are more than 1
// content process available.
const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
add_task(async function() {
await enableServiceWorkerDebugging();
info("Force two content processes");
await pushPref("dom.ipc.processCount", 2);
const { tab, document } = await openAboutDebugging("workers");
const warningSection = document.querySelector(
".service-worker-multi-process"
);
const img = warningSection.querySelector(".warning");
ok(img, "warning message is rendered");
const serviceWorkersElement = getServiceWorkerList(document);
const swTab = await addTab(TAB_URL, { background: true });
info("Wait for service worker to appear in the list");
// Check that the service worker appears in the UI
const serviceWorkerContainer = await waitUntilServiceWorkerContainer(
SERVICE_WORKER,
document
);
info("Wait until the service worker is running and the Debug button appears");
await waitUntil(() => {
return !!getDebugButton(serviceWorkerContainer);
}, 100);
info("Check that service worker buttons are disabled.");
let debugButton = getDebugButton(serviceWorkerContainer);
ok(debugButton.disabled, "Start/Debug button is disabled");
info("Update the preference to 1");
const onWarningCleared = waitUntil(() => {
const hasWarning = document.querySelector(".service-worker-multi-process");
return !hasWarning && !debugButton.disabled;
}, 100);
await pushPref("dom.ipc.processCount", 1);
await onWarningCleared;
ok(!debugButton.disabled, "Debug button is enabled.");
info("Update the preference back to 2");
const onWarningRestored = waitUntil(() => {
const hasWarning = document.querySelector(".service-worker-multi-process");
return hasWarning && getDebugButton(serviceWorkerContainer).disabled;
}, 100);
await pushPref("dom.ipc.processCount", 2);
await onWarningRestored;
// Update the reference to the debugButton, as the previous DOM element might have been
// deleted.
debugButton = getDebugButton(serviceWorkerContainer);
ok(debugButton.disabled, "Debug button is disabled again.");
info("Unregister service worker");
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
await removeTab(swTab);
await closeAboutDebugging(tab);
});
function getDebugButton(serviceWorkerContainer) {
return serviceWorkerContainer.querySelector(".debug-button");
}

View File

@ -1,56 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that Service Worker section should show warning message in
// about:debugging if any of following conditions is met:
// 1. service worker is disabled
// 2. the about:debugging pannel is openned in private browsing mode
// 3. the about:debugging pannel is openned in private content window
var imgClass = ".service-worker-disabled .warning";
add_task(async function() {
await new Promise(done => {
info("disable service workers");
const options = { set: [["dom.serviceWorkers.enabled", false]] };
SpecialPowers.pushPrefEnv(options, done);
});
const { tab, document } = await openAboutDebugging("workers");
// Check that the warning img appears in the UI
const img = document.querySelector(imgClass);
ok(img, "warning message is rendered");
await closeAboutDebugging(tab);
});
add_task(async function() {
await new Promise(done => {
info("set private browsing mode as default");
const options = { set: [["browser.privatebrowsing.autostart", true]] };
SpecialPowers.pushPrefEnv(options, done);
});
const { tab, document } = await openAboutDebugging("workers");
// Check that the warning img appears in the UI
const img = document.querySelector(imgClass);
ok(img, "warning message is rendered");
await closeAboutDebugging(tab);
});
add_task(async function() {
info("Opening a new private window");
const win = OpenBrowserWindow({ private: true });
await waitForDelayedStartupFinished(win);
const { tab, document } = await openAboutDebugging("workers", win);
// Check that the warning img appears in the UI
const img = document.querySelector(imgClass);
ok(img, "warning message is rendered");
await closeAboutDebugging(tab);
win.close();
});

View File

@ -1,111 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* global sendAsyncMessage */
"use strict";
// Test that clicking on the Push button next to a Service Worker works as
// intended in about:debugging.
// It should trigger a "push" notification in the worker.
// Service workers can't be loaded from chrome://, but http:// is ok with
// dom.serviceWorkers.testing.enabled turned on.
const SERVICE_WORKER = URL_ROOT + "service-workers/push-sw.js";
const TAB_URL = URL_ROOT + "service-workers/push-sw.html";
add_task(async function() {
await enableServiceWorkerDebugging();
const { tab, document } = await openAboutDebugging("workers");
// Listen for mutations in the service-workers list.
const serviceWorkersElement = getServiceWorkerList(document);
// Open a tab that registers a push service worker.
const swTab = await addTab(TAB_URL);
info("Make the test page notify us when the service worker sends a message.");
await ContentTask.spawn(swTab.linkedBrowser, {}, function() {
const win = content.wrappedJSObject;
win.navigator.serviceWorker.addEventListener("message", function(event) {
sendAsyncMessage(event.data);
});
});
// Expect the service worker to claim the test window when activating.
const onClaimed = onTabMessage(swTab, "sw-claimed");
info("Wait until the service worker appears in the UI");
await waitUntilServiceWorkerContainer(SERVICE_WORKER, document);
info(
"Ensure that the registration resolved before trying to interact with " +
"the service worker."
);
await waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
await waitForServiceWorkerActivation(SERVICE_WORKER, document);
info("Wait until the service worker is running");
const container = await waitUntilServiceWorkerContainer(
SERVICE_WORKER,
document
);
await waitUntil(
() => container.querySelector(".target-status").textContent === "Running",
100
);
// Retrieve the Push button for the worker.
const names = [...document.querySelectorAll("#service-workers .target-name")];
const name = names.filter(
element => element.textContent === SERVICE_WORKER
)[0];
ok(name, "Found the service worker in the list");
const targetElement = name.parentNode.parentNode;
const pushBtn = targetElement.querySelector(".push-button");
ok(pushBtn, "Found its push button");
info(
"Wait for the service worker to claim the test window before proceeding."
);
await onClaimed;
info(
"Click on the Push button and wait for the service worker to receive " +
"a push notification"
);
const onPushNotification = onTabMessage(swTab, "sw-pushed");
pushBtn.click();
await onPushNotification;
ok(true, "Service worker received a push notification");
// Finally, unregister the service worker itself.
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
await removeTab(swTab);
await closeAboutDebugging(tab);
});
/**
* Helper to listen once on a message sent using postMessage from the provided tab.
*/
function onTabMessage(tab, message) {
const mm = tab.linkedBrowser.messageManager;
return new Promise(done => {
mm.addMessageListener(message, function listener() {
mm.removeMessageListener(message, listener);
done();
});
});
}

View File

@ -1,114 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that a Service Worker registration's Push Service subscription appears
// in about:debugging if it exists, and disappears when unregistered.
// Service workers can't be loaded from chrome://, but http:// is ok with
// dom.serviceWorkers.testing.enabled turned on.
const SERVICE_WORKER = URL_ROOT + "service-workers/push-sw.js";
const TAB_URL = URL_ROOT + "service-workers/push-sw.html";
const FAKE_ENDPOINT = "https://fake/endpoint";
const PushService = Cc["@mozilla.org/push/Service;1"].getService(
Ci.nsIPushService
).wrappedJSObject;
add_task(async function() {
info("Turn on workers via mochitest http.");
await enableServiceWorkerDebugging();
info("Mock the push service");
PushService.service = {
_registrations: new Map(),
_notify(scope) {
Services.obs.notifyObservers(
null,
PushService.subscriptionModifiedTopic,
scope
);
},
init() {},
register(pageRecord) {
const registration = {
endpoint: FAKE_ENDPOINT,
};
this._registrations.set(pageRecord.scope, registration);
this._notify(pageRecord.scope);
return Promise.resolve(registration);
},
registration(pageRecord) {
return Promise.resolve(this._registrations.get(pageRecord.scope));
},
unregister(pageRecord) {
const deleted = this._registrations.delete(pageRecord.scope);
if (deleted) {
this._notify(pageRecord.scope);
}
return Promise.resolve(deleted);
},
};
const { tab, document } = await openAboutDebugging("workers");
// Listen for mutations in the service-workers list.
const serviceWorkersElement = document.getElementById("service-workers");
// Open a tab that registers a push service worker.
const swTab = await addTab(TAB_URL);
info("Wait until the service worker appears in about:debugging");
await waitUntilServiceWorkerContainer(SERVICE_WORKER, document);
await waitForServiceWorkerActivation(SERVICE_WORKER, document);
// Wait for the service worker details to update.
const names = [...document.querySelectorAll("#service-workers .target-name")];
const name = names.filter(
element => element.textContent === SERVICE_WORKER
)[0];
ok(name, "Found the service worker in the list");
const targetContainer = name.closest(".target-container");
// Retrieve the push subscription endpoint URL, and verify it looks good.
info("Wait for the push URL");
const pushURL = await waitUntilElement(
".service-worker-push-url",
targetContainer
);
info("Found the push service URL in the service worker details");
is(pushURL.textContent, FAKE_ENDPOINT, "The push service URL looks correct");
// Unsubscribe from the push service.
ContentTask.spawn(swTab.linkedBrowser, {}, function() {
const win = content.wrappedJSObject;
return win.sub.unsubscribe();
});
// Wait for the service worker details to update again
info("Wait until the push URL is removed from the UI");
await waitUntil(
() => !targetContainer.querySelector(".service-worker-push-url"),
100
);
info("The push service URL should be removed");
// Finally, unregister the service worker itself.
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
info("Unmock the push service");
PushService.service = null;
await removeTab(swTab);
await closeAboutDebugging(tab);
});

View File

@ -1,83 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that clicking on the Start button next to a Service Worker works as
// intended in about:debugging.
// It should cause a worker to start running in a child process.
// Service workers can't be loaded from chrome://, but http:// is ok with
// dom.serviceWorkers.testing.enabled turned on.
const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
const SW_TIMEOUT = 1000;
add_task(async function() {
await enableServiceWorkerDebugging();
await pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
await pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
const { tab, document } = await openAboutDebugging("workers");
// Listen for mutations in the service-workers list.
const serviceWorkersElement = getServiceWorkerList(document);
// Open a tab that registers an empty service worker.
const swTab = await addTab(TAB_URL);
// Wait for the service-workers list to update.
info("Wait until the service worker appears in about:debugging");
await waitUntilServiceWorkerContainer(SERVICE_WORKER, document);
info(
"Ensure that the registration resolved before trying to interact with " +
"the service worker."
);
await waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
await waitForServiceWorkerActivation(SERVICE_WORKER, document);
// Retrieve the Target element corresponding to the service worker.
const names = [...document.querySelectorAll("#service-workers .target-name")];
const name = names.filter(
element => element.textContent === SERVICE_WORKER
)[0];
ok(name, "Found the service worker in the list");
const targetElement = name.parentNode.parentNode;
// The service worker may already be killed with the low 1s timeout.
// At this stage, either the SW is started and the Debug button is visible or was
// already stopped and the start button is visible. Wait until the service worker is
// stopped.
info("Wait until the start button is visible");
await waitUntilElement(".start-button", targetElement);
// We should now have a Start button but no Debug button.
const startBtn = targetElement.querySelector(".start-button");
ok(startBtn, "Found its start button");
ok(!targetElement.querySelector(".debug-button"), "No debug button");
// Click on the Start button and wait for the service worker to be back.
startBtn.click();
info("Wait until the service worker starts and the debug button appears");
await waitUntilElement(".debug-button", targetElement);
info("Found the debug button");
// Check that we have a Debug button but not a Start button again.
ok(!targetElement.querySelector(".start-button"), "No start button");
// Finally, unregister the service worker itself.
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
await removeTab(swTab);
await closeAboutDebugging(tab);
});

View File

@ -1,57 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Service workers can't be loaded from chrome://,
// but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
const SERVICE_WORKER = URL_ROOT + "service-workers/delay-sw.js";
const TAB_URL = URL_ROOT + "service-workers/delay-sw.html";
const SW_TIMEOUT = 2000;
requestLongerTimeout(2);
add_task(async function() {
await enableServiceWorkerDebugging();
await pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
await pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
const { tab, document } = await openAboutDebugging("workers");
// Listen for mutations in the service-workers list.
const serviceWorkersElement = getServiceWorkerList(document);
const swTab = await addTab(TAB_URL);
info("Wait until the service worker appears in about:debugging");
const container = await waitUntilServiceWorkerContainer(
SERVICE_WORKER,
document
);
// We should ideally check that the service worker registration goes through the
// "registering" and "running" steps, but it is difficult to workaround race conditions
// for a test running on a wide variety of platforms. Due to intermittent failures, we
// simply check that the registration transitions to "stopped".
const status = container.querySelector(".target-status");
await waitUntil(() => status.textContent == "Stopped", 100);
is(status.textContent, "Stopped", "Service worker is currently stopped");
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker unregistered");
} catch (e) {
ok(false, "Service worker not unregistered; " + e);
}
// Check that the service worker disappeared from the UI
let names = [...document.querySelectorAll("#service-workers .target-name")];
names = names.map(element => element.textContent);
ok(
!names.includes(SERVICE_WORKER),
"The service worker url is no longer in the list: " + names
);
await removeTab(swTab);
await closeAboutDebugging(tab);
});

View File

@ -1,88 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
// Service workers can't be loaded from chrome://,
// but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
const SERVICE_WORKER = URL_ROOT + "service-workers/empty-sw.js";
const TAB_URL = URL_ROOT + "service-workers/empty-sw.html";
const SW_TIMEOUT = 1000;
add_task(async function() {
await enableServiceWorkerDebugging();
await pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
await pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
const { tab, document } = await openAboutDebugging("workers");
const serviceWorkersElement = getServiceWorkerList(document);
const swTab = await addTab(TAB_URL);
info("Wait until the service worker appears in about:debugging");
await waitUntilServiceWorkerContainer(SERVICE_WORKER, document);
// Ensure that the registration resolved before trying to connect to the sw
await waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
// Retrieve the DEBUG button for the worker
const names = [...document.querySelectorAll("#service-workers .target-name")];
const name = names.filter(
element => element.textContent === SERVICE_WORKER
)[0];
ok(name, "Found the service worker in the list");
const targetElement = name.parentNode.parentNode;
const debugBtn = targetElement.querySelector(".debug-button");
ok(debugBtn, "Found its debug button");
// Click on it and wait for the toolbox to be ready
const onToolboxReady = new Promise(done => {
gDevTools.once("toolbox-ready", function(toolbox) {
done(toolbox);
});
});
debugBtn.click();
let toolbox = await onToolboxReady;
// Wait for more than the regular timeout,
// so that if the worker freezing doesn't work,
// it will be destroyed and removed from the list
await new Promise(done => {
setTimeout(done, SW_TIMEOUT * 2);
});
assertHasTarget(true, document, "service-workers", SERVICE_WORKER);
ok(
targetElement.querySelector(".debug-button"),
"The debug button is still there"
);
await toolbox.destroy();
toolbox = null;
// Now ensure that the worker is correctly destroyed
// after we destroy the toolbox.
// The DEBUG button should disappear once the worker is destroyed.
info("Wait until the debug button disappears");
await waitUntil(() => {
return !targetElement.querySelector(".debug-button");
});
// Finally, unregister the service worker itself.
try {
await unregisterServiceWorker(swTab, serviceWorkersElement);
ok(true, "Service worker registration unregistered");
} catch (e) {
ok(false, "SW not unregistered; " + e);
}
assertHasTarget(false, document, "service-workers", SERVICE_WORKER);
await removeTab(swTab);
await closeAboutDebugging(tab);
});

View File

@ -1,65 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that clicking on the unregister link in the Service Worker details works
// as intended in about:debugging.
// It should unregister the service worker, which should trigger an update of
// the displayed list of service workers.
// Service workers can't be loaded from chrome://, but http:// is ok with
// dom.serviceWorkers.testing.enabled turned on.
const SCOPE = URL_ROOT + "service-workers/";
const SERVICE_WORKER = SCOPE + "empty-sw.js";
const TAB_URL = SCOPE + "empty-sw.html";
add_task(async function() {
await enableServiceWorkerDebugging();
const { tab, document } = await openAboutDebugging("workers");
// Open a tab that registers an empty service worker.
const swTab = await addTab(TAB_URL);
info("Wait until the service worker appears in about:debugging");
await waitUntilServiceWorkerContainer(SERVICE_WORKER, document);
await waitForServiceWorkerActivation(SERVICE_WORKER, document);
info(
"Ensure that the registration resolved before trying to interact with " +
"the service worker."
);
await waitForServiceWorkerRegistered(swTab);
ok(true, "Service worker registration resolved");
const targets = document.querySelectorAll("#service-workers .target");
is(targets.length, 1, "One service worker is now displayed.");
const target = targets[0];
const name = target.querySelector(".target-name");
is(name.textContent, SERVICE_WORKER, "Found the service worker in the list");
info("Check the scope displayed scope is correct");
const scope = target.querySelector(".service-worker-scope");
is(
scope.textContent,
SCOPE,
"The expected scope is displayed in the service worker info."
);
info("Unregister the service worker via the unregister link.");
const unregisterLink = target.querySelector(".unregister-link");
ok(unregisterLink, "Found the unregister link");
unregisterLink.click();
info("Wait until the service worker disappears");
await waitUntil(() => {
return !document.querySelector("#service-workers .target");
});
await removeTab(swTab);
await closeAboutDebugging(tab);
});

View File

@ -1,79 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TAB_URL = "data:text/html,<title>foo</title>";
add_task(async function() {
const { tab, document } = await openAboutDebugging("tabs");
// Wait for initial tabs list which may be empty
let tabsElement = getTabList(document);
await waitUntilElement(".target-name", tabsElement);
// Refresh tabsElement to get the .target-list element
tabsElement = getTabList(document);
let names = [...tabsElement.querySelectorAll(".target-name")];
const initialTabCount = names.length;
info("Open a new background tab");
const newTab = await addTab(TAB_URL, { background: true });
info("Wait for the tab to appear in the list with the correct name");
const container = await waitUntilTabContainer("foo", document);
info("Wait until the title to update");
await waitUntil(() => {
return container.querySelector(".target-name").title === TAB_URL;
}, 100);
const icon = container.querySelector(".target-icon");
ok(icon && icon.src, "Tab icon found and src attribute is not empty");
info("Check if the tab icon is a valid image");
await new Promise(r => {
const image = new Image();
image.onload = () => {
ok(true, "Favicon is not a broken image");
r();
};
image.onerror = () => {
ok(false, "Favicon is a broken image");
r();
};
image.src = icon.src;
});
// Finally, close the tab
await removeTab(newTab);
info("Wait until the tab container is removed");
await waitUntil(() => !getTabContainer("foo", document), 100);
// Check that the tab disappeared from the UI
names = [...tabsElement.querySelectorAll("#tabs .target-name")];
is(names.length, initialTabCount, "The tab disappeared from the UI");
await closeAboutDebugging(tab);
});
function getTabContainer(name, document) {
const nameElements = [...document.querySelectorAll("#tabs .target-name")];
const nameElement = nameElements.filter(
element => element.textContent === name
)[0];
if (nameElement) {
return nameElement.closest(".target-container");
}
return null;
}
async function waitUntilTabContainer(name, document) {
await waitUntil(() => {
return getTabContainer(name, document);
});
return getTabContainer(name, document);
}

View File

@ -1,511 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env browser */
/* eslint no-unused-vars: [2, {"vars": "local"}] */
/* import-globals-from ../../shared/test/shared-head.js */
"use strict";
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
this
);
const { AddonManager } = ChromeUtils.import(
"resource://gre/modules/AddonManager.jsm"
);
const { Management } = ChromeUtils.import(
"resource://gre/modules/Extension.jsm",
null
);
const { ExtensionTestCommon } = ChromeUtils.import(
"resource://testing-common/ExtensionTestCommon.jsm"
);
async function openAboutDebugging(page, win) {
info("Turn off the new about:debugging for the test");
await pushPref("devtools.aboutdebugging.new-enabled", false);
info("opening about:debugging");
let url = "about:debugging";
if (page) {
url += "#" + page;
}
const tab = await addTab(url, { window: win });
const browser = tab.linkedBrowser;
const document = browser.contentDocument;
const window = browser.contentWindow;
info("Wait until the main about debugging container is available");
await waitUntilElement(".app", document);
return { tab, document, window };
}
function closeAboutDebugging(tab) {
info("Closing about:debugging");
return removeTab(tab);
}
function getSupportsFile(path) {
const cr = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
Ci.nsIChromeRegistry
);
const uri = Services.io.newURI(CHROME_URL_ROOT + path);
const fileurl = cr.convertChromeURL(uri);
return fileurl.QueryInterface(Ci.nsIFileURL);
}
/**
* Depending on whether there are addons installed, return either a target list
* element or its container.
* @param {DOMDocument} document #addons section container document
* @return {DOMNode} target list or container element
*/
function getAddonList(document) {
return (
document.querySelector("#addons .target-list") ||
document.querySelector("#addons .targets")
);
}
/**
* Depending on whether there are temporary addons installed, return either a
* target list element or its container.
* @param {DOMDocument} document #temporary-addons section container document
* @return {DOMNode} target list or container element
*/
function getTemporaryAddonList(document) {
return (
document.querySelector("#temporary-addons .target-list") ||
document.querySelector("#temporary-addons .targets")
);
}
/**
* Depending on whether the addon is installed, return either the addon list
* element or throw an Error.
* @param {DOMDocument} document addon section container document
* @return {DOMNode} target list
* @throws {Error} add-on not found error
*/
function getAddonListWithAddon(document, id) {
const addon = document.querySelector(`[data-addon-id="${id}"]`);
if (!addon) {
throw new Error("couldn't find add-on by id");
}
return addon.closest(".target-list");
}
function getInstalledAddonNames(document) {
const selector = "#addons .target-name, #temporary-addons .target-name";
return [...document.querySelectorAll(selector)];
}
/**
* Depending on whether there are service workers installed, return either a
* target list element or its container.
* @param {DOMDocument} document #service-workers section container document
* @return {DOMNode} target list or container element
*/
function getServiceWorkerList(document) {
return (
document.querySelector("#service-workers .target-list") ||
document.querySelector("#service-workers.targets")
);
}
/**
* Retrieve the container element for the service worker corresponding to the provided
* name.
*
* @param {String} name
* expected service worker name
* @param {DOMDocument} document
* #service-workers section container document
* @return {DOMNode} container element
*/
function getServiceWorkerContainer(name, document) {
const nameElements = [
...document.querySelectorAll("#service-workers .target-name"),
];
const nameElement = nameElements.filter(
element => element.textContent === name
)[0];
if (nameElement) {
return nameElement.closest(".target-container");
}
return null;
}
/**
* Wait until a service worker "container" element is found with a specific service worker
* name, in the provided document.
* Returns a promise that resolves the service worker container element.
*
* @param {String} name
* expected service worker name
* @param {DOMDocument} document
* #service-workers section container document
* @return {Promise} promise that resolves the service worker container element.
*/
async function waitUntilServiceWorkerContainer(name, document) {
await waitUntil(() => {
return getServiceWorkerContainer(name, document);
}, 100);
return getServiceWorkerContainer(name, document);
}
/**
* Wait until a selector matches an element in a given parent node.
* Returns a promise that resolves the matched element.
*
* @param {String} selector
* CSS selector to match.
* @param {DOMNode} parent
* Parent that should contain the element.
* @return {Promise} promise that resolves the matched DOMNode.
*/
async function waitUntilElement(selector, parent) {
await waitUntil(() => {
return parent.querySelector(selector);
}, 100);
return parent.querySelector(selector);
}
/**
* Depending on whether there are tabs opened, return either a
* target list element or its container.
* @param {DOMDocument} document #tabs section container document
* @return {DOMNode} target list or container element
*/
function getTabList(document) {
return (
document.querySelector("#tabs .target-list") ||
document.querySelector("#tabs.targets")
);
}
async function installAddon({ document, path, file, name }) {
// Mock the file picker to select a test addon
const MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
if (path) {
file = getSupportsFile(path);
MockFilePicker.setFiles([file.file]);
} else {
MockFilePicker.setFiles([file]);
}
const onAddonInstalled = new Promise(done => {
Management.on("startup", function listener(event, extension) {
if (extension.name != name) {
return;
}
Management.off("startup", listener);
done();
});
});
const AboutDebugging = document.defaultView.AboutDebugging;
// List updated twice:
// - AddonManager's onInstalled event
// - WebExtension's Management's startup event.
const onListUpdated = waitForNEvents(AboutDebugging, "addons-updated", 2);
// Trigger the file picker by clicking on the button
document.getElementById("load-addon-from-file").click();
await onListUpdated;
await onAddonInstalled;
ok(true, "Addon installed and running its bootstrap.js file");
info("Wait for the addon to appear in the UI");
await waitUntilAddonContainer(name, document);
}
async function uninstallAddon({ document, id, name }) {
// Now uninstall this addon
await new Promise(async done => {
const addon = await AddonManager.getAddonByID(id);
const listener = {
onUninstalled: function(uninstalledAddon) {
if (uninstalledAddon != addon) {
return;
}
AddonManager.removeAddonListener(listener);
done();
},
};
AddonManager.addAddonListener(listener);
addon.uninstall();
});
info("Wait until the addon is removed from about:debugging");
await waitUntil(() => !getAddonContainer(name, document), 100);
}
function getAddonCount(document) {
const addonListContainer = getAddonList(document);
const addonElements = addonListContainer.querySelectorAll(".target");
return addonElements.length;
}
/**
* Returns a promise that will resolve when the add-on list has been updated.
*
* @param {Node} document
* @return {Promise}
*/
function waitForInitialAddonList(document) {
info(
"Waiting for add-ons to load. Current add-on count: " +
getAddonCount(document)
);
return waitUntil(() => getAddonCount(document) > 0, 100);
}
function getAddonContainer(name, document) {
const nameElements = [
...document.querySelectorAll("#addons-panel .target-name"),
];
const nameElement = nameElements.filter(
element => element.textContent === name
)[0];
if (nameElement) {
return nameElement.closest(".addon-target-container");
}
return null;
}
async function waitUntilAddonContainer(name, document) {
await waitUntil(() => {
return getAddonContainer(name, document);
});
return getAddonContainer(name, document);
}
/**
* Checks if an about:debugging TargetList element contains a Target element
* corresponding to the specified name.
* @param {Boolean} expected
* @param {Document} document
* @param {String} type
* @param {String} name
*/
function assertHasTarget(expected, document, type, name) {
let names = [...document.querySelectorAll("#" + type + " .target-name")];
names = names.map(element => element.textContent);
is(
names.includes(name),
expected,
"The " + type + " url appears in the list: " + names
);
}
/**
* Returns a promise that will resolve after the service worker in the page
* has successfully registered itself.
* @param {Tab} tab
* @return {Promise} Resolves when the service worker is registered.
*/
function waitForServiceWorkerRegistered(tab) {
return ContentTask.spawn(tab.linkedBrowser, {}, async function() {
// Retrieve the `sw` promise created in the html page.
const { sw } = content.wrappedJSObject;
await sw;
});
}
/**
* Asks the service worker within the test page to unregister, and returns a
* promise that will resolve when it has successfully unregistered itself and the
* about:debugging UI has fully processed this update.
*
* @param {Tab} tab
* @param {Node} serviceWorkersElement
* @return {Promise} Resolves when the service worker is unregistered.
*/
async function unregisterServiceWorker(tab, serviceWorkersElement) {
// Get the initial count of service worker registrations.
let registrations = serviceWorkersElement.querySelectorAll(
".target-container"
);
const registrationCount = registrations.length;
// Wait until the registration count is decreased by one.
const isRemoved = waitUntil(() => {
registrations = serviceWorkersElement.querySelectorAll(".target-container");
return registrations.length === registrationCount - 1;
}, 100);
// Unregister the service worker from the content page
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
// Retrieve the `sw` promise created in the html page
const { sw } = content.wrappedJSObject;
const registration = await sw;
await registration.unregister();
});
return isRemoved;
}
/**
* Waits for the creation of a new window, usually used with create private
* browsing window.
* Returns a promise that will resolve when the window is successfully created.
* @param {window} win
*/
function waitForDelayedStartupFinished(win) {
return new Promise(function(resolve) {
Services.obs.addObserver(function observer(subject, topic) {
if (win == subject) {
Services.obs.removeObserver(observer, topic);
resolve();
}
}, "browser-delayed-startup-finished");
});
}
/**
* open the about:debugging page and install an addon
*/
async function setupTestAboutDebuggingWebExtension(name, file) {
await new Promise(resolve => {
const options = {
set: [
// Force enabling of addons debugging
["devtools.chrome.enabled", true],
["devtools.debugger.remote-enabled", true],
// Disable security prompt
["devtools.debugger.prompt-connection", false],
// Enable Browser toolbox test script execution via env variable
["devtools.browser-toolbox.allow-unsafe-script", true],
],
};
SpecialPowers.pushPrefEnv(options, resolve);
});
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
await installAddon({
document,
file,
name,
});
// Retrieve the DEBUG button for the addon
const names = getInstalledAddonNames(document);
const nameEl = names.filter(element => element.textContent === name)[0];
ok(name, "Found the addon in the list");
const targetElement = nameEl.parentNode.parentNode;
const debugBtn = targetElement.querySelector(".debug-button");
ok(debugBtn, "Found its debug button");
return { tab, document, debugBtn };
}
/**
* Wait for aboutdebugging to be notified about the activation of the service worker
* corresponding to the provided service worker url.
*/
async function waitForServiceWorkerActivation(swUrl, document) {
const serviceWorkersElement = getServiceWorkerList(document);
const names = serviceWorkersElement.querySelectorAll(".target-name");
const name = [...names].filter(element => element.textContent === swUrl)[0];
const targetElement = name.parentNode.parentNode;
const targetStatus = targetElement.querySelector(".target-status");
await waitUntil(() => {
return targetStatus.textContent !== "Registering";
}, 100);
}
/**
* Set all preferences needed to enable service worker debugging and testing.
*/
async function enableServiceWorkerDebugging() {
const options = {
set: [
// Enable service workers.
["dom.serviceWorkers.enabled", true],
["dom.push.enabled", true],
// Accept workers from mochitest's http.
["dom.serviceWorkers.testing.enabled", true],
// Force single content process.
["dom.ipc.processCount", 1],
],
};
// Wait for dom.ipc.processCount to be updated before releasing processes.
await new Promise(done => {
SpecialPowers.pushPrefEnv(options, done);
});
Services.ppmm.releaseCachedProcesses();
}
/**
* Returns a promise that resolves when the given add-on event is fired. The
* resolved value is an array of arguments passed for the event.
*/
function promiseAddonEvent(event) {
return new Promise(resolve => {
const listener = {
[event]: function(...args) {
AddonManager.removeAddonListener(listener);
resolve(args);
},
};
AddonManager.addAddonListener(listener);
});
}
/**
* Install an add-on using the AddonManager so it does not show up as temporary.
*/
function installAddonWithManager(filePath) {
return new Promise(async (resolve, reject) => {
const install = await AddonManager.getInstallForFile(filePath);
if (!install) {
throw new Error(`An install was not created for ${filePath}`);
}
install.addListener({
onDownloadFailed: reject,
onDownloadCancelled: reject,
onInstallFailed: reject,
onInstallCancelled: reject,
onInstallEnded: resolve,
});
install.install();
});
}
function getAddonByID(addonId) {
return AddonManager.getAddonByID(addonId);
}
/**
* Uninstall an add-on.
*/
async function tearDownAddon(AboutDebugging, addon) {
const onUninstalled = promiseAddonEvent("onUninstalled");
const onListUpdated = once(AboutDebugging, "addons-updated");
addon.uninstall();
await onListUpdated;
const [uninstalledAddon] = await onUninstalled;
is(
uninstalledAddon.id,
addon.id,
`Add-on was uninstalled: ${uninstalledAddon.id}`
);
}

View File

@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Service worker test</title>
</head>
<body>
<script type="text/javascript">
"use strict";
var sw = navigator.serviceWorker.register("delay-sw.js");
sw.then(
function() {
dump("SW registered\n");
},
function(e) {
dump("SW not registered: " + e + "\n");
}
);
</script>
</body>
</html>

View File

@ -1,17 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env worker */
"use strict";
function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// Wait for one second to switch from installing to installed.
self.addEventListener("install", function(event) {
event.waitUntil(wait(1000));
});

View File

@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Service worker test</title>
</head>
<body>
<script type="text/javascript">
"use strict";
var sw = navigator.serviceWorker.register("empty-sw.js");
sw.then(
function() {
dump("SW registered\n");
},
function(e) {
dump("SW not registered: " + e + "\n");
}
);
</script>
</body>
</html>

View File

@ -1 +0,0 @@
// Empty, just test registering.

View File

@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Service worker test</title>
</head>
<body>
<script type="text/javascript">
"use strict";
var sw = navigator.serviceWorker.register("fetch-sw.js", {scope: "fetch-sw/"});
sw.then(
function() {
dump("SW registered\n");
},
function(e) {
dump("SW not registered: " + e + "\n");
}
);
</script>
</body>
</html>

View File

@ -1,6 +0,0 @@
"use strict";
// Bug 1328293
self.onfetch = function(event) {
// do nothing.
};

View File

@ -1,43 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Service worker push test</title>
</head>
<body>
<script type="text/javascript">
/* exported sw */
"use strict";
// The subscription is expected as a global by browser_service_workers_push_service.js
var sub = null;
// The registration promise is expected as a global by head.js's unregisterServiceWorker.
var sw = (async function() {
await new Promise(resolve => {
const perm = { type: "desktop-notification", allow: true, context: document };
SpecialPowers.pushPermissions([perm], resolve);
});
const registrationPromise = navigator.serviceWorker.register("push-sw.js");
try {
const registration = await registrationPromise;
dump("SW registered\n");
try {
sub = await registration.pushManager.subscribe();
dump("SW subscribed to push: " + sub.endpoint + "\n");
} catch (e) {
dump("SW not subscribed to push: " + e + "\n");
}
} catch (e) {
dump("SW not registered: " + e + "\n");
}
return registrationPromise;
})();
</script>
</body>
</html>

View File

@ -1,35 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-env worker */
/* global clients */
"use strict";
// Send a message to all controlled windows.
function postMessage(message) {
return clients.matchAll().then(function(clientlist) {
clientlist.forEach(function(client) {
client.postMessage(message);
});
});
}
// Don't wait for the next page load to become the active service worker.
self.addEventListener("install", function(event) {
event.waitUntil(self.skipWaiting());
});
// Claim control over the currently open test page when activating.
self.addEventListener("activate", function(event) {
event.waitUntil(
self.clients.claim().then(function() {
return postMessage("sw-claimed");
})
);
});
// Forward all "push" events to the controlled window.
self.addEventListener("push", function(event) {
event.waitUntil(postMessage("sw-pushed"));
});

View File

@ -5,8 +5,7 @@
const { utils: Cu } = Components;
const { BrowserLoader } = Cu.import(
"resource://devtools/client/shared/browser-loader.js",
{}
"resource://devtools/client/shared/browser-loader.js"
);
// Module Loader

View File

@ -6,7 +6,8 @@
"node": ">=8.9.4"
},
"scripts": {
"test": "jest"
"test": "jest",
"test-ci": "jest --json"
},
"dependencies": {
"jest": "^23.0.0",

View File

@ -21,12 +21,18 @@ const Provider = createFactory(
require("devtools/client/shared/vendor/react-redux").Provider
);
const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
const { L10nRegistry } = require("resource://gre/modules/L10nRegistry.jsm");
const Services = require("Services");
const { l10n } = require("./src/modules/l10n");
const { configureStore } = require("./src/create-store");
const actions = require("./src/actions/index");
const { WorkersListener } =
require("devtools/client/shared/workers-listener");
// NOTE: this API may change names for these functions. See Bug 1531349.
const { addMultiE10sListener, isMultiE10s, removeMultiE10sListener } =
require("devtools/shared/multi-e10s-helper");
const App = createFactory(require("./src/components/App"));
/**
@ -37,6 +43,7 @@ window.Application = {
async bootstrap({ toolbox, panel }) {
this.updateWorkers = this.updateWorkers.bind(this);
this.updateDomain = this.updateDomain.bind(this);
this.updateCanDebugWorkers = this.updateCanDebugWorkers.bind(this);
this.mount = document.querySelector("#mount");
this.toolbox = toolbox;
@ -51,75 +58,47 @@ window.Application = {
return toolbox.selectTool(toolId);
},
};
this.toolbox.target.on("workerListChanged", this.updateWorkers);
this.client.mainRoot.on(
"serviceWorkerRegistrationListChanged",
this.updateWorkers
);
this.client.mainRoot.on("processListChanged", this.updateWorkers);
this.client.mainRoot.onFront("serviceWorkerRegistration", front => {
this.serviceWorkerRegistrationFronts.push(front);
front.on("push-subscription-modified", this.updateWorkers);
front.on("registration-changed", this.updateWorkers);
});
this.toolbox.target.on("navigate", this.updateDomain);
this.workersListener = new WorkersListener(this.client.mainRoot);
this.workersListener.addListener(this.updateWorkers);
this.toolbox.target.on("navigate", this.updateDomain);
addMultiE10sListener(this.updateCanDebugWorkers);
// start up updates for the initial state
this.updateDomain();
this.updateCanDebugWorkers();
await this.updateWorkers();
const fluentBundles = await this.createFluentBundles();
await l10n.init(["devtools/application.ftl"]);
// Render the root Application component.
const app = App({ client: this.client, fluentBundles, serviceContainer });
const app = App({
client: this.client,
fluentBundles: l10n.getBundles(),
serviceContainer,
});
render(Provider({ store: this.store }, app), this.mount);
},
/**
* Retrieve message contexts for the current locales, and return them as an array of
* FluentBundles elements.
*/
async createFluentBundles() {
const locales = Services.locale.appLocalesAsBCP47;
const generator = L10nRegistry.generateBundles(locales, [
"devtools/application.ftl",
]);
// Return value of generateBundles is a generator and should be converted to
// a sync iterable before using it with React.
const contexts = [];
for await (const message of generator) {
contexts.push(message);
}
return contexts;
},
async updateWorkers() {
const { service } = await this.client.mainRoot.listAllWorkers();
this.actions.updateWorkers(service);
},
removeRegistrationFrontListeners() {
for (const front of this.serviceWorkerRegistrationFronts) {
front.off("push-subscription-modified", this.updateWorkers);
front.off("registration-changed", this.updateWorkers);
}
this.serviceWorkerRegistrationFronts = [];
},
updateDomain() {
this.actions.updateDomain(this.toolbox.target.url);
},
updateCanDebugWorkers() {
// NOTE: this API may change names for this function. See Bug 1531349.
const canDebugWorkers = !isMultiE10s();
this.actions.updateCanDebugWorkers(canDebugWorkers);
},
destroy() {
this.toolbox.target.off("workerListChanged", this.updateWorkers);
this.client.mainRoot.off(
"serviceWorkerRegistrationListChanged",
this.updateWorkers
);
this.client.mainRoot.off("processListChanged", this.updateWorkers);
this.removeRegistrationFrontListeners();
this.workersListener.removeListener();
this.toolbox.target.off("navigate", this.updateDomain);
removeMultiE10sListener(this.updateCanDebugWorkers);
unmountComponentAtNode(this.mount);
this.mount = null;

View File

@ -12,4 +12,6 @@ DevToolsModules(
'panel.js'
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
BROWSER_CHROME_MANIFESTS += [
'test/browser/browser.ini'
]

View File

@ -4,7 +4,7 @@
"use strict";
const { UPDATE_WORKERS } = require("../constants");
const { UPDATE_CAN_DEBUG_WORKERS, UPDATE_WORKERS } = require("../constants");
function updateWorkers(workers) {
return {
@ -13,6 +13,14 @@ function updateWorkers(workers) {
};
}
function updateCanDebugWorkers(canDebugWorkers) {
return {
type: UPDATE_CAN_DEBUG_WORKERS,
canDebugWorkers,
};
}
module.exports = {
updateCanDebugWorkers,
updateWorkers,
};

View File

@ -22,11 +22,11 @@ a:visited {
cursor: pointer;
}
a.disabled,
a.disabled:hover,
a.disabled:visited {
a.disabled-link,
a.disabled-link:hover,
a.disabled-link:visited {
opacity: 0.5 !important;
cursor: default;
cursor: not-allowed;
}
/*
@ -56,4 +56,4 @@ a.disabled:visited {
.application:not(.application--empty) {
grid-template-rows: 1fr auto;
}
}

View File

@ -24,42 +24,58 @@ const WorkerListEmpty = createFactory(require("./WorkerListEmpty"));
class App extends Component {
static get propTypes() {
return {
// mapped from state
canDebugWorkers: PropTypes.bool.isRequired,
client: PropTypes.object.isRequired,
workers: PropTypes.array.isRequired,
serviceContainer: PropTypes.object.isRequired,
// mapped from state
domain: PropTypes.string.isRequired,
fluentBundles: PropTypes.array.isRequired,
serviceContainer: PropTypes.object.isRequired,
// mapped from state
workers: PropTypes.array.isRequired,
};
}
render() {
let {
workers,
domain,
const {
canDebugWorkers,
client,
serviceContainer,
domain,
fluentBundles,
serviceContainer,
workers,
} = this.props;
// Filter out workers from other domains
workers = workers.filter(x => new URL(x.url).hostname === domain);
const isEmpty = workers.length === 0;
const domainWorkers = workers.filter(
x => new URL(x.url).hostname === domain
);
const isWorkerListEmpty = domainWorkers.length === 0;
return LocalizationProvider(
{ messages: fluentBundles },
main(
{ className: `application ${isEmpty ? "application--empty" : ""}` },
isEmpty
{
className: `application ${
isWorkerListEmpty ? "application--empty" : ""
}`,
},
isWorkerListEmpty
? WorkerListEmpty({ serviceContainer })
: WorkerList({ workers, client })
: WorkerList({ canDebugWorkers, client, workers: domainWorkers })
)
);
}
}
function mapStateToProps(state) {
return {
canDebugWorkers: state.workers.canDebugWorkers,
domain: state.page.domain,
workers: state.workers.list,
};
}
// Exports
module.exports = connect(state => ({
workers: state.workers.list,
domain: state.page.domain,
}))(App);
module.exports = connect(mapStateToProps)(App);

View File

@ -53,7 +53,7 @@ class Worker extends Component {
static get propTypes() {
return {
client: PropTypes.instanceOf(DebuggerClient).isRequired,
debugDisabled: PropTypes.bool,
isDebugEnabled: PropTypes.bool.isRequired,
worker: PropTypes.shape({
active: PropTypes.bool,
name: PropTypes.string.isRequired,
@ -84,6 +84,11 @@ class Worker extends Component {
}
start() {
if (!this.props.isDebugEnabled) {
console.log("Service workers cannot be started in multi-e10s");
return;
}
if (!this.isActive() || this.isRunning()) {
console.log("Running or inactive service workers cannot be started");
return;
@ -129,6 +134,52 @@ class Worker extends Component {
return getUnicodeUrlPath(parts[parts.length - 1]);
}
renderDebugLink() {
const { isDebugEnabled } = this.props;
const shallDisableLink = !this.isRunning() || !isDebugEnabled;
const linkClass = shallDisableLink ? "disabled-link" : "";
const localizationId = isDebugEnabled
? "serviceworker-worker-debug"
: "serviceworker-worker-debug-forbidden";
const link = Localized(
{
id: localizationId,
// The localized title is only displayed if the debug link is disabled.
attrs: {
title: shallDisableLink,
},
},
a({
onClick: !shallDisableLink ? this.debug : null,
className: `${linkClass} worker__debug-link js-debug-link`,
})
);
return link;
}
renderStartLink() {
const { isDebugEnabled } = this.props;
const linkClass = !isDebugEnabled ? "disabled-link" : "";
const link = Localized(
{
id: "serviceworker-worker-start2",
// The localized title is only displayed if the debug link is disabled.
attrs: {
title: !isDebugEnabled,
},
},
a({
onClick: this.start,
className: `worker__start-link js-start-link ${linkClass}`,
})
);
return link;
}
render() {
const { worker } = this.props;
const status = this.getServiceWorkerStatus();
@ -145,30 +196,6 @@ class Worker extends Component {
)
: null;
const debugLinkDisabled = this.isRunning() ? "" : "disabled";
const debugLink = Localized(
{
id: "serviceworker-worker-debug",
// The localized title is only displayed if the debug link is disabled.
attrs: { title: !this.isRunning() },
},
a({
onClick: this.isRunning() ? this.debug : null,
className: `${debugLinkDisabled} worker__debug-link js-debug-link`,
})
);
const startLink = !this.isRunning()
? Localized(
{ id: "serviceworker-worker-start" },
a({
onClick: this.start,
className: "worker__start-link",
})
)
: null;
const lastUpdated = worker.lastUpdateTime
? Localized(
{
@ -208,7 +235,7 @@ class Worker extends Component {
},
this.formatSource(worker.url)
),
debugLink,
this.renderDebugLink(),
lastUpdated ? br({}) : null,
lastUpdated ? lastUpdated : null
),
@ -218,8 +245,11 @@ class Worker extends Component {
),
dd(
{},
Localized({ id: "serviceworker-worker-status-" + status }, span({})),
startLink
Localized(
{ id: "serviceworker-worker-status-" + status },
span({ className: "js-worker-status" })
),
!this.isRunning() ? this.renderStartLink() : null
)
)
);

View File

@ -30,13 +30,14 @@ const Localized = createFactory(FluentReact.Localized);
class WorkerList extends Component {
static get propTypes() {
return {
canDebugWorkers: PropTypes.bool.isRequired,
client: PropTypes.object.isRequired,
workers: PropTypes.object.isRequired,
workers: PropTypes.array.isRequired,
};
}
render() {
const { workers, client } = this.props;
const { canDebugWorkers, client, workers } = this.props;
return [
article(
@ -47,7 +48,7 @@ class WorkerList extends Component {
workers.map(worker =>
Worker({
client,
debugDisabled: false,
isDebugEnabled: canDebugWorkers,
worker,
})
)
@ -55,7 +56,7 @@ class WorkerList extends Component {
),
Localized(
{
id: "serviceworker-list-aboutdebugging",
id: "serviceworker-list-aboutdebugging2",
a: a({
className: "aboutdebugging-plug__link",
onClick: () => openTrustedLink("about:debugging#workers"),

View File

@ -55,7 +55,7 @@ class WorkerListEmpty extends Component {
render() {
return article(
{ className: "worker-list-empty" },
{ className: "worker-list-empty js-worker-list-empty" },
Localized(
{
id: "serviceworker-empty-intro",

View File

@ -5,8 +5,9 @@
"use strict";
const actionTypes = {
UPDATE_WORKERS: "UPDATE_WORKERS",
UPDATE_DOMAIN: "UPDATE_DOMAIN",
UPDATE_CAN_DEBUG_WORKERS: "UPDATE_CAN_DEBUG_WORKERS",
UPDATE_WORKERS: "UPDATE_WORKERS",
};
// flatten constants

View File

@ -0,0 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { FluentL10n } = require("devtools/client/shared/fluent-l10n/fluent-l10n");
// exports a singleton, which will be used across all application panel modules
exports.l10n = new FluentL10n();

View File

@ -3,6 +3,5 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'Panel.js',
'Target.js',
'l10n.js',
)

View File

@ -5,6 +5,7 @@
DIRS += [
'actions',
'components',
'modules',
'reducers',
]

View File

@ -4,22 +4,27 @@
"use strict";
const { UPDATE_WORKERS } = require("../constants");
const { UPDATE_CAN_DEBUG_WORKERS, UPDATE_WORKERS } = require("../constants");
function WorkersState() {
return {
// Array of all service workers
list: [],
canDebugWorkers: false,
};
}
function workersReducer(state = WorkersState(), action) {
switch (action.type) {
case UPDATE_CAN_DEBUG_WORKERS: {
return Object.assign({}, state, {
canDebugWorkers: action.canDebugWorkers,
});
}
case UPDATE_WORKERS: {
const { workers } = action;
return { list: workers };
return Object.assign({}, state, { list: workers });
}
default:
return state;
}

View File

@ -6,5 +6,5 @@
module.exports = {
// Extend from the shared list of defined globals for mochitests.
"extends": "../../../.eslintrc.mochitests.js"
"extends": "../../../../.eslintrc.mochitests.js"
};

View File

@ -4,14 +4,14 @@ subsuite = devtools
skip-if = serviceworker_e10s
support-files =
head.js
service-workers/debug-sw.js
service-workers/debug.html
service-workers/dynamic-registration.html
service-workers/empty.html
service-workers/empty-sw.js
service-workers/scope-page.html
service-workers/simple.html
service-workers/simple-unicode.html
resources/service-workers/debug-sw.js
resources/service-workers/debug.html
resources/service-workers/dynamic-registration.html
resources/service-workers/empty.html
resources/service-workers/empty-sw.js
resources/service-workers/scope-page.html
resources/service-workers/simple.html
resources/service-workers/simple-unicode.html
!/devtools/client/debugger/test/mochitest/helpers.js
!/devtools/client/debugger/test/mochitest/helpers/context.js
!/devtools/client/shared/test/frame-script-utils.js
@ -22,6 +22,9 @@ support-files =
[browser_application_panel_list-domain-workers.js]
[browser_application_panel_list-several-workers.js]
[browser_application_panel_list-single-worker.js]
[browser_application_panel_list-workers-empty.js]
[browser_application_panel_list-unicode.js]
[browser_application_panel_open-links.js]
skip-if = true # Bug 1467256
[browser_application_panel_unregister-worker.js]
[browser_application_panel_start-service-worker.js]

View File

@ -3,19 +3,19 @@
"use strict";
/* import-globals-from ../../debugger/test/mochitest/helpers.js */
/* import-globals-from ../../../debugger/test/mochitest/helpers.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/helpers.js",
this
);
/* import-globals-from ../../debugger/test/mochitest/helpers/context.js */
/* import-globals-from ../../../debugger/test/mochitest/helpers/context.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/helpers/context.js",
this
);
const TAB_URL = URL_ROOT + "service-workers/debug.html";
const TAB_URL = URL_ROOT + "resources/service-workers/debug.html";
add_task(async function() {
await enableApplicationPanel();

View File

@ -8,9 +8,9 @@
* current domain.
*/
const SIMPLE_URL = URL_ROOT + "service-workers/simple.html";
const SIMPLE_URL = URL_ROOT + "resources/service-workers/simple.html";
const OTHER_URL = SIMPLE_URL.replace("example.com", "test1.example.com");
const EMPTY_URL = (URL_ROOT + "service-workers/empty.html").replace(
const EMPTY_URL = (URL_ROOT + "resources/service-workers/empty.html").replace(
"example.com",
"test2.example.com"
);
@ -35,6 +35,7 @@ add_task(async function() {
);
await navigate(target, EMPTY_URL);
info("Wait until the service worker list is updated");
await waitUntil(() => doc.querySelector(".worker-list-empty") !== null);
ok(
true,

View File

@ -8,8 +8,8 @@
* same domain.
*/
const SIMPLE_URL = URL_ROOT + "service-workers/simple.html";
const OTHER_SCOPE_URL = URL_ROOT + "service-workers/scope-page.html";
const SIMPLE_URL = URL_ROOT + "resources/service-workers/simple.html";
const OTHER_SCOPE_URL = URL_ROOT + "resources/service-workers/scope-page.html";
add_task(async function() {
await enableApplicationPanel();

View File

@ -3,7 +3,8 @@
"use strict";
const TAB_URL = URL_ROOT + "service-workers/dynamic-registration.html";
const TAB_URL =
URL_ROOT + "resources/service-workers/dynamic-registration.html";
add_task(async function() {
await enableApplicationPanel();
@ -30,7 +31,7 @@ add_task(async function() {
const scopeEl = workerContainer.querySelector(".js-sw-scope");
const expectedScope =
"example.com/browser/devtools/client/application/test/" +
"service-workers/";
"browser/resources/service-workers/";
ok(
scopeEl.textContent.startsWith(expectedScope),
"Service worker has the expected scope"

View File

@ -3,10 +3,9 @@
"use strict";
const TAB_URL = (URL_ROOT + "service-workers/simple-unicode.html").replace(
"example.com",
"xn--hxajbheg2az3al.xn--jxalpdlp"
);
const TAB_URL = (
URL_ROOT + "resources/service-workers/simple-unicode.html"
).replace("example.com", "xn--hxajbheg2az3al.xn--jxalpdlp");
/**
* Check that the application panel displays filenames and URL's in human-readable,

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Check that the application panel only displays service workers from the
* current domain.
*/
const EMPTY_URL = URL_ROOT + "resources/service-workers/empty.html";
add_task(async function() {
await enableApplicationPanel();
const { panel, tab } = await openNewTabAndApplicationPanel(EMPTY_URL);
const doc = panel.panelWin.document;
await waitUntil(() => doc.querySelector(".js-worker-list-empty") !== null);
ok(true, "No service workers are shown for an empty page");
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

View File

@ -9,7 +9,7 @@ const { Toolbox } = require("devtools/client/framework/toolbox");
* Check that links work when the devtools are detached in a separate window.
*/
const TAB_URL = URL_ROOT + "service-workers/empty.html";
const TAB_URL = URL_ROOT + "resources/service-workers/empty.html";
add_task(async function() {
await enableApplicationPanel();

View File

@ -0,0 +1,68 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TAB_URL = URL_ROOT + "resources/service-workers/simple.html";
/**
* Tests that the Start button works for service workers who can be debugged
*/
add_task(async function() {
await enableApplicationPanel(); // this also enables SW debugging
// Setting a low idle_timeout and idle_extended_timeout will allow the service worker
// to reach the STOPPED state quickly, which will allow us to test the start button.
// The default value is 30000 milliseconds.
info("Set a low service worker idle timeout");
await pushPref("dom.serviceWorkers.idle_timeout", 1000);
await pushPref("dom.serviceWorkers.idle_extended_timeout", 1000);
const { panel, tab, target } = await openNewTabAndApplicationPanel(TAB_URL);
const doc = panel.panelWin.document;
await waitForWorkerRegistration(tab);
info("Wait until the service worker appears in the application panel");
await waitUntil(() => getWorkerContainers(doc).length === 1);
info("Wait until the start link is displayed and enabled");
const container = getWorkerContainers(doc)[0];
await waitUntil(() =>
container.querySelector(".js-start-link:not(.disabled-link)"));
info("Click the link and wait for the worker to start");
const link = container.querySelector(".js-start-link");
link.click();
await waitUntil(() =>
container.querySelector(".js-worker-status").textContent === "Running"
);
ok(true, "Worker status is 'Running'");
await unregisterAllWorkers(target.client);
});
/**
* Tests that Start button is disabled for service workers, when they cannot be debugged
*/
add_task(async function() {
await enableApplicationPanel();
// disable sw debugging by increasing the # of processes and thus multi-e10s kicking in
info("Disable service worker debugging");
await pushPref("dom.ipc.processCount", 8);
const { panel, tab, target } = await openNewTabAndApplicationPanel(TAB_URL);
const doc = panel.panelWin.document;
await waitForWorkerRegistration(tab);
info("Wait until the service worker appears in the application panel");
await waitUntil(() => getWorkerContainers(doc).length === 1);
info("Wait until the start link is displayed");
const container = getWorkerContainers(doc)[0];
await waitUntil(() => container.querySelector(".js-start-link"));
ok(container.querySelector(".js-start-link.disabled-link"),
"Start link is disabled");
await unregisterAllWorkers(target.client);
});

View File

@ -0,0 +1,33 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TAB_URL = URL_ROOT + "resources/service-workers/simple.html";
add_task(async function() {
await enableApplicationPanel();
const { panel, tab, target } = await openNewTabAndApplicationPanel(TAB_URL);
const doc = panel.panelWin.document;
info("Wait until the service worker appears in the application panel");
await waitUntil(() => getWorkerContainers(doc).length === 1);
const workerContainer = getWorkerContainers(doc)[0];
info("Wait until the unregister button is displayed for the service worker");
await waitUntil(() => workerContainer.querySelector(".js-unregister-button"));
info("Click the unregister button");
const button = workerContainer.querySelector(".js-unregister-button");
button.click();
info("Wait until the service worker is removed from the application panel");
await waitUntil(() => getWorkerContainers(doc).length === 0);
ok(true, "Service worker list is empty");
// just in case cleanup
await unregisterAllWorkers(target.client);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

View File

@ -3,7 +3,7 @@
/* eslint-env browser */
/* eslint no-unused-vars: [2, {"vars": "local"}] */
/* import-globals-from ../../shared/test/shared-head.js */
/* import-globals-from ../../../shared/test/shared-head.js */
"use strict";
@ -71,3 +71,11 @@ async function unregisterAllWorkers(client) {
await worker.registrationFront.unregister();
}
}
async function waitForWorkerRegistration(swTab) {
info("Wait until the registration appears on the window");
const swBrowser = swTab.linkedBrowser;
await asyncWaitUntil(async () => ContentTask.spawn(swBrowser, {}, function() {
return content.wrappedJSObject.getRegistration();
}));
}

Some files were not shown because too many files have changed in this diff Show More