Bug 1621337 - Add support for service workers in target-list r=ochameau

Depends on D74701

Differential Revision: https://phabricator.services.mozilla.com/D74700
This commit is contained in:
Julian Descottes 2020-05-22 06:48:14 +00:00
Родитель 1465bfa0c4
Коммит 839278ac34
3 изменённых файлов: 244 добавлений и 10 удалений

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

@ -4,13 +4,212 @@
"use strict";
// eslint-disable-next-line mozilla/reject-some-requires
const { WorkersListener } = require("devtools/client/shared/workers-listener");
const {
LegacyWorkersWatcher,
} = require("devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher");
class LegacyServiceWorkersWatcher extends LegacyWorkersWatcher {
constructor(...args) {
super(...args);
this._registrations = [];
this._processTargets = new Set();
// We need to listen for registration changes at least in order to properly
// filter service workers by domain when debugging a local tab.
//
// A WorkerTarget instance has a url property, but it points to the url of
// the script, whereas the url property of the ServiceWorkerRegistration
// points to the URL controlled by the service worker.
//
// Historically we have been matching the service worker registration URL
// to match service workers for local tab tools (app panel & debugger).
// Maybe here we could have some more info on the actual worker.
this._workersListener = new WorkersListener(this.rootFront, {
registrationsOnly: true,
});
// Note that this is called much more often than when a registration
// is created or destroyed. WorkersListener notifies of anything that
// potentially impacted workers.
// I use it as a shortcut in this first patch. Listening to rootFront's
// "serviceWorkerRegistrationListChanged" should be enough to be notified
// about registrations. And if we need to also update the
// "debuggerServiceWorkerStatus" from here, then we would have to
// also listen to "registration-changed" one each registration.
this._onRegistrationListChanged = this._onRegistrationListChanged.bind(
this
);
// Flag used from the parent class to listen to process targets.
// Decision tree is complicated, keep all logic in the parent methods.
this._isServiceWorkerWatcher = true;
}
// Override from LegacyWorkersWatcher.
_supportWorkerTarget(workerTarget) {
return workerTarget.isServiceWorker;
if (!workerTarget.isServiceWorker) {
return false;
}
const swFronts = this._getAllServiceWorkerFronts();
return swFronts.some(({ id }) => id === workerTarget.id);
}
// Override from LegacyWorkersWatcher.
async listen() {
this._workersListener.addListener(this._onRegistrationListChanged);
// Fetch the registrations before calling listen, since service workers
// might already be available and will need to be compared with the existing
// registrations.
await this._onRegistrationListChanged();
await super.listen();
}
// Override from LegacyWorkersWatcher.
unlisten() {
this._workersListener.removeListener(this._onRegistrationListChanged);
super.unlisten();
}
// Override from LegacyWorkersWatcher.
async _onProcessAvailable({ targetFront }) {
if (this.target.isLocalTab) {
// XXX: This has been ported straight from the current debugger
// implementation. Since pauseMatchingServiceWorkers expects an origin
// to filter matching workers, it only makes sense when we are debugging
// a tab. However in theory, parent process debugging could pause all
// service workers without matching anything.
const origin = new URL(this.target.url).origin;
try {
// To support early breakpoint we need to setup the
// `pauseMatchingServiceWorkers` mechanism in each process.
await targetFront.pauseMatchingServiceWorkers({ origin });
} catch (e) {
if (targetFront.actorID) {
throw e;
} else {
console.warn(
"Process target destroyed while calling pauseMatchingServiceWorkers"
);
}
}
}
this._processTargets.add(targetFront);
return super._onProcessAvailable({ targetFront });
}
_onProcessDestroyed({ targetFront }) {
this._processTargets.delete(targetFront);
return super._onProcessDestroyed({ targetFront });
}
async _onRegistrationListChanged() {
const {
registrations,
} = await this.rootFront.listServiceWorkerRegistrations();
this._registrations = registrations.filter(r =>
this._isRegistrationValid(r)
);
// Everything after this point is not strictly necessary for sw support
// in the target list, but it makes the behavior closer to the previous
// listAllWorkers/WorkersListener pair.
const allServiceWorkerTargets = this._getAllServiceWorkerTargets();
const swFronts = this._getAllServiceWorkerFronts();
for (const target of allServiceWorkerTargets) {
const match = swFronts.find(({ id }) => id === target.id);
if (!match) {
// XXX: At this point the worker target is not really destroyed, but
// historically, listAllWorkers* APIs stopped returning worker targets
// if worker registrations are no longer available.
this.onTargetDestroyed(target);
this._removeTargetReferences(target);
}
}
}
// Retrieve all the ServiceWorkerFronts currently known.
_getAllServiceWorkerFronts() {
return (
this._registrations
// Flatten all ServiceWorkerRegistration fronts into list of
// ServiceWorker fronts. ServiceWorker fronts are just a description
// class and are not targets. They are not WorkerTarget fronts.
.reduce((p, registration) => {
return [
registration.evaluatingWorker,
registration.activeWorker,
registration.installingWorker,
registration.waitingWorker,
...p,
];
}, [])
// Filter out null workers, most registrations only have one worker
// set at a given time.
.filter(Boolean)
);
}
_getProcessTargets() {
return [...this._processTargets];
}
// Flatten all service worker targets in all processes.
_getAllServiceWorkerTargets() {
const allProcessTargets = this._getProcessTargets().filter(target =>
this.targetsByProcess.get(target)
);
const serviceWorkerTargets = [];
for (const target of allProcessTargets) {
serviceWorkerTargets.push(...this.targetsByProcess.get(target));
}
return serviceWorkerTargets;
}
// Delete the provided worker target from the internal targetsByProcess Maps.
_removeTargetReferences(target) {
const allProcessTargets = this._getProcessTargets().filter(t =>
this.targetsByProcess.get(t)
);
for (const processTarget of allProcessTargets) {
this.targetsByProcess.get(processTarget).delete(target);
}
}
// Check if the registration is relevant for the current target, ie
// corresponds to the same domain.
_isRegistrationValid(registration) {
if (this.target.isParentProcess) {
// All registrations are valid for main process debugging.
return true;
}
if (!this.target.isLocalTab) {
// No support for service worker targets outside of main process & local
// tab debugging.
return false;
}
// For local tabs, we match ServiceWorkerRegistrations and the target
// if they share the same hostname for their "url" properties.
const targetDomain = new URL(this.target.url).hostname;
try {
const registrationDomain = new URL(registration.url).hostname;
return registrationDomain === targetDomain;
} catch (e) {
// XXX: Some registrations have an empty URL.
return false;
}
}
}

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

@ -11,6 +11,10 @@ loader.lazyRequireGetter(
true
);
const {
LegacyProcessesWatcher,
} = require("devtools/shared/resources/legacy-target-watchers/legacy-processes-watcher");
class LegacyWorkersWatcher {
constructor(targetList, onTargetAvailable, onTargetDestroyed) {
this.targetList = targetList;
@ -122,9 +126,26 @@ class LegacyWorkersWatcher {
this._onProcessAvailable,
this._onProcessDestroyed
);
// The ParentProcessTarget front is considered to be a FRAME instead of a PROCESS.
// So process it manually here.
await this._onProcessAvailable({ targetFront: this.target });
} else if (this._isServiceWorkerWatcher) {
this._legacyProcessesWatcher = new LegacyProcessesWatcher(
this.targetList,
targetFront => {
// Service workers only live in content processes.
if (!targetFront.isParentProcess) {
this._onProcessAvailable({ targetFront });
}
},
targetFront => {
if (!targetFront.isParentProcess) {
this._onProcessDestroyed({ targetFront });
}
}
);
await this._legacyProcessesWatcher.listen();
} else {
this.targetsByProcess.set(this.target, new Set());
this._workerListChangedListener = this._workerListChanged.bind(
@ -136,21 +157,33 @@ class LegacyWorkersWatcher {
}
}
_getProcessTargets() {
return this.targetList.getAllTargets(TargetList.TYPES.PROCESS);
}
unlisten() {
// Stop listening for new process targets.
if (this.target.isParentProcess) {
for (const targetFront of this.targetList.getAllTargets(
TargetList.TYPES.PROCESS
)) {
const listener = this.targetsListeners.get(targetFront);
targetFront.off("workerListChanged", listener);
this.targetsByProcess.delete(targetFront);
this.targetsListeners.delete(targetFront);
}
this.targetList.unwatchTargets(
[TargetList.TYPES.PROCESS],
this._onProcessAvailable,
this._onProcessDestroyed
);
} else if (this._isServiceWorkerWatcher) {
this._legacyProcessesWatcher.unlisten();
}
// Cleanup the targetsByProcess/targetsListeners maps, and unsubscribe from
// all targetFronts. Process target fronts are either stored locally when
// watching service workers for the content toolbox, or can be retrieved via
// the TargetList API otherwise (see _getProcessTargets implementations).
if (this.target.isParentProcess || this._isServiceWorkerWatcher) {
for (const targetFront of this._getProcessTargets()) {
const listener = this.targetsListeners.get(targetFront);
targetFront.off("workerListChanged", listener);
this.targetsByProcess.delete(targetFront);
this.targetsListeners.delete(targetFront);
}
} else {
this.target.off("workerListChanged", this._workerListChangedListener);
delete this._workerListChangedListener;

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

@ -106,6 +106,7 @@ class TargetList {
// This allows listening for workers in the content toolbox outside of fission contexts
// For now, this is only toggled by tests.
this.listenForWorkers = false;
this.listenForServiceWorkers = false;
}
// Called whenever a new Target front is available.
@ -195,11 +196,12 @@ class TargetList {
types.push(TargetList.TYPES.SHARED_WORKER);
}
if (
this.listenForWorkers &&
this.listenForServiceWorkers &&
!types.includes(TargetList.TYPES.SERVICE_WORKER)
) {
types.push(TargetList.TYPES.SERVICE_WORKER);
}
// If no pref are set to true, nor is listenForWorkers set to true,
// we won't listen for any additional target. Only the top level target
// will be managed. We may still do target-switching.