Bug 1459953 - Part 1: Show all service worker states r=jdescottes,fluent-reviewers,ochameau,flod

Differential Revision: https://phabricator.services.mozilla.com/D63864

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Belén Albeza 2020-03-17 11:26:35 +00:00
Родитель f03e90da15
Коммит 6ed6667ca2
21 изменённых файлов: 197 добавлений и 146 удалений

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

@ -132,6 +132,7 @@ class ServiceWorkerAdditionalActions extends PureComponent {
className: "default-button default-button--micro qa-unregister-button",
key: "service-worker-unregister-button",
labelId: "about-debugging-worker-action-unregister",
disabled: false,
onClick: this.unregister.bind(this),
});
}

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

@ -4,6 +4,8 @@
"use strict";
const { Ci } = require("chrome");
const {
DEBUG_TARGETS,
REQUEST_WORKERS_SUCCESS,
@ -29,7 +31,7 @@ const workerComponentDataMiddleware = store => next => action => {
};
function getServiceWorkerStatus(worker) {
const isActive = worker.active;
const isActive = worker.state === Ci.nsIServiceWorkerInfo.STATE_ACTIVATED;
const isRunning = !!worker.workerTargetFront;
if (isActive && isRunning) {
@ -37,6 +39,7 @@ function getServiceWorkerStatus(worker) {
} else if (isActive) {
return SERVICE_WORKER_STATUSES.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.

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

@ -99,12 +99,8 @@ window.Application = {
},
async updateWorkers() {
const { service } = await this.client.mainRoot.listAllWorkers();
// filter out workers that don't have an URL or a scope
// TODO: Bug 1595138 investigate why we lack those properties
const workers = service.filter(x => x.url && x.scope);
this.actions.updateWorkers(workers);
const registrationsWithWorkers = await this.client.mainRoot.listAllServiceWorkers();
this.actions.updateWorkers(registrationsWithWorkers);
},
updateDomain() {

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

@ -77,3 +77,7 @@
.worker__link-debug {
margin: 0 calc(var(--base-unit) * 2);
}
.worker__status {
text-transform: capitalize;
}

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

@ -30,6 +30,7 @@ const {
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
const { l10n } = require("devtools/client/application/src/modules/l10n");
const {
services,
@ -97,19 +98,19 @@ class Worker extends PureComponent {
}
isActive() {
return this.props.worker.active;
return this.props.worker.isActive;
}
getServiceWorkerStatus() {
getLocalizedStatus() {
if (this.isActive() && this.isRunning()) {
return "running";
return l10n.getString("serviceworker-worker-status-running");
} else if (this.isActive()) {
return "stopped";
return l10n.getString("serviceworker-worker-status-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";
// NOTE: this is already localized by the service worker front
// (strings are in debugger.properties)
return this.props.worker.stateText;
}
formatScope(scope) {
@ -171,7 +172,7 @@ class Worker extends PureComponent {
render() {
const { worker } = this.props;
const status = this.getServiceWorkerStatus();
const statusText = this.getLocalizedStatus();
const unregisterButton = this.isActive()
? Localized(
@ -235,10 +236,7 @@ class Worker extends PureComponent {
),
dd(
{},
Localized(
{ id: "serviceworker-worker-status-" + status },
span({ className: "js-worker-status" })
),
span({ className: "js-worker-status worker__status" }, statusText),
" ",
!this.isRunning() ? this.renderStartButton() : null
)

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

@ -4,6 +4,8 @@
"use strict";
const { Ci } = require("chrome");
const {
UPDATE_CAN_DEBUG_WORKERS,
UPDATE_WORKERS,
@ -17,6 +19,19 @@ function WorkersState() {
};
}
function buildWorkerDataFromFronts({ registration, workers }) {
return workers.map(worker => ({
id: worker.id,
isActive: worker.state === Ci.nsIServiceWorkerInfo.STATE_ACTIVATED,
scope: registration.scope,
lastUpdateTime: registration.lastUpdateTime, // only available for active worker
url: worker.url,
registrationFront: registration,
workerTargetFront: worker.workerTargetFront,
stateText: worker.stateText,
}));
}
function workersReducer(state = WorkersState(), action) {
switch (action.type) {
case UPDATE_CAN_DEBUG_WORKERS: {
@ -26,7 +41,9 @@ function workersReducer(state = WorkersState(), action) {
}
case UPDATE_WORKERS: {
const { workers } = action;
return Object.assign({}, state, { list: workers });
return Object.assign({}, state, {
list: workers.map(buildWorkerDataFromFronts).flat(),
});
}
default:
return state;

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

@ -7,10 +7,11 @@
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const worker = {
active: PropTypes.bool,
name: PropTypes.string.isRequired,
scope: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
isActive: PropTypes.bool.isRequired,
lastUpdateTime: PropTypes.number,
scope: PropTypes.string.isRequired,
stateText: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
// registrationFront can be missing in e10s.
registrationFront: PropTypes.object,

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

@ -61,5 +61,9 @@ add_task(async function() {
const workerScript = findSource(debuggerContext, "debug-sw.js");
await removeBreakpoint(debuggerContext, workerScript.id, 11);
await unregisterAllWorkers(target.client);
await unregisterAllWorkers(target.client, doc);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

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

@ -43,5 +43,9 @@ add_task(async function() {
"Start button is disabled"
);
await unregisterAllWorkers(target.client);
await unregisterAllWorkers(target.client, doc);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

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

@ -18,7 +18,9 @@ const EMPTY_URL = (URL_ROOT + "resources/service-workers/empty.html").replace(
add_task(async function() {
await enableApplicationPanel();
const { panel, toolbox } = await openNewTabAndApplicationPanel(SIMPLE_URL);
const { panel, toolbox, tab } = await openNewTabAndApplicationPanel(
SIMPLE_URL
);
const doc = panel.panelWin.document;
selectPage(panel, "service-workers");
@ -58,5 +60,9 @@ add_task(async function() {
"Second service worker registration is displayed for the correct domain"
);
await unregisterAllWorkers(toolbox.target.client);
await unregisterAllWorkers(toolbox.target.client, doc);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

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

@ -14,7 +14,9 @@ const OTHER_SCOPE_URL = URL_ROOT + "resources/service-workers/scope-page.html";
add_task(async function() {
await enableApplicationPanel();
const { panel, toolbox } = await openNewTabAndApplicationPanel(SIMPLE_URL);
const { panel, toolbox, tab } = await openNewTabAndApplicationPanel(
SIMPLE_URL
);
const doc = panel.panelWin.document;
selectPage(panel, "service-workers");
@ -44,5 +46,9 @@ add_task(async function() {
ok(true, "Second service worker registration is displayed");
await unregisterAllWorkers(toolbox.target.client);
await unregisterAllWorkers(toolbox.target.client, doc);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

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

@ -57,4 +57,8 @@ add_task(async function() {
info("Wait until the service worker is removed from the application panel");
await waitUntil(() => getWorkerContainers(doc).length === 0);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

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

@ -15,7 +15,7 @@ const TAB_URL = (
add_task(async function() {
await enableApplicationPanel();
const { panel, target } = await openNewTabAndApplicationPanel(TAB_URL);
const { panel, target, tab } = await openNewTabAndApplicationPanel(TAB_URL);
const doc = panel.panelWin.document;
selectPage(panel, "service-workers");
@ -39,5 +39,9 @@ add_task(async function() {
"Service worker has the expected Unicode url"
);
await unregisterAllWorkers(target.client);
await unregisterAllWorkers(target.client, doc);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

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

@ -37,6 +37,7 @@ add_task(async function() {
info("Select service worker page");
selectPage(panel, "service-workers");
await waitUntil(() => doc.querySelector(".js-service-workers-page") !== null);
await unregisterAllWorkers(target.client, doc);
info("Select manifest page in the sidebar");
const link = doc.querySelector(".js-sidebar-manifest");
@ -45,8 +46,6 @@ add_task(async function() {
await waitUntil(() => doc.querySelector(".js-manifest-page") !== null);
ok(true, "Manifest page was selected.");
await unregisterAllWorkers(target.client);
// close the tab
info("Closing the tab.");
await target.client.waitForRequestsToSettle();

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

@ -46,5 +46,9 @@ add_task(async function() {
});
ok(true, "Worker status is 'Running'");
await unregisterAllWorkers(target.client);
await unregisterAllWorkers(target.client, doc);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);
});

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

@ -37,7 +37,7 @@ add_task(async function() {
// close the tab
info("Closing the tab.");
await unregisterAllWorkers(toolbox.target.client);
await unregisterAllWorkers(toolbox.target.client, doc);
await BrowserTestUtils.removeTab(tab);
});

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

@ -28,7 +28,8 @@ add_task(async function() {
ok(true, "Service worker list is empty");
// just in case cleanup
await unregisterAllWorkers(target.client);
await unregisterAllWorkers(target.client, doc);
// close the tab
info("Closing the tab.");
await BrowserTestUtils.removeTab(tab);

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

@ -27,6 +27,8 @@ async function enableServiceWorkerDebugging() {
// Enable service workers in the debugger
await pushPref("devtools.debugger.features.windowless-service-workers", true);
// Disable randomly spawning processes during tests
await pushPref("dom.ipc.processPrelaunch.enabled", false);
// Wait for dom.ipc.processCount to be updated before releasing processes.
Services.ppmm.releaseCachedProcesses();
@ -64,7 +66,7 @@ async function openNewTabAndApplicationPanel(url) {
return { panel, tab, target, toolbox };
}
async function unregisterAllWorkers(client) {
async function unregisterAllWorkers(client, doc) {
info("Wait until all workers have a valid registrationFront");
let workers;
await asyncWaitUntil(async function() {
@ -79,6 +81,9 @@ async function unregisterAllWorkers(client) {
for (const worker of workers.service) {
await worker.registrationFront.unregister();
}
// wait for service workers to disappear from the UI
waitUntil(() => getWorkerContainers(doc).length === 0);
}
async function waitForWorkerRegistration(swTab) {

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

@ -3,6 +3,8 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { Ci } = require("chrome");
const {
updateCanDebugWorkers,
updateWorkers,
@ -33,13 +35,40 @@ add_task(async function() {
add_task(async function() {
info("Test workers reducer: UPDATE_WORKERS action");
const state = WorkersState();
const action = updateWorkers([{ foo: "bar" }, { lorem: "ipsum" }]);
const rawData = [
{
registration: {
scope: "lorem-ipsum",
lastUpdateTime: 42,
},
workers: [
{
id: 1,
state: Ci.nsIServiceWorkerInfo.STATE_ACTIVATED,
url: "https://example.com",
workerTargetFront: { foo: "bar" },
stateText: "activated",
},
],
},
];
const expectedData = [
{
id: 1,
isActive: true,
scope: "lorem-ipsum",
lastUpdateTime: 42,
url: "https://example.com",
registrationFront: rawData[0].registration,
workerTargetFront: rawData[0].workers[0].workerTargetFront,
stateText: "activated",
},
];
const action = updateWorkers(rawData);
const newState = workersReducer(state, action);
deepEqual(
newState.list,
[{ foo: "bar" }, { lorem: "ipsum" }],
"workers contains the expected list"
);
deepEqual(newState.list, expectedData, "workers contains the expected list");
});

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

@ -56,10 +56,6 @@ serviceworker-worker-status-running = Running
# Service Worker status. A stopped service worker is registered but not currently active.
serviceworker-worker-status-stopped = Stopped
# Service Worker status. A registering service worker is not yet registered and cannot be
# started or debugged.
serviceworker-worker-status-registering = Registering
# Text displayed when no service workers are visible for the current page. Clicking on the
# link will open https://developer-mozilla-org/docs/Web/API/Service_Worker_API/Using_Service_Workers
serviceworker-empty-intro = You need to register a Service Worker to inspect it here. <a>Learn more</a>

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

@ -60,6 +60,56 @@ class RootFront extends FrontClassWithSpec(rootSpec) {
this.applicationType = form.applicationType;
this.traits = form.traits;
}
/**
* Retrieve all service worker registrations with their corresponding workers.
* @param {Array} [workerTargets] (optional)
* Array containing the result of a call to `listAllWorkerTargets`.
* (this exists to avoid duplication of calls to that method)
* @return {Object[]} result - An Array of Objects with the following format
* - {result[].registration} - The registration front
* - {result[].workers} Array of form-like objects for service workers
*/
async listAllServiceWorkers(workerTargets) {
const result = [];
const { registrations } = await this.listServiceWorkerRegistrations();
const allWorkers = workerTargets
? workerTargets
: await this.listAllWorkerTargets();
for (const registrationFront of registrations) {
// TODO: add all workers to the result. See Bug 1612897
const latestWorker =
registrationFront.activeWorker ||
registrationFront.waitingWorker ||
registrationFront.installingWorker ||
registrationFront.evaluatingWorker;
if (latestWorker !== null) {
const latestWorkerFront = allWorkers.find(
workerFront => workerFront.id === latestWorker.id
);
if (latestWorkerFront) {
registrationFront.workerTargetFront = latestWorkerFront;
}
// TODO: return only the worker targets. See Bug 1620605
result.push({
registration: registrationFront,
workers: [
{
id: registrationFront.id,
name: latestWorker.url,
state: latestWorker.state,
stateText: latestWorker.stateText,
url: latestWorker.url,
workerTargetFront: latestWorkerFront,
},
],
});
}
}
return result;
}
/**
* Retrieve all service worker registrations as well as workers from the parent and
@ -77,116 +127,35 @@ class RootFront extends FrontClassWithSpec(rootSpec) {
* Array of WorkerTargetActor forms, containing other workers.
*/
async listAllWorkers() {
let registrations = [];
let workers = [];
try {
// List service worker registrations
({ registrations } = await this.listServiceWorkerRegistrations());
workers = await this.listAllWorkerTargets();
} catch (e) {
// Something went wrong, maybe our client is disconnected?
}
const allWorkers = await this.listAllWorkerTargets();
const serviceWorkers = await this.listAllServiceWorkers(allWorkers);
const result = {
service: [],
service: serviceWorkers
.map(({ registration, workers }) => {
return workers.map(worker => {
return Object.assign(worker, {
registrationFront: registration,
fetch: registration.fetch,
});
});
})
.flat(),
shared: [],
other: [],
};
registrations.forEach(front => {
const {
activeWorker,
waitingWorker,
installingWorker,
evaluatingWorker,
} = front;
const newestWorker =
activeWorker || waitingWorker || installingWorker || evaluatingWorker;
// All the information is simply mirrored from the registration front.
// However since registering workers will fetch similar information from the worker
// target front and will not have a service worker registration front, consumers
// should not read meta data directly on the registration front instance.
result.service.push({
active: front.active,
fetch: front.fetch,
id: front.id,
lastUpdateTime: front.lastUpdateTime,
name: front.url,
registrationFront: front,
scope: front.scope,
url: front.url,
newestWorkerId: newestWorker && newestWorker.id,
});
});
workers.forEach(front => {
allWorkers.forEach(front => {
const worker = {
id: front.id,
name: front.url,
url: front.url,
name: front.url,
workerTargetFront: front,
};
switch (front.type) {
case Ci.nsIWorkerDebugger.TYPE_SERVICE:
const registration = result.service.find(r => {
// If registrationFront is missing, it means this entry is actually
// a workerFront that has been augmented and pushed to
// result.service in an earlier iteration.
// This should no longer happen after Bug 1595964 is resolved.
if (!r.registrationFront) {
// We can safely return false here since `r` is not a full
// service worker registration, but merely a worker.
return false;
}
/**
* Older servers will not define `ServiceWorkerFront.id` (the value
* of `r.newestWorkerId`), and a `ServiceWorkerFront`'s ID will only
* match its corresponding WorkerTargetFront's ID if their
* underlying actors are "connected" - this is only guaranteed with
* parent-intercept mode. The `if` statement is for backward
* compatibility and can be removed when the release channel is
* >= FF69 _and_ parent-intercept is stable (which definitely won't
* happen when the release channel is < FF69).
*/
const { isParentInterceptEnabled } = r.registrationFront.traits;
if (!r.newestWorkerId || !isParentInterceptEnabled) {
return r.scope === front.scope;
}
return r.newestWorkerId === front.id;
});
if (registration) {
// Before bug 1595964, URLs were not available for registrations
// whose worker's main script is being evaluated. Now, URLs are
// always available, and this test deals with older servers.
// @backward-compatibility: remove in Firefox 75
if (!registration.url) {
registration.name = registration.url = front.url;
}
registration.workerTargetFront = front;
} else {
// If we are missing the registration, augment the worker front with
// fields expected on service worker registration fronts so that it
// can be displayed in UIs handling on service worker registrations.
// When does this happen:
// A - If parent intercept is disabled:
// If a service worker registration could not be found, this means we are in
// e10s, and registrations are not forwarded to other processes until they
// reach the activated state.
// B - If parent intercept is enabled:
// This can apparently happen when the registration is currently
// in evaluating state. Cleanup is tracked in Bug 1595964.
worker.fetch = front.fetch;
worker.scope = front.scope;
worker.active = false;
result.service.push(worker);
}
// do nothing, since we already fetched them in `serviceWorkers`
break;
case Ci.nsIWorkerDebugger.TYPE_SHARED:
result.shared.push(worker);