summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/resources/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/resources/index.js')
-rw-r--r--devtools/server/actors/resources/index.js471
1 files changed, 471 insertions, 0 deletions
diff --git a/devtools/server/actors/resources/index.js b/devtools/server/actors/resources/index.js
new file mode 100644
index 0000000000..e2857502ad
--- /dev/null
+++ b/devtools/server/actors/resources/index.js
@@ -0,0 +1,471 @@
+/* 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 Targets = require("resource://devtools/server/actors/targets/index.js");
+
+const TYPES = {
+ CONSOLE_MESSAGE: "console-message",
+ CSS_CHANGE: "css-change",
+ CSS_MESSAGE: "css-message",
+ CSS_REGISTERED_PROPERTIES: "css-registered-properties",
+ DOCUMENT_EVENT: "document-event",
+ ERROR_MESSAGE: "error-message",
+ LAST_PRIVATE_CONTEXT_EXIT: "last-private-context-exit",
+ NETWORK_EVENT: "network-event",
+ NETWORK_EVENT_STACKTRACE: "network-event-stacktrace",
+ PLATFORM_MESSAGE: "platform-message",
+ REFLOW: "reflow",
+ SERVER_SENT_EVENT: "server-sent-event",
+ SOURCE: "source",
+ STYLESHEET: "stylesheet",
+ THREAD_STATE: "thread-state",
+ JSTRACER_TRACE: "jstracer-trace",
+ JSTRACER_STATE: "jstracer-state",
+ WEBSOCKET: "websocket",
+
+ // storage types
+ CACHE_STORAGE: "Cache",
+ COOKIE: "cookies",
+ EXTENSION_STORAGE: "extension-storage",
+ INDEXED_DB: "indexed-db",
+ LOCAL_STORAGE: "local-storage",
+ SESSION_STORAGE: "session-storage",
+
+ // root types
+ EXTENSIONS_BGSCRIPT_STATUS: "extensions-backgroundscript-status",
+};
+exports.TYPES = TYPES;
+
+// Helper dictionaries, which will contain data specific to each resource type.
+// - `path` is the absolute path to the module defining the Resource Watcher class.
+//
+// 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.
+//
+// These are several dictionaries, which depend how the resource watcher classes are instantiated.
+
+// Frame target resources are spawned via a BrowsingContext Target Actor.
+// Their watcher class receives a target actor as first argument.
+// They are instantiated for each observed BrowsingContext, from the content process where it runs.
+// They are meant to observe all resources related to a given Browsing Context.
+const FrameTargetResources = augmentResourceDictionary({
+ [TYPES.CACHE_STORAGE]: {
+ path: "devtools/server/actors/resources/storage-cache",
+ },
+ [TYPES.CONSOLE_MESSAGE]: {
+ path: "devtools/server/actors/resources/console-messages",
+ },
+ [TYPES.CSS_CHANGE]: {
+ path: "devtools/server/actors/resources/css-changes",
+ },
+ [TYPES.CSS_MESSAGE]: {
+ path: "devtools/server/actors/resources/css-messages",
+ },
+ [TYPES.CSS_REGISTERED_PROPERTIES]: {
+ path: "devtools/server/actors/resources/css-registered-properties",
+ },
+ [TYPES.DOCUMENT_EVENT]: {
+ path: "devtools/server/actors/resources/document-event",
+ },
+ [TYPES.ERROR_MESSAGE]: {
+ path: "devtools/server/actors/resources/error-messages",
+ },
+ [TYPES.JSTRACER_STATE]: {
+ path: "devtools/server/actors/resources/jstracer-state",
+ },
+ [TYPES.JSTRACER_TRACE]: {
+ path: "devtools/server/actors/resources/jstracer-trace",
+ },
+ [TYPES.LOCAL_STORAGE]: {
+ path: "devtools/server/actors/resources/storage-local-storage",
+ },
+ [TYPES.PLATFORM_MESSAGE]: {
+ path: "devtools/server/actors/resources/platform-messages",
+ },
+ [TYPES.SESSION_STORAGE]: {
+ path: "devtools/server/actors/resources/storage-session-storage",
+ },
+ [TYPES.STYLESHEET]: {
+ path: "devtools/server/actors/resources/stylesheets",
+ },
+ [TYPES.NETWORK_EVENT]: {
+ path: "devtools/server/actors/resources/network-events-content",
+ },
+ [TYPES.NETWORK_EVENT_STACKTRACE]: {
+ path: "devtools/server/actors/resources/network-events-stacktraces",
+ },
+ [TYPES.REFLOW]: {
+ path: "devtools/server/actors/resources/reflow",
+ },
+ [TYPES.SOURCE]: {
+ path: "devtools/server/actors/resources/sources",
+ },
+ [TYPES.THREAD_STATE]: {
+ path: "devtools/server/actors/resources/thread-states",
+ },
+ [TYPES.SERVER_SENT_EVENT]: {
+ path: "devtools/server/actors/resources/server-sent-events",
+ },
+ [TYPES.WEBSOCKET]: {
+ path: "devtools/server/actors/resources/websockets",
+ },
+});
+
+// Process target resources are spawned via a Process Target Actor.
+// Their watcher class receives a process target actor as first argument.
+// They are instantiated for each observed Process (parent and all content processes).
+// They are meant to observe all resources related to a given process.
+const ProcessTargetResources = augmentResourceDictionary({
+ [TYPES.CONSOLE_MESSAGE]: {
+ path: "devtools/server/actors/resources/console-messages",
+ },
+ [TYPES.JSTRACER_TRACE]: {
+ path: "devtools/server/actors/resources/jstracer-trace",
+ },
+ [TYPES.JSTRACER_STATE]: {
+ path: "devtools/server/actors/resources/jstracer-state",
+ },
+ [TYPES.ERROR_MESSAGE]: {
+ path: "devtools/server/actors/resources/error-messages",
+ },
+ [TYPES.PLATFORM_MESSAGE]: {
+ path: "devtools/server/actors/resources/platform-messages",
+ },
+ [TYPES.SOURCE]: {
+ path: "devtools/server/actors/resources/sources",
+ },
+ [TYPES.THREAD_STATE]: {
+ path: "devtools/server/actors/resources/thread-states",
+ },
+});
+
+// Worker target resources are spawned via a Worker Target Actor.
+// Their watcher class receives a worker target actor as first argument.
+// They are instantiated for each observed worker, from the worker thread.
+// They are meant to observe all resources related to a given worker.
+//
+// We'll only support a few resource types in Workers (console-message, source,
+// thread state, …) as error and platform messages are not supported since we need access
+// to Ci, which isn't available in worker context.
+// Errors are emitted from the content process main thread so the user would still get them.
+const WorkerTargetResources = augmentResourceDictionary({
+ [TYPES.CONSOLE_MESSAGE]: {
+ path: "devtools/server/actors/resources/console-messages",
+ },
+ [TYPES.JSTRACER_TRACE]: {
+ path: "devtools/server/actors/resources/jstracer-trace",
+ },
+ [TYPES.JSTRACER_STATE]: {
+ path: "devtools/server/actors/resources/jstracer-state",
+ },
+ [TYPES.SOURCE]: {
+ path: "devtools/server/actors/resources/sources",
+ },
+ [TYPES.THREAD_STATE]: {
+ path: "devtools/server/actors/resources/thread-states",
+ },
+});
+
+// Parent process resources are spawned via the Watcher Actor.
+// Their watcher class receives the watcher actor as first argument.
+// They are instantiated once per watcher from the parent process.
+// They are meant to observe all resources related to a given context designated by the Watcher (and its sessionContext)
+// they should be observed from the parent process.
+const ParentProcessResources = augmentResourceDictionary({
+ [TYPES.NETWORK_EVENT]: {
+ path: "devtools/server/actors/resources/network-events",
+ },
+ [TYPES.COOKIE]: {
+ path: "devtools/server/actors/resources/storage-cookie",
+ },
+ [TYPES.EXTENSION_STORAGE]: {
+ path: "devtools/server/actors/resources/storage-extension",
+ },
+ [TYPES.INDEXED_DB]: {
+ path: "devtools/server/actors/resources/storage-indexed-db",
+ },
+ [TYPES.DOCUMENT_EVENT]: {
+ path: "devtools/server/actors/resources/parent-process-document-event",
+ },
+ [TYPES.LAST_PRIVATE_CONTEXT_EXIT]: {
+ path: "devtools/server/actors/resources/last-private-context-exit",
+ },
+});
+
+// Root resources are spawned via the Root Actor.
+// Their watcher class receives the root actor as first argument.
+// They are instantiated only once from the parent process.
+// They are meant to observe anything easily observable from the parent process
+// that isn't related to any particular context/target.
+// This is especially useful when you need to observe something without having to instantiate a Watcher actor.
+const RootResources = augmentResourceDictionary({
+ [TYPES.EXTENSIONS_BGSCRIPT_STATUS]: {
+ path: "devtools/server/actors/resources/extensions-backgroundscript-status",
+ },
+});
+exports.RootResources = RootResources;
+
+function augmentResourceDictionary(dict) {
+ for (const resource of Object.values(dict)) {
+ resource.watchers = new WeakMap();
+
+ 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 rootOrWatcherOrTargetActor
+ * Either a RootActor or WatcherActor or a TargetActor which can be listening to a resource.
+ */
+function getResourceTypeDictionary(rootOrWatcherOrTargetActor) {
+ const { typeName } = rootOrWatcherOrTargetActor;
+ if (typeName == "root") {
+ return RootResources;
+ }
+ if (typeName == "watcher") {
+ return ParentProcessResources;
+ }
+ const { targetType } = rootOrWatcherOrTargetActor;
+ return getResourceTypeDictionaryForTargetType(targetType);
+}
+
+/**
+ * For a targetType, return the related dictionary.
+ *
+ * @param String targetType
+ * A targetType string (See Targets.TYPES)
+ */
+function getResourceTypeDictionaryForTargetType(targetType) {
+ switch (targetType) {
+ case Targets.TYPES.FRAME:
+ return FrameTargetResources;
+ case Targets.TYPES.PROCESS:
+ return ProcessTargetResources;
+ case Targets.TYPES.WORKER:
+ return WorkerTargetResources;
+ case Targets.TYPES.SERVICE_WORKER:
+ return WorkerTargetResources;
+ 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 rootOrWatcherOrTargetActor
+ * Either a RootActor or WatcherActor or a TargetActor which can be listening to a resource.
+ * @param String resourceType
+ * The resource type to be observed.
+ */
+function getResourceTypeEntry(rootOrWatcherOrTargetActor, resourceType) {
+ const dict = getResourceTypeDictionary(rootOrWatcherOrTargetActor);
+ if (!(resourceType in dict)) {
+ throw new Error(
+ `Unsupported resource type '${resourceType}' for ${rootOrWatcherOrTargetActor.typeName}`
+ );
+ }
+ return dict[resourceType];
+}
+
+/**
+ * Start watching for a new list of resource types.
+ * This will also emit all already existing resources before resolving.
+ *
+ * @param Actor rootOrWatcherOrTargetActor
+ * Either a RootActor or WatcherActor or a TargetActor which can be listening to a resource:
+ * * RootActor will be used for resources observed from the parent process and aren't related to any particular
+ * context/descriptor. They can be observed right away when connecting to the RDP server
+ * without instantiating any actor other than the root actor.
+ * * WatcherActor will be used for resources listened from the parent process.
+ * * TargetActor will be used for resources listened from the content process.
+ * This actor:
+ * - defines what context to observe (browsing context, process, worker, ...)
+ * Via browsingContextID, windows, docShells attributes for the target actor.
+ * Via the `sessionContext` object for the watcher actor.
+ * (only for Watcher and Target actors. Root actor is context-less.)
+ * - exposes `notifyResources` method to be notified about all the resources updates
+ * This method will receive two arguments:
+ * - {String} updateType, which can be "available", "updated", or "destroyed"
+ * - {Array<Object>} resources, which will be the list of resource's forms
+ * or special update object for "updated" scenario.
+ * @param Array<String> resourceTypes
+ * List of all type of resource to listen to.
+ */
+async function watchResources(rootOrWatcherOrTargetActor, 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 } = rootOrWatcherOrTargetActor;
+ // Only target actors usecase will have a target type.
+ // For Root and Watcher we process the `resourceTypes` list unfiltered.
+ if (targetType) {
+ resourceTypes = getResourceTypesForTargetType(resourceTypes, targetType);
+ }
+ const promises = [];
+ for (const resourceType of resourceTypes) {
+ const { watchers, WatcherClass } = getResourceTypeEntry(
+ rootOrWatcherOrTargetActor,
+ resourceType
+ );
+
+ // Ignore resources we're already listening to
+ if (watchers.has(rootOrWatcherOrTargetActor)) {
+ continue;
+ }
+
+ // Don't watch for console messages from the worker target if worker messages are still
+ // being cloned to the main process, otherwise we'll get duplicated messages in the
+ // console output (See Bug 1778852).
+ if (
+ resourceType == TYPES.CONSOLE_MESSAGE &&
+ rootOrWatcherOrTargetActor.workerConsoleApiMessagesDispatchedToMainThread
+ ) {
+ continue;
+ }
+
+ const watcher = new WatcherClass();
+ promises.push(
+ watcher.watch(rootOrWatcherOrTargetActor, {
+ onAvailable: rootOrWatcherOrTargetActor.notifyResources.bind(
+ rootOrWatcherOrTargetActor,
+ "available"
+ ),
+ onUpdated: rootOrWatcherOrTargetActor.notifyResources.bind(
+ rootOrWatcherOrTargetActor,
+ "updated"
+ ),
+ onDestroyed: rootOrWatcherOrTargetActor.notifyResources.bind(
+ rootOrWatcherOrTargetActor,
+ "destroyed"
+ ),
+ })
+ );
+ watchers.set(rootOrWatcherOrTargetActor, watcher);
+ }
+ await Promise.all(promises);
+}
+exports.watchResources = watchResources;
+
+function getParentProcessResourceTypes(resourceTypes) {
+ return resourceTypes.filter(resourceType => {
+ return resourceType in ParentProcessResources;
+ });
+}
+exports.getParentProcessResourceTypes = getParentProcessResourceTypes;
+
+function getResourceTypesForTargetType(resourceTypes, targetType) {
+ const resourceDictionnary =
+ getResourceTypeDictionaryForTargetType(targetType);
+ return resourceTypes.filter(resourceType => {
+ return resourceType in resourceDictionnary;
+ });
+}
+exports.getResourceTypesForTargetType = getResourceTypesForTargetType;
+
+function hasResourceTypesForTargets(resourceTypes) {
+ return resourceTypes.some(resourceType => {
+ return resourceType in FrameTargetResources;
+ });
+}
+exports.hasResourceTypesForTargets = hasResourceTypesForTargets;
+
+/**
+ * Stop watching for a list of resource types.
+ *
+ * @param Actor rootOrWatcherOrTargetActor
+ * The related actor, already passed to watchResources.
+ * @param Array<String> resourceTypes
+ * List of all type of resource to stop listening to.
+ */
+function unwatchResources(rootOrWatcherOrTargetActor, resourceTypes) {
+ for (const resourceType of resourceTypes) {
+ // Pull all info about this resource type from `Resources` global object
+ const { watchers } = getResourceTypeEntry(
+ rootOrWatcherOrTargetActor,
+ resourceType
+ );
+
+ const watcher = watchers.get(rootOrWatcherOrTargetActor);
+ if (watcher) {
+ watcher.destroy();
+ watchers.delete(rootOrWatcherOrTargetActor);
+ }
+ }
+}
+exports.unwatchResources = unwatchResources;
+
+/**
+ * Clear resources for a list of resource types.
+ *
+ * @param Actor rootOrWatcherOrTargetActor
+ * The related actor, already passed to watchResources.
+ * @param Array<String> resourceTypes
+ * List of all type of resource to clear.
+ */
+function clearResources(rootOrWatcherOrTargetActor, resourceTypes) {
+ for (const resourceType of resourceTypes) {
+ const { watchers } = getResourceTypeEntry(
+ rootOrWatcherOrTargetActor,
+ resourceType
+ );
+
+ const watcher = watchers.get(rootOrWatcherOrTargetActor);
+ if (watcher && typeof watcher.clear == "function") {
+ watcher.clear();
+ }
+ }
+}
+
+exports.clearResources = clearResources;
+
+/**
+ * Stop watching for all watched resources on a given actor.
+ *
+ * @param Actor rootOrWatcherOrTargetActor
+ * The related actor, already passed to watchResources.
+ */
+function unwatchAllResources(rootOrWatcherOrTargetActor) {
+ for (const { watchers } of Object.values(
+ getResourceTypeDictionary(rootOrWatcherOrTargetActor)
+ )) {
+ const watcher = watchers.get(rootOrWatcherOrTargetActor);
+ if (watcher) {
+ watcher.destroy();
+ watchers.delete(rootOrWatcherOrTargetActor);
+ }
+ }
+}
+exports.unwatchAllResources = unwatchAllResources;
+
+/**
+ * If we are watching for the given resource type,
+ * return the current ResourceWatcher instance used by this target actor
+ * in order to observe this resource type.
+ *
+ * @param Actor watcherOrTargetActor
+ * Either a WatcherActor or a TargetActor which can be listening to a resource.
+ * WatcherActor will be used for resources listened from the parent process,
+ * and TargetActor will be used for resources listened from the content process.
+ * @param String resourceType
+ * The resource type to query
+ * @return ResourceWatcher
+ * The resource watcher instance, defined in devtools/server/actors/resources/
+ */
+function getResourceWatcher(watcherOrTargetActor, resourceType) {
+ const { watchers } = getResourceTypeEntry(watcherOrTargetActor, resourceType);
+
+ return watchers.get(watcherOrTargetActor);
+}
+exports.getResourceWatcher = getResourceWatcher;