process.js (7985B)
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 "use strict"; 6 7 /* 8 * Represents any process running in Firefox. 9 * This can be: 10 * - the parent process, where all top level chrome window runs: 11 * like browser.xhtml, sidebars, devtools iframes, the browser console, ... 12 * - any content process 13 * 14 * There is some special cases in the class around: 15 * - xpcshell, where there is only one process which doesn't expose any DOM document 16 * And instead of exposing a ParentProcessTargetActor, getTarget will return 17 * a ContentProcessTargetActor. 18 * - background task, similarly to xpcshell, they don't expose any DOM document 19 * and this also works with a ContentProcessTargetActor. 20 * 21 * See devtools/docs/backend/actor-hierarchy.md for more details. 22 */ 23 24 const { Actor } = require("resource://devtools/shared/protocol.js"); 25 const { 26 processDescriptorSpec, 27 } = require("resource://devtools/shared/specs/descriptors/process.js"); 28 29 const { 30 DevToolsServer, 31 } = require("resource://devtools/server/devtools-server.js"); 32 33 const { 34 createBrowserSessionContext, 35 createContentProcessSessionContext, 36 } = require("resource://devtools/server/actors/watcher/session-context.js"); 37 38 loader.lazyRequireGetter( 39 this, 40 "ContentProcessTargetActor", 41 "resource://devtools/server/actors/targets/content-process.js", 42 true 43 ); 44 loader.lazyRequireGetter( 45 this, 46 "ParentProcessTargetActor", 47 "resource://devtools/server/actors/targets/parent-process.js", 48 true 49 ); 50 loader.lazyRequireGetter( 51 this, 52 "connectToContentProcess", 53 "resource://devtools/server/connectors/content-process-connector.js", 54 true 55 ); 56 loader.lazyRequireGetter( 57 this, 58 "WatcherActor", 59 "resource://devtools/server/actors/watcher.js", 60 true 61 ); 62 63 class ProcessDescriptorActor extends Actor { 64 constructor(connection, options = {}) { 65 super(connection, processDescriptorSpec); 66 67 if ("id" in options && typeof options.id != "number") { 68 throw Error("process connect requires a valid `id` attribute."); 69 } 70 71 this.id = options.id; 72 this._windowGlobalTargetActor = null; 73 this.isParent = options.parent; 74 this.destroy = this.destroy.bind(this); 75 } 76 77 get browsingContextID() { 78 if (this._windowGlobalTargetActor) { 79 return this._windowGlobalTargetActor.docShell.browsingContext.id; 80 } 81 return null; 82 } 83 84 get isWindowlessParent() { 85 return this.isParent && (this.isXpcshell || this.isBackgroundTaskMode); 86 } 87 88 get isXpcshell() { 89 return Services.env.exists("XPCSHELL_TEST_PROFILE_DIR"); 90 } 91 92 get isBackgroundTaskMode() { 93 const bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService( 94 Ci.nsIBackgroundTasks 95 ); 96 return bts && bts.isBackgroundTaskMode; 97 } 98 99 _parentProcessConnect() { 100 let targetActor; 101 if (this.isWindowlessParent) { 102 // Check if we are running on xpcshell or in background task mode. 103 // In these modes, there is no valid browsing context to attach to 104 // and so ParentProcessTargetActor doesn't make sense as it inherits from 105 // WindowGlobalTargetActor. So instead use ContentProcessTargetActor, which 106 // matches the needs of these modes. 107 targetActor = new ContentProcessTargetActor(this.conn, { 108 isXpcShellTarget: true, 109 sessionContext: createContentProcessSessionContext(), 110 }); 111 } else { 112 // Create the target actor for the parent process, which is in the same process 113 // as this target. Because we are in the same process, we have a true actor that 114 // should be managed by the ProcessDescriptorActor. 115 targetActor = new ParentProcessTargetActor(this.conn, { 116 // This target actor is special and will stay alive as long 117 // as the toolbox/client is alive. It is the original top level target for 118 // the BrowserToolbox and isTopLevelTarget should always be true here. 119 // (It isn't the typical behavior of WindowGlobalTargetActor's base class) 120 isTopLevelTarget: true, 121 sessionContext: createBrowserSessionContext(), 122 }); 123 // this is a special field that only parent process with a browsing context 124 // have, as they are the only processes at the moment that have child 125 // browsing contexts 126 this._windowGlobalTargetActor = targetActor; 127 } 128 this.manage(targetActor); 129 // to be consistent with the return value of the _childProcessConnect, we are returning 130 // the form here. This might be memoized in the future 131 return targetActor.form(); 132 } 133 134 /** 135 * Connect to a remote process actor, always a ContentProcess target. 136 */ 137 async _childProcessConnect() { 138 const { id } = this; 139 const mm = this._lookupMessageManager(id); 140 if (!mm) { 141 return { 142 error: "noProcess", 143 message: "There is no process with id '" + id + "'.", 144 }; 145 } 146 const childTargetForm = await connectToContentProcess( 147 this.conn, 148 mm, 149 this.destroy 150 ); 151 return childTargetForm; 152 } 153 154 _lookupMessageManager(id) { 155 for (let i = 0; i < Services.ppmm.childCount; i++) { 156 const mm = Services.ppmm.getChildAt(i); 157 158 // A zero id is used for the parent process, instead of its actual pid. 159 if (id ? mm.osPid == id : mm.isInProcess) { 160 return mm; 161 } 162 } 163 return null; 164 } 165 166 /** 167 * Connect the a process actor. 168 */ 169 async getTarget() { 170 if (!DevToolsServer.allowChromeProcess) { 171 return { 172 error: "forbidden", 173 message: "You are not allowed to debug processes.", 174 }; 175 } 176 if (this.isParent) { 177 return this._parentProcessConnect(); 178 } 179 // This is a remote process we are connecting to 180 return this._childProcessConnect(); 181 } 182 183 /** 184 * Return a Watcher actor, allowing to keep track of targets which 185 * already exists or will be created. It also helps knowing when they 186 * are destroyed. 187 */ 188 getWatcher({ enableWindowGlobalThreadActors = false } = {}) { 189 if (!this.watcher) { 190 this.watcher = new WatcherActor( 191 this.conn, 192 createBrowserSessionContext({ 193 enableWindowGlobalThreadActors, 194 }) 195 ); 196 this.manage(this.watcher); 197 } 198 return this.watcher; 199 } 200 201 form() { 202 return { 203 actor: this.actorID, 204 id: this.id, 205 isParent: this.isParent, 206 isWindowlessParent: this.isWindowlessParent, 207 traits: { 208 // Supports the Watcher actor. Can be removed as part of Bug 1680280. 209 // Bug 1687461: WatcherActor only supports the parent process, where we debug everything. 210 // For the "Browser Content Toolbox", where we debug only one content process, 211 // we will still be using legacy listeners. 212 watcher: this.isParent, 213 // ParentProcessTargetActor can be reloaded. 214 supportsReloadDescriptor: this.isParent && !this.isWindowlessParent, 215 }, 216 }; 217 } 218 219 async reloadDescriptor() { 220 if (!this.isParent || this.isWindowlessParent) { 221 throw new Error( 222 "reloadDescriptor is only available for parent process descriptors" 223 ); 224 } 225 226 // Reload for the parent process will restart the whole browser 227 // 228 // This aims at replicate `DevelopmentHelpers.quickRestart` 229 // This allows a user to do a full firefox restart + session restore 230 // Via Ctrl+Alt+R on the Browser Console/Toolbox 231 232 // Maximize the chance of fetching new source content by clearing the cache 233 Services.obs.notifyObservers(null, "startupcache-invalidate"); 234 235 // Avoid safemode popup from appearing on restart 236 Services.env.set("MOZ_DISABLE_SAFE_MODE_KEY", "1"); 237 238 Services.startup.quit( 239 Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart 240 ); 241 } 242 243 destroy() { 244 this.emit("descriptor-destroyed"); 245 246 this._windowGlobalTargetActor = null; 247 super.destroy(); 248 } 249 } 250 251 exports.ProcessDescriptorActor = ProcessDescriptorActor;