summaryrefslogtreecommitdiffstats
path: root/devtools/client/application/src/components/service-workers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /devtools/client/application/src/components/service-workers
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/application/src/components/service-workers')
-rw-r--r--devtools/client/application/src/components/service-workers/Registration.css73
-rw-r--r--devtools/client/application/src/components/service-workers/Registration.js153
-rw-r--r--devtools/client/application/src/components/service-workers/RegistrationList.css54
-rw-r--r--devtools/client/application/src/components/service-workers/RegistrationList.js92
-rw-r--r--devtools/client/application/src/components/service-workers/RegistrationListEmpty.js122
-rw-r--r--devtools/client/application/src/components/service-workers/Worker.css75
-rw-r--r--devtools/client/application/src/components/service-workers/Worker.js225
-rw-r--r--devtools/client/application/src/components/service-workers/WorkersPage.js71
-rw-r--r--devtools/client/application/src/components/service-workers/moz.build11
9 files changed, 876 insertions, 0 deletions
diff --git a/devtools/client/application/src/components/service-workers/Registration.css b/devtools/client/application/src/components/service-workers/Registration.css
new file mode 100644
index 0000000000..84b6de58e1
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/Registration.css
@@ -0,0 +1,73 @@
+/* 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/. */
+
+/*
+ * The current layout of a registration is
+ *
+ * +------+----------------------+----------------+
+ * | Header - scope + timestamp | Unregister_btn |
+ * +------+----------------------+----------------|
+ * | worker 1 |
+ | worker 2 |
+ | ... |
+ +----------------------------------------------+
+ | Unregister btn |
+ +----------------------------------------------+
+ */
+
+.registration {
+ line-height: 1.5;
+ font-size: var(--body-10-font-size);
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ grid-template-rows: minmax(calc(var(--base-unit) * 6), auto) 1fr auto;
+ grid-column-gap: calc(4 * var(--base-unit));
+ grid-row-gap: calc(2 * var(--base-unit));
+ grid-template-areas: "header header-controls"
+ "workers workers"
+ "footer-controls footer-controls";
+}
+
+/* vertical layout */
+@media(max-width: 700px) {
+ .registration__controls {
+ grid-area: footer-controls;
+ justify-self: end;
+ }
+}
+
+/* wide layout */
+@media(min-width: 701px) {
+ .registration__controls {
+ grid-area: header-controls;
+ }
+}
+
+.registration__header {
+ grid-area: header;
+}
+
+.registration__scope {
+ font-size: var(--title-10-font-size);
+ font-weight: var(--title-10-font-weight);
+ user-select: text;
+ margin: 0;
+
+ grid-area: scope;
+}
+
+.registration__updated-time {
+ color: var(--theme-text-color-alt);
+ grid-area: timestamp;
+}
+
+.registration__workers {
+ grid-area: workers;
+ list-style-type: none;
+ padding: 0;
+}
+
+.registration__workers-item:not(:first-child) {
+ margin-block-start: calc(var(--base-unit) * 2);
+}
diff --git a/devtools/client/application/src/components/service-workers/Registration.js b/devtools/client/application/src/components/service-workers/Registration.js
new file mode 100644
index 0000000000..97569f57e2
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/Registration.js
@@ -0,0 +1,153 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+
+const {
+ article,
+ aside,
+ h2,
+ header,
+ li,
+ p,
+ time,
+ ul,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+
+const {
+ getUnicodeUrl,
+} = require("resource://devtools/client/shared/unicode-url.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const Types = require("resource://devtools/client/application/src/types/index.js");
+
+const {
+ unregisterWorker,
+} = require("resource://devtools/client/application/src/actions/workers.js");
+
+const UIButton = createFactory(
+ require("resource://devtools/client/application/src/components/ui/UIButton.js")
+);
+
+const Worker = createFactory(
+ require("resource://devtools/client/application/src/components/service-workers/Worker.js")
+);
+
+/**
+ * This component is dedicated to display a service worker registration, along
+ * the list of attached workers to it.
+ * It displays information about the registration as well as an Unregister
+ * button.
+ */
+class Registration extends PureComponent {
+ static get propTypes() {
+ return {
+ className: PropTypes.string,
+ isDebugEnabled: PropTypes.bool.isRequired,
+ registration: PropTypes.shape(Types.registration).isRequired,
+ // this prop get automatically injected via `connect`
+ dispatch: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.unregister = this.unregister.bind(this);
+ }
+
+ unregister() {
+ this.props.dispatch(unregisterWorker(this.props.registration));
+ }
+
+ isActive() {
+ const { workers } = this.props.registration;
+ return workers.some(
+ x => x.state === Ci.nsIServiceWorkerInfo.STATE_ACTIVATED
+ );
+ }
+
+ formatScope(scope) {
+ const [, remainder] = getUnicodeUrl(scope).split("://");
+ // remove the last slash from the url, if present
+ // or return the full scope if there's no remainder
+ return remainder ? remainder.replace(/\/$/, "") : scope;
+ }
+
+ render() {
+ const { registration, isDebugEnabled, className } = this.props;
+
+ const unregisterButton = this.isActive()
+ ? Localized(
+ { id: "serviceworker-worker-unregister" },
+ UIButton({
+ onClick: this.unregister,
+ className: "js-unregister-button",
+ })
+ )
+ : null;
+
+ const lastUpdated = registration.lastUpdateTime
+ ? Localized(
+ {
+ id: "serviceworker-worker-updated",
+ // XXX: $date should normally be a Date object, but we pass the timestamp as a
+ // workaround. See Bug 1465718. registration.lastUpdateTime is in microseconds,
+ // convert to a valid timestamp in milliseconds by dividing by 1000.
+ $date: registration.lastUpdateTime / 1000,
+ time: time({ className: "js-sw-updated" }),
+ },
+ p({ className: "registration__updated-time" })
+ )
+ : null;
+
+ const scope = h2(
+ {
+ title: registration.scope,
+ className: "registration__scope js-sw-scope devtools-ellipsis-text",
+ },
+ this.formatScope(registration.scope)
+ );
+
+ return li(
+ { className: className ? className : "" },
+ article(
+ { className: "registration js-sw-container" },
+ header({ className: "registration__header" }, scope, lastUpdated),
+ aside({ className: "registration__controls" }, unregisterButton),
+ // render list of workers
+ ul(
+ { className: "registration__workers" },
+ registration.workers.map(worker => {
+ return li(
+ {
+ key: worker.id,
+ className: "registration__workers-item",
+ },
+ Worker({
+ worker,
+ isDebugEnabled,
+ })
+ );
+ })
+ )
+ )
+ );
+ }
+}
+
+const mapDispatchToProps = dispatch => ({ dispatch });
+module.exports = connect(mapDispatchToProps)(Registration);
diff --git a/devtools/client/application/src/components/service-workers/RegistrationList.css b/devtools/client/application/src/components/service-workers/RegistrationList.css
new file mode 100644
index 0000000000..021ed7e351
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/RegistrationList.css
@@ -0,0 +1,54 @@
+/* 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/. */
+
+.aboutdebugging-plug {
+ padding-block: calc(var(--base-unit) * 3);
+ border-block-start: 1px solid var(--separator-color);
+
+ /* display flex to handle showing the icon with ::before */
+ display: flex;
+ flex-direction: row;
+ column-gap: calc(var(--base-unit) * 2);
+ align-items: baseline;
+ font-size: var(--body-10-font-size);
+ font-weight: var(--body-10-font-weight);
+}
+
+.aboutdebugging-plug::before {
+ flex: 0 0 auto;
+ width: calc(var(--base-unit) * 4);
+ height: calc(var(--base-unit) * 4);
+ content: "";
+ -moz-context-properties: fill;
+ fill: currentColor;
+ background-image: url(chrome://global/skin/icons/developer.svg);
+ /* the icon size is taller than the line-height of the text. Since the
+ text can occupy multiple lines, and we want to keep the icon aligned
+ with respect to the first line, instead of align-items: center in
+ .aboutdebugging-plug, we use baseline, and fine tune the position here. */
+ position: relative;
+ top: 3px;
+}
+
+.registrations-container {
+ flex-grow: 1;
+}
+
+.registrations-container__list {
+ padding-inline-start: 0;
+}
+
+.registrations-container__item {
+ list-style-type: none;
+ margin: 0;
+ padding: calc(var(--base-unit) * 5) 0;
+}
+
+.registrations-container__item:first-child {
+ padding-top: 0;
+}
+
+.registrations-container__item:not(:last-child) {
+ border-bottom: 1px solid var(--separator-color);
+}
diff --git a/devtools/client/application/src/components/service-workers/RegistrationList.js b/devtools/client/application/src/components/service-workers/RegistrationList.js
new file mode 100644
index 0000000000..ed89c9cff3
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/RegistrationList.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ openTrustedLink,
+} = require("resource://devtools/client/shared/link.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ a,
+ article,
+ footer,
+ h1,
+ p,
+ ul,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const Types = require("resource://devtools/client/application/src/types/index.js");
+const Registration = createFactory(
+ require("resource://devtools/client/application/src/components/service-workers/Registration.js")
+);
+
+/**
+ * This component handles the list of service workers displayed in the application panel
+ * and also displays a suggestion to use about debugging for debugging other service
+ * workers.
+ */
+class RegistrationList extends PureComponent {
+ static get propTypes() {
+ return {
+ canDebugWorkers: PropTypes.bool.isRequired,
+ registrations: Types.registrationArray.isRequired,
+ };
+ }
+
+ render() {
+ const { canDebugWorkers, registrations } = this.props;
+
+ return [
+ article(
+ {
+ className: "registrations-container",
+ key: "registrations-container",
+ },
+ Localized(
+ { id: "serviceworker-list-header" },
+ h1({
+ className: "app-page__title",
+ })
+ ),
+ ul(
+ { className: "registrations-container__list" },
+ registrations.map(registration =>
+ Registration({
+ key: registration.id,
+ isDebugEnabled: canDebugWorkers,
+ registration,
+ className: "registrations-container__item",
+ })
+ )
+ )
+ ),
+
+ footer(
+ { className: "aboutdebugging-plug" },
+ Localized(
+ {
+ id: "serviceworker-list-aboutdebugging",
+ key: "serviceworkerlist-footer",
+ a: a({
+ className: "aboutdebugging-plug__link",
+ onClick: () => openTrustedLink("about:debugging#workers"),
+ }),
+ },
+ p({})
+ )
+ ),
+ ];
+ }
+}
+
+// Exports
+module.exports = RegistrationList;
diff --git a/devtools/client/application/src/components/service-workers/RegistrationListEmpty.js b/devtools/client/application/src/components/service-workers/RegistrationListEmpty.js
new file mode 100644
index 0000000000..2454f121e8
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/RegistrationListEmpty.js
@@ -0,0 +1,122 @@
+/* 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 {
+ openDocLink,
+ openTrustedLink,
+} = require("resource://devtools/client/shared/link.js");
+const {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const {
+ a,
+ article,
+ aside,
+ div,
+ h1,
+ img,
+ p,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+
+const {
+ services,
+} = require("resource://devtools/client/application/src/modules/application-services.js");
+
+const DOC_URL =
+ "https://developer.mozilla.org/docs/Web/API/Service_Worker_API/Using_Service_Workers" +
+ "?utm_source=devtools&utm_medium=sw-panel-blank";
+
+/**
+ * This component displays help information when no service workers are found for the
+ * current target.
+ */
+class RegistrationListEmpty extends PureComponent {
+ switchToConsole() {
+ services.selectTool("webconsole");
+ }
+
+ switchToDebugger() {
+ services.selectTool("jsdebugger");
+ }
+
+ openAboutDebugging() {
+ openTrustedLink("about:debugging#workers");
+ }
+
+ openDocumentation() {
+ openDocLink(DOC_URL);
+ }
+
+ render() {
+ return article(
+ { className: "app-page__icon-container js-registration-list-empty" },
+ aside(
+ {},
+ Localized(
+ {
+ id: "sidebar-item-service-workers",
+ attrs: {
+ alt: true,
+ },
+ },
+ img({
+ className: "app-page__icon",
+ src: "chrome://devtools/skin/images/debugging-workers.svg",
+ })
+ )
+ ),
+ div(
+ {},
+ Localized(
+ {
+ id: "serviceworker-empty-intro2",
+ },
+ h1({ className: "app-page__title" })
+ ),
+ Localized(
+ {
+ id: "serviceworker-empty-suggestions2",
+ a: a({
+ onClick: () => this.switchToConsole(),
+ }),
+ // NOTE: for <Localized> to parse the markup in the string, the
+ // markup needs to be actual HTML elements
+ span: a({
+ onClick: () => this.switchToDebugger(),
+ }),
+ },
+ p({})
+ ),
+ p(
+ {},
+ Localized(
+ { id: "serviceworker-empty-intro-link" },
+ a({
+ onClick: () => this.openDocumentation(),
+ })
+ )
+ ),
+ p(
+ {},
+ Localized(
+ { id: "serviceworker-empty-suggestions-aboutdebugging2" },
+ a({
+ className: "js-trusted-link",
+ onClick: () => this.openAboutDebugging(),
+ })
+ )
+ )
+ )
+ );
+ }
+}
+
+// Exports
+module.exports = RegistrationListEmpty;
diff --git a/devtools/client/application/src/components/service-workers/Worker.css b/devtools/client/application/src/components/service-workers/Worker.css
new file mode 100644
index 0000000000..e44b49ef6b
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/Worker.css
@@ -0,0 +1,75 @@
+/* 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/. */
+
+ /*
+ * The current layout of a service worker item is
+ *
+ * +------------+------------------------------+
+ * | Worker | script_name |
+ * | Icon |------------------------------|
+ * | | status start_button |
+ * +------------+------------------------------+
+ */
+
+.worker {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ grid-template-areas: "icon source"
+ "icon misc";
+ column-gap: calc(var(--base-unit) * 2);
+ row-gap: var(--base-unit);
+
+ line-height: calc(var(--base-unit) * 4);
+ font-size: var(--body-10-font-size);
+}
+
+.worker__icon {
+ grid-area: icon;
+}
+
+.worker__icon-image {
+ width: calc(var(--base-unit) * 4);
+ height: calc(var(--base-unit) * 4);
+}
+
+.worker__source {
+ grid-area: source;
+ user-select: text;
+}
+
+.worker__misc {
+ grid-area: misc;
+}
+
+.worker__status {
+ text-transform: capitalize;
+ --status-bg-color: transparent;
+ --status-border-color: transparent;
+}
+
+.worker__status::before {
+ content: "";
+ margin-inline-end: var(--base-unit);
+ width: calc(var(--base-unit) * 2);
+ height: calc(var(--base-unit) * 2);
+ display: inline-block;
+ background: var(--status-bg-color);
+ border: 1px solid var(--status-border-color);
+ border-radius: 100%;
+}
+
+.worker__status--active {
+ --status-bg-color: var(--green-60);
+ --status-border-color: var(--green-60);
+}
+
+.worker__status--waiting {
+ --status-bg-color: var(--theme-text-color-alt);
+ --status-border-color: var(--theme-text-color-alt);
+}
+
+.worker__status--installing, .worker__status--default {
+ --status-bg-color: transparent;
+ --status-border-color: var(--theme-text-color-alt);
+}
diff --git a/devtools/client/application/src/components/service-workers/Worker.js b/devtools/client/application/src/components/service-workers/Worker.js
new file mode 100644
index 0000000000..bc95e084a9
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/Worker.js
@@ -0,0 +1,225 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const {
+ a,
+ img,
+ p,
+ section,
+ span,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+
+const {
+ getUnicodeUrlPath,
+} = require("resource://devtools/client/shared/unicode-url.js");
+
+const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
+const Localized = createFactory(FluentReact.Localized);
+const {
+ l10n,
+} = require("resource://devtools/client/application/src/modules/l10n.js");
+
+const {
+ services,
+} = require("resource://devtools/client/application/src/modules/application-services.js");
+const Types = require("resource://devtools/client/application/src/types/index.js");
+
+const {
+ startWorker,
+} = require("resource://devtools/client/application/src/actions/workers.js");
+
+const UIButton = createFactory(
+ require("resource://devtools/client/application/src/components/ui/UIButton.js")
+);
+
+/**
+ * This component is dedicated to display a worker, more accurately a service worker, in
+ * the list of workers displayed in the application panel. It displays information about
+ * the worker as well as action links and buttons to interact with the worker (e.g. debug,
+ * unregister, update etc...).
+ */
+class Worker extends PureComponent {
+ static get propTypes() {
+ return {
+ isDebugEnabled: PropTypes.bool.isRequired,
+ worker: PropTypes.shape(Types.worker).isRequired,
+ // this prop get automatically injected via `connect`
+ dispatch: PropTypes.func.isRequired,
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.debug = this.debug.bind(this);
+ this.viewSource = this.viewSource.bind(this);
+ this.start = this.start.bind(this);
+ }
+
+ debug() {
+ if (!this.isRunning()) {
+ console.log("Service workers cannot be debugged if they are not running");
+ return;
+ }
+
+ services.openWorkerInDebugger(this.props.worker.workerDescriptorFront);
+ }
+
+ viewSource() {
+ if (!this.isRunning()) {
+ console.log(
+ "Service workers cannot be inspected if they are not running"
+ );
+ return;
+ }
+
+ services.viewWorkerSource(this.props.worker.workerDescriptorFront);
+ }
+
+ start() {
+ if (!this.isActive() || this.isRunning()) {
+ console.log("Running or inactive service workers cannot be started");
+ return;
+ }
+
+ this.props.dispatch(startWorker(this.props.worker));
+ }
+
+ isRunning() {
+ // We know the worker is running if it has a worker actor.
+ return !!this.props.worker.workerDescriptorFront;
+ }
+
+ isActive() {
+ return this.props.worker.state === Ci.nsIServiceWorkerInfo.STATE_ACTIVATED;
+ }
+
+ getLocalizedStatus() {
+ if (this.isActive() && this.isRunning()) {
+ return l10n.getString("serviceworker-worker-status-running");
+ } else if (this.isActive()) {
+ return l10n.getString("serviceworker-worker-status-stopped");
+ }
+ // NOTE: this is already localized by the service worker front
+ // (strings are in debugger.properties)
+ return this.props.worker.stateText;
+ }
+
+ getClassNameForStatus(baseClass) {
+ const { state } = this.props.worker;
+
+ switch (state) {
+ case Ci.nsIServiceWorkerInfo.STATE_PARSED:
+ case Ci.nsIServiceWorkerInfo.STATE_INSTALLING:
+ return "worker__status--installing";
+ case Ci.nsIServiceWorkerInfo.STATE_INSTALLED:
+ case Ci.nsIServiceWorkerInfo.STATE_ACTIVATING:
+ return "worker__status--waiting";
+ case Ci.nsIServiceWorkerInfo.STATE_ACTIVATED:
+ return "worker__status--active";
+ }
+
+ return "worker__status--default";
+ }
+
+ formatSource(source) {
+ const parts = source.split("/");
+ return getUnicodeUrlPath(parts[parts.length - 1]);
+ }
+
+ renderInspectLink(url) {
+ // avoid rendering the inspect link if sw is not running
+ const isDisabled = !this.isRunning();
+ // view source instead of debugging when debugging sw is not available
+ const callbackFn = this.props.isDebugEnabled ? this.debug : this.viewSource;
+
+ const sourceUrl = span(
+ { className: "js-source-url" },
+ this.formatSource(url)
+ );
+
+ return isDisabled
+ ? sourceUrl
+ : a(
+ {
+ onClick: callbackFn,
+ title: url,
+ href: "#",
+ className: "js-inspect-link",
+ },
+ sourceUrl,
+ "\u00A0", // &nbsp;
+ Localized(
+ {
+ id: "serviceworker-worker-inspect-icon",
+ attrs: {
+ alt: true,
+ },
+ },
+ img({
+ src: "chrome://devtools/skin/images/application-debug.svg",
+ })
+ )
+ );
+ }
+
+ renderStartButton() {
+ // avoid rendering the button at all for workers that are either running,
+ // or in a state that prevents them from starting (like waiting)
+ if (this.isRunning() || !this.isActive()) {
+ return null;
+ }
+
+ return Localized(
+ { id: "serviceworker-worker-start3" },
+ UIButton({
+ onClick: this.start,
+ className: `js-start-button`,
+ size: "micro",
+ })
+ );
+ }
+
+ render() {
+ const { worker } = this.props;
+ const statusText = this.getLocalizedStatus();
+ const statusClassName = this.getClassNameForStatus();
+
+ return section(
+ { className: "worker js-sw-worker" },
+ p(
+ { className: "worker__icon" },
+ img({
+ className: "worker__icon-image",
+ src: "chrome://devtools/skin/images/debugging-workers.svg",
+ })
+ ),
+ p({ className: "worker__source" }, this.renderInspectLink(worker.url)),
+ p(
+ { className: "worker__misc" },
+ span(
+ { className: `js-worker-status worker__status ${statusClassName}` },
+ statusText
+ ),
+ " ",
+ this.renderStartButton()
+ )
+ );
+ }
+}
+
+const mapDispatchToProps = dispatch => ({ dispatch });
+module.exports = connect(mapDispatchToProps)(Worker);
diff --git a/devtools/client/application/src/components/service-workers/WorkersPage.js b/devtools/client/application/src/components/service-workers/WorkersPage.js
new file mode 100644
index 0000000000..a44dd292f8
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/WorkersPage.js
@@ -0,0 +1,71 @@
+/* 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 {
+ createFactory,
+ PureComponent,
+} = require("resource://devtools/client/shared/vendor/react.js");
+const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
+const {
+ section,
+} = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
+const {
+ connect,
+} = require("resource://devtools/client/shared/vendor/react-redux.js");
+
+const Types = require("resource://devtools/client/application/src/types/index.js");
+const RegistrationList = createFactory(
+ require("resource://devtools/client/application/src/components/service-workers/RegistrationList.js")
+);
+const RegistrationListEmpty = createFactory(
+ require("resource://devtools/client/application/src/components/service-workers/RegistrationListEmpty.js")
+);
+
+class WorkersPage extends PureComponent {
+ static get propTypes() {
+ return {
+ // mapped from state
+ canDebugWorkers: PropTypes.bool.isRequired,
+ domain: PropTypes.string.isRequired,
+ registrations: Types.registrationArray.isRequired,
+ };
+ }
+
+ render() {
+ const { canDebugWorkers, domain, registrations } = this.props;
+
+ // Filter out workers from other domains
+ const domainWorkers = registrations.filter(
+ x => !!x.workers.length && new URL(x.workers[0].url).hostname === domain
+ );
+ const isListEmpty = domainWorkers.length === 0;
+
+ return section(
+ {
+ className: `app-page js-service-workers-page ${
+ isListEmpty ? "app-page--empty" : ""
+ }`,
+ },
+ isListEmpty
+ ? RegistrationListEmpty({})
+ : RegistrationList({
+ canDebugWorkers,
+ registrations: domainWorkers,
+ })
+ );
+ }
+}
+
+function mapStateToProps(state) {
+ return {
+ canDebugWorkers: state.workers.canDebugWorkers,
+ domain: state.page.domain,
+ registrations: state.workers.list,
+ };
+}
+
+// Exports
+module.exports = connect(mapStateToProps)(WorkersPage);
diff --git a/devtools/client/application/src/components/service-workers/moz.build b/devtools/client/application/src/components/service-workers/moz.build
new file mode 100644
index 0000000000..f9704b9df8
--- /dev/null
+++ b/devtools/client/application/src/components/service-workers/moz.build
@@ -0,0 +1,11 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ "Registration.js",
+ "RegistrationList.js",
+ "RegistrationListEmpty.js",
+ "Worker.js",
+ "WorkersPage.js",
+)