content_script.sys.mjs (6506B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { ContentProcessWatcherRegistry } from "resource://devtools/server/connectors/js-process-actor/ContentProcessWatcherRegistry.sys.mjs"; 6 7 import { addDebuggerToGlobal } from "resource://gre/modules/jsdebugger.sys.mjs"; 8 // This will inject `Debugger` in the global scope 9 // eslint-disable-next-line mozilla/reject-globalThis-modification 10 addDebuggerToGlobal(globalThis); 11 12 const lazy = {}; 13 14 const EXTENSION_CONTENT_SYS_MJS = 15 "resource://gre/modules/ExtensionContent.sys.mjs"; 16 17 // Do not import Targets/index.js to prevent having to load DevTools module loader. 18 const CONTENT_SCRIPT = "content_script"; 19 20 ChromeUtils.defineESModuleGetters( 21 lazy, 22 { 23 // ExtensionContent.sys.mjs is a singleton and must be loaded through the 24 // main loader. Note that the user of lazy.ExtensionContent elsewhere in 25 // this file (at webextensionsContentScriptGlobals) looks up the module 26 // via Cu.isESModuleLoaded, which also uses the main loader as desired. 27 ExtensionContent: EXTENSION_CONTENT_SYS_MJS, 28 }, 29 { global: "shared" } 30 ); 31 32 let gDbg = null; 33 34 function watch() { 35 // Listen for new globals via Spidermonkey Debugger API 36 // in order to catch new Content Script sandboxes created later on 37 gDbg = new Debugger(); 38 gDbg.onNewGlobalObject = onNewGlobal; 39 40 // Also listen to this event emitted by ExtensionContent to know 41 // when some Content Script sandboxes are destroyed. 42 // It happens when a page navigates/reload and also on add-on disabling. 43 Services.obs.addObserver(observe, "content-script-destroyed"); 44 } 45 46 function unwatch() { 47 gDbg.onNewGlobalObject = undefined; 48 gDbg = null; 49 50 Services.obs.removeObserver(observe, "content-script-destroyed"); 51 } 52 53 /** 54 * Called whenever a new global is instantiated in the current process 55 * 56 * @param {Debugger.Object} global 57 */ 58 function onNewGlobal(global) { 59 // Content scripts are only using Sandboxes as global. 60 if (global.class != "Sandbox") { 61 return; 62 } 63 64 // WebExtension codebase will flag its sandboxes with a browser-id attribute 65 // in order to inform which tab they are injected against. 66 const contentScriptSandbox = global.unsafeDereference(); 67 const metadata = Cu.getSandboxMetadata(contentScriptSandbox); 68 if (!metadata) { 69 return; 70 } 71 const sandboxBrowserId = metadata["browser-id"]; 72 if (!sandboxBrowserId) { 73 return; 74 } 75 76 for (const watcherDataObject of ContentProcessWatcherRegistry.getAllWatchersDataObjects( 77 CONTENT_SCRIPT 78 )) { 79 const { sessionContext } = watcherDataObject; 80 const { browserId, type } = sessionContext; 81 // Accept all the content scripts if we are debugging everything (session context type == all) 82 if (type != "all" && browserId != sandboxBrowserId) { 83 continue; 84 } 85 createContentScriptTargetActor(watcherDataObject, { 86 sessionContext, 87 contentScriptSandbox, 88 }); 89 } 90 } 91 92 /** 93 * Listen to an Observer Service notification emitted by ExtensionContent 94 * when any content script sandbox is destroyed. 95 */ 96 function observe(sandbox, topic) { 97 if (topic != "content-script-destroyed") { 98 return; 99 } 100 101 for (const watcherDataObject of ContentProcessWatcherRegistry.getAllWatchersDataObjects( 102 CONTENT_SCRIPT 103 )) { 104 const targetActor = watcherDataObject.actors.find( 105 actor => actor.contentScriptSandbox === sandbox 106 ); 107 if (!targetActor) { 108 continue; 109 } 110 111 ContentProcessWatcherRegistry.destroyTargetActor( 112 watcherDataObject, 113 targetActor, 114 {} 115 ); 116 } 117 } 118 119 function createTargetsForWatcher(watcherDataObject, _isProcessActorStartup) { 120 // Ignore this process if there is no extension activity in it 121 if (!Cu.isESModuleLoaded(EXTENSION_CONTENT_SYS_MJS)) { 122 return; 123 } 124 125 const { sessionContext } = watcherDataObject; 126 // Only try spawning Content Script targets when debugging tabs or everything. 127 if ( 128 sessionContext.type != "browser-element" && 129 sessionContext.type != "all" 130 ) { 131 return; 132 } 133 const { browserId, type } = sessionContext; 134 135 const sandboxes = lazy.ExtensionContent.getAllContentScriptGlobals(); 136 for (const contentScriptSandbox of sandboxes) { 137 if (!contentScriptSandbox || Cu.isDeadWrapper(contentScriptSandbox)) { 138 // Bug 1979448: Skip invalid or dead sandboxes. 139 continue; 140 } 141 142 const metadata = Cu.getSandboxMetadata(contentScriptSandbox); 143 // Accept all the content scripts if we are debugging everything (session context type == all) 144 // 145 // Ignore sandboxes without metadata which are related to the hack 146 // of bug 1214658, which spawns content script sandboxes in order 147 // to expose chrome/browser API to iframes loading extension documents. 148 if (type != "all" && (!metadata || metadata["browser-id"] != browserId)) { 149 continue; 150 } 151 createContentScriptTargetActor(watcherDataObject, { 152 sessionContext, 153 contentScriptSandbox, 154 }); 155 } 156 } 157 158 function destroyTargetsForWatcher(watcherDataObject, options) { 159 // Unregister and destroy the existing target actors for this target type 160 const actorsToDestroy = watcherDataObject.actors.filter( 161 actor => actor.targetType == CONTENT_SCRIPT 162 ); 163 watcherDataObject.actors = watcherDataObject.actors.filter( 164 actor => actor.targetType != CONTENT_SCRIPT 165 ); 166 167 for (const actor of actorsToDestroy) { 168 ContentProcessWatcherRegistry.destroyTargetActor( 169 watcherDataObject, 170 actor, 171 options 172 ); 173 } 174 } 175 176 /** 177 * Instantiate a ContentScript target actor for a given content script sandbox 178 * and a given watcher actor. 179 * 180 * @param {object} watcherDataObject 181 * @param {Sandbox} sandbox 182 */ 183 function createContentScriptTargetActor(watcherDataObject, sandbox) { 184 const { connection, loader } = 185 ContentProcessWatcherRegistry.getOrCreateConnectionForWatcher( 186 watcherDataObject.watcherActorID, 187 false 188 ); 189 190 const { WebExtensionContentScriptTargetActor } = loader.require( 191 "devtools/server/actors/targets/content-script" 192 ); 193 194 // Create the actual target actor. 195 const targetActor = new WebExtensionContentScriptTargetActor( 196 connection, 197 sandbox 198 ); 199 200 ContentProcessWatcherRegistry.onNewTargetActor( 201 watcherDataObject, 202 targetActor, 203 false 204 ); 205 } 206 207 export const ContentScriptTargetWatcher = { 208 watch, 209 unwatch, 210 createTargetsForWatcher, 211 destroyTargetsForWatcher, 212 };