Bug 1657105 - Allow listening for resources from both parent and content processes. r=jdescottes,nchevobbe

Differential Revision: https://phabricator.services.mozilla.com/D86046
This commit is contained in:
Alexandre Poirot 2020-08-12 16:49:23 +00:00
parent 9f9a8d9b3e
commit 3441a8b394
6 changed files with 135 additions and 124 deletions

View File

@ -4,6 +4,8 @@
"use strict";
const Targets = require("devtools/server/actors/targets/index");
const TYPES = {
CONSOLE_MESSAGE: "console-message",
CSS_CHANGE: "css-change",
@ -14,17 +16,16 @@ const TYPES = {
};
exports.TYPES = TYPES;
// Helper dictionary, which will contain all data specific to each resource type.
// Helper dictionaries, which will contain data specific to each resource type.
// - `path` is the absolute path to the module defining the Resource Watcher class,
// - `parentProcessResource` is an optional boolean to be set to true for resources to be
// watched from the parent process.
// Also see the following for loop which will add new attributes for each type:
// Also see the attributes added by `augmentResourceDictionary` for each type:
// - `watchers` is a weak map which will store Resource Watchers
// (i.e. devtools/server/actors/resources/ class instances)
// keyed by target actor -or- watcher actor.
// - `WatcherClass` is a shortcut to the Resource Watcher module.
// Each module exports a Resource Watcher class.
const Resources = {
// These lists are specific for the parent process and each target type.
const FrameTargetResources = augmentResourceDictionary({
[TYPES.CONSOLE_MESSAGE]: {
path: "devtools/server/actors/resources/console-messages",
},
@ -43,12 +44,56 @@ const Resources = {
[TYPES.PLATFORM_MESSAGE]: {
path: "devtools/server/actors/resources/platform-messages",
},
};
});
const ParentProcessResources = augmentResourceDictionary({});
for (const resource of Object.values(Resources)) {
resource.watchers = new WeakMap();
function augmentResourceDictionary(dict) {
for (const resource of Object.values(dict)) {
resource.watchers = new WeakMap();
loader.lazyRequireGetter(resource, "WatcherClass", resource.path);
loader.lazyRequireGetter(resource, "WatcherClass", resource.path);
}
return dict;
}
/**
* For a given actor, return the related dictionary defined just before,
* that contains info about how to listen for a given resource type, from a given actor.
*
* @param Actor watcherOrTargetActor
* Either a WatcherActor or a TargetActor which can be listening to a resource.
*/
function getResourceTypeDictionary(watcherOrTargetActor) {
const { typeName } = watcherOrTargetActor;
if (typeName == "watcher") {
return ParentProcessResources;
}
const { targetType } = watcherOrTargetActor;
switch (targetType) {
case "frame":
return FrameTargetResources;
default:
throw new Error(`Unsupported target actor typeName '${targetType}'`);
}
}
/**
* For a given actor, return the object stored in one of the previous dictionary
* that contains info about how to listen for a given resource type, from a given actor.
*
* @param Actor watcherOrTargetActor
* Either a WatcherActor or a TargetActor which can be listening to a resource.
* @param String resourceType
* The resource type to be observed.
*/
function getResourceTypeEntry(watcherOrTargetActor, resourceType) {
const dict = getResourceTypeDictionary(watcherOrTargetActor);
if (!(resourceType in dict)) {
throw new Error(
`Unsupported resource type '${resourceType}' for ${watcherOrTargetActor.typeName}`
);
}
return dict[resourceType];
}
/**
@ -68,12 +113,17 @@ for (const resource of Object.values(Resources)) {
* List of all type of resource to listen to.
*/
function watchResources(watcherOrTargetActor, resourceTypes) {
// If we are given a target actor, filter out the resource types supported by the target.
// When using sharedData to pass types between processes, we are passing them for all target types.
const { targetType } = watcherOrTargetActor;
if (targetType) {
resourceTypes = getResourceTypesForTargetType(resourceTypes, targetType);
}
for (const resourceType of resourceTypes) {
if (!(resourceType in Resources)) {
throw new Error(`Unsupported resource type '${resourceType}'`);
}
// Pull all info about this resource type from `Resources` global object
const { watchers, WatcherClass } = Resources[resourceType];
const { watchers, WatcherClass } = getResourceTypeEntry(
watcherOrTargetActor,
resourceType
);
// Ignore resources we're already listening to
if (watchers.has(watcherOrTargetActor)) {
@ -88,52 +138,31 @@ function watchResources(watcherOrTargetActor, resourceTypes) {
watchers.set(watcherOrTargetActor, watcher);
}
}
exports.watchResources = watchResources;
function getParentProcessResourceTypes(resourceTypes) {
return resourceTypes.filter(resourceType => {
if (!(resourceType in Resources)) {
throw new Error(`Unsupported resource type '${resourceType}'`);
}
return !!Resources[resourceType].parentProcessResource;
return resourceType in ParentProcessResources;
});
}
function getContentProcessResourceTypes(resourceTypes) {
return resourceTypes.filter(resourceType => {
if (!(resourceType in Resources)) {
throw new Error(`Unsupported resource type '${resourceType}'`);
}
return !Resources[resourceType].parentProcessResource;
exports.getParentProcessResourceTypes = getParentProcessResourceTypes;
function getResourceTypesForTargetType(resourceTypes, targetType) {
if (targetType == Targets.TYPES.FRAME) {
return resourceTypes.filter(resourceType => {
return resourceType in FrameTargetResources;
});
}
throw new Error(`Unsupported target type ${targetType}`);
}
exports.getResourceTypesForTargetType = getResourceTypesForTargetType;
function hasResourceTypesForTargets(resourceTypes) {
return resourceTypes.some(resourceType => {
return resourceType in FrameTargetResources;
});
}
/**
* See `watchResources` jsdoc.
*
* This one is to be called from the target process/thread.
* This is called by DevToolsFrameChild.
*/
function watchTargetResources(targetActor, resourceTypes) {
const contentProcessTypes = getContentProcessResourceTypes(resourceTypes);
watchResources(targetActor, contentProcessTypes);
}
exports.watchTargetResources = watchTargetResources;
/**
* See `watchResources` jsdoc.
* This one is to be called from the parent process.
* This is called by WatcherActor.
*
* @return Array<String>
* List of all resource types that aren't watched from the parent process.
*/
function watchParentProcessResources(watcherActor, resourceTypes) {
const parentProcessTypes = getParentProcessResourceTypes(resourceTypes);
watchResources(watcherActor, parentProcessTypes);
return resourceTypes.filter(
resource => !parentProcessTypes.includes(resource)
);
}
exports.watchParentProcessResources = watchParentProcessResources;
exports.hasResourceTypesForTargets = hasResourceTypesForTargets;
/**
* Stop watching for a list of resource types.
@ -145,11 +174,11 @@ exports.watchParentProcessResources = watchParentProcessResources;
*/
function unwatchResources(watcherOrTargetActor, resourceTypes) {
for (const resourceType of resourceTypes) {
if (!(resourceType in Resources)) {
throw new Error(`Unsupported resource type '${resourceType}'`);
}
// Pull all info about this resource type from `Resources` global object
const { watchers } = Resources[resourceType];
const { watchers } = getResourceTypeEntry(
watcherOrTargetActor,
resourceType
);
const watcher = watchers.get(watcherOrTargetActor);
if (watcher) {
@ -158,44 +187,18 @@ function unwatchResources(watcherOrTargetActor, resourceTypes) {
}
}
}
/**
* See `unwatchResources` jsdoc.
* This one is to be called from the target process/thread.
* This is called by DevToolsFrameChild.
*/
function unwatchTargetResources(targetActor, resourceTypes) {
const contentProcessTypes = getContentProcessResourceTypes(resourceTypes);
unwatchResources(targetActor, contentProcessTypes);
}
exports.unwatchTargetResources = unwatchTargetResources;
/**
* See `unwatchResources` jsdoc.
* This one is to be called from the parent process.
* This is called by WatcherActor.
*
* @return Array<String>
* List of all resource types that aren't watched from the parent process.
*/
function unwatchParentProcessResources(watcherActor, resourceTypes) {
const parentProcessTypes = getParentProcessResourceTypes(resourceTypes);
unwatchResources(watcherActor, parentProcessTypes);
return resourceTypes.filter(
resource => !parentProcessTypes.includes(resource)
);
}
exports.unwatchParentProcessResources = unwatchParentProcessResources;
exports.unwatchResources = unwatchResources;
/**
* Stop watching for all watched resources on a given actor.
*
* @param Actor watcherOrTargetActor
* The related actor, already passed to watchTargetResources or watchParentProcessResources.
* The related actor, already passed to watchResources.
*/
function unwatchAllTargetResources(watcherOrTargetActor) {
for (const { watchers } of Object.values(Resources)) {
for (const { watchers } of Object.values(
getResourceTypeDictionary(watcherOrTargetActor)
)) {
const watcher = watchers.get(watcherOrTargetActor);
if (watcher) {
watcher.destroy();
@ -220,11 +223,7 @@ exports.unwatchAllTargetResources = unwatchAllTargetResources;
* The resource watcher instance, defined in devtools/server/actors/resources/
*/
function getResourceWatcher(watcherOrTargetActor, resourceType) {
if (!(resourceType in Resources)) {
throw new Error(`Unsupported resource type '${resourceType}'`);
}
// Pull the watchers Map for this resource type from `Resources` global object
const { watchers } = Resources[resourceType];
const { watchers } = getResourceTypeEntry(watcherOrTargetActor, resourceType);
return watchers.get(watcherOrTargetActor);
}

View File

@ -315,11 +315,11 @@ const browsingContextTargetPrototype = {
* which is a JSM and doesn't have a reference to a DevTools Loader.
*/
watchTargetResources(resourceTypes) {
return Resources.watchTargetResources(this, resourceTypes);
return Resources.watchResources(this, resourceTypes);
},
unwatchTargetResources(resourceTypes) {
return Resources.unwatchTargetResources(this, resourceTypes);
return Resources.unwatchResources(this, resourceTypes);
},
/**

View File

@ -233,17 +233,19 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
async watchResources(resourceTypes) {
// First process resources which have to be listened from the parent process
// (the watcher actor always runs in the parent process)
const contentProcessResourceTypes = Resources.watchParentProcessResources(
Resources.watchResources(
this,
resourceTypes
Resources.getParentProcessResourceTypes(resourceTypes)
);
// Bail out early if all resources were watched from parent process
if (contentProcessResourceTypes.length == 0) {
// Bail out early if all resources were watched from parent process.
// In this scenario, we do not need to update these resource types in the WatcherRegistry
// as targets do not care about them.
if (!Resources.hasResourceTypesForTargets(resourceTypes)) {
return;
}
WatcherRegistry.watchResources(this, contentProcessResourceTypes);
WatcherRegistry.watchResources(this, resourceTypes);
// Fetch resources from all existing targets
for (const targetType in TARGET_HELPERS) {
@ -255,10 +257,14 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
) {
continue;
}
const targetResourceTypes = Resources.getResourceTypesForTargetType(
resourceTypes,
targetType
);
const targetHelperModule = TARGET_HELPERS[targetType];
await targetHelperModule.watchResources({
watcher: this,
resourceTypes: contentProcessResourceTypes,
resourceTypes: targetResourceTypes,
});
}
@ -281,11 +287,15 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
* We will eventually get rid of this code once all targets are properly supported by
* the Watcher Actor and we have target helpers for all of them.
*/
const frameResourceTypes = Resources.getResourceTypesForTargetType(
resourceTypes,
Targets.TYPES.FRAME
);
const targetActor = this.browserElement
? TargetActorRegistry.getTargetActor(this.browserId)
: TargetActorRegistry.getParentProcessTargetActor();
if (targetActor) {
await targetActor.watchTargetResources(resourceTypes);
await targetActor.watchTargetResources(frameResourceTypes);
}
},
@ -298,21 +308,21 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
unwatchResources(resourceTypes) {
// First process resources which are listened from the parent process
// (the watcher actor always runs in the parent process)
const contentProcessResourceTypes = Resources.unwatchParentProcessResources(
Resources.unwatchResources(
this,
resourceTypes
Resources.getParentProcessResourceTypes(resourceTypes)
);
// Bail out early if all resources were watched from parent process.
// These parent process resource types won't be saved in the WatcherRegistry because content processes
// do not need to know about them.
if (contentProcessResourceTypes.length == 0) {
// Bail out early if all resources were all watched from parent process.
// In this scenario, we do not need to update these resource types in the WatcherRegistry
// as targets do not care about them.
if (!Resources.hasResourceTypesForTargets(resourceTypes)) {
return;
}
const isWatchingResources = WatcherRegistry.unwatchResources(
this,
contentProcessResourceTypes
resourceTypes
);
if (!isWatchingResources) {
return;
@ -330,20 +340,28 @@ exports.WatcherActor = protocol.ActorClassWithSpec(watcherSpec, {
) {
continue;
}
const targetResourceTypes = Resources.getResourceTypesForTargetType(
resourceTypes,
targetType
);
const targetHelperModule = TARGET_HELPERS[targetType];
targetHelperModule.unwatchResources({
watcher: this,
resourceTypes: contentProcessResourceTypes,
resourceTypes: targetResourceTypes,
});
}
}
// See comment in watchResources.
const frameResourceTypes = Resources.getResourceTypesForTargetType(
resourceTypes,
Targets.TYPES.FRAME
);
const targetActor = this.browserElement
? TargetActorRegistry.getTargetActor(this.browserId)
: TargetActorRegistry.getParentProcessTargetActor();
if (targetActor) {
targetActor.unwatchTargetResources(contentProcessResourceTypes);
targetActor.unwatchTargetResources(frameResourceTypes);
}
// Unregister the JS Window Actor if there is no more DevTools code observing any target/resource

View File

@ -23,12 +23,10 @@ add_task(
};
// But both tabs and processes will be going through the ConsoleMessages module
// We force watching for console message first,
Resources.watchTargetResources(targetActor, [
Resources.TYPES.CONSOLE_MESSAGE,
]);
Resources.watchResources(targetActor, [Resources.TYPES.CONSOLE_MESSAGE]);
// And then listen for resource RDP event.
// Bug 1646677: But we should probably migrate this test to ResourceWatcher so that
// we don't have to hack the server side via Resource.watchTargetResources call.
// we don't have to hack the server side via Resource.watchResources call.
targetActor.on("resource-available-form", resources => {
if (resources[0].resourceType == Resources.TYPES.CONSOLE_MESSAGE) {
lastMessage = resources[0].message;

View File

@ -23,12 +23,10 @@ add_task(
};
// But both tabs and processes will be going through the ConsoleMessages module
// We force watching for console message first,
Resources.watchTargetResources(targetActor, [
Resources.TYPES.CONSOLE_MESSAGE,
]);
Resources.watchResources(targetActor, [Resources.TYPES.CONSOLE_MESSAGE]);
// And then listen for resource RDP event.
// Bug 1646677: But we should probably migrate this test to ResourceWatcher so that
// we don't have to hack the server side via Resource.watchTargetResources call.
// we don't have to hack the server side via Resource.watchResources call.
targetActor.on("resource-available-form", resources => {
if (resources[0].resourceType == Resources.TYPES.CONSOLE_MESSAGE) {
lastMessage = resources[0].message;

View File

@ -23,12 +23,10 @@ add_task(
};
// But both tabs and processes will be going through the ConsoleMessages module
// We force watching for console message first,
Resources.watchTargetResources(targetActor, [
Resources.TYPES.CONSOLE_MESSAGE,
]);
Resources.watchResources(targetActor, [Resources.TYPES.CONSOLE_MESSAGE]);
// And then listen for resource RDP event.
// Bug 1646677: But we should probably migrate this test to ResourceWatcher so that
// we don't have to hack the server side via Resource.watchTargetResources call.
// we don't have to hack the server side via Resource.watchResources call.
targetActor.on("resource-available-form", resources => {
if (resources[0].resourceType == Resources.TYPES.CONSOLE_MESSAGE) {
lastMessage = resources[0].message;