frame.js (6718B)
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 /* global addEventListener */ 8 9 /* 10 * Frame script that listens for requests to start a `DevToolsServer` for a frame in a 11 * content process. Loaded into content process frames by the main process during 12 * frame-connector.js' connectToFrame. 13 */ 14 15 try { 16 var chromeGlobal = this; 17 18 // Encapsulate in its own scope to allows loading this frame script more than once. 19 (function () { 20 // In most cases, we are debugging a tab in content process, without chrome 21 // privileges. But in some tests, we are attaching to privileged document. 22 // Because the debugger can't be running in the same compartment than its debuggee, 23 // we have to load the server in a dedicated Loader, loading modules in a distinct compartment. 24 // That's what DistinctSystemPrincipalLoader does. 25 let loader, 26 customLoader = false; 27 if (content.document.nodePrincipal.isSystemPrincipal) { 28 const { useDistinctSystemPrincipalLoader } = ChromeUtils.importESModule( 29 "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", 30 { global: "shared" } 31 ); 32 loader = useDistinctSystemPrincipalLoader(chromeGlobal); 33 customLoader = true; 34 } else { 35 // Otherwise, use the shared loader. 36 loader = ChromeUtils.importESModule( 37 "resource://devtools/shared/loader/Loader.sys.mjs" 38 ); 39 } 40 const { require } = loader; 41 42 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); 43 const { 44 DevToolsServer, 45 } = require("resource://devtools/server/devtools-server.js"); 46 47 DevToolsServer.init(); 48 // We want a special server without any root actor and only target-scoped actors. 49 // We are going to spawn a WindowGlobalTargetActor instance in the next few lines, 50 // it is going to act like a root actor without being one. 51 DevToolsServer.registerActors({ target: true }); 52 53 const connections = new Map(); 54 55 const onConnect = DevToolsUtils.makeInfallible(function (msg) { 56 const mm = msg.target; 57 const prefix = msg.data.prefix; 58 59 // If we try to create several frame targets simultaneously, the frame script will be loaded several times. 60 // In this case a single "debug:connect" message might be received by all the already loaded frame scripts. 61 // Check if the DevToolsServer already knows the provided connection prefix, 62 // because it means that another framescript instance already handled this message. 63 // Another "debug:connect" message is guaranteed to be emitted for another prefix, 64 // so we keep the message listener and wait for this next message. 65 if (DevToolsServer.hasConnectionForPrefix(prefix)) { 66 return; 67 } 68 removeMessageListener("debug:connect", onConnect); 69 70 const conn = DevToolsServer.connectToParent(prefix, mm); 71 connections.set(prefix, conn); 72 73 const { 74 WindowGlobalTargetActor, 75 } = require("resource://devtools/server/actors/targets/window-global.js"); 76 const { 77 createBrowserElementSessionContext, 78 } = require("resource://devtools/server/actors/watcher/session-context.js"); 79 80 const { docShell } = chromeGlobal; 81 // For a script loaded via loadFrameScript, the global is the content 82 // message manager. 83 // All WindowGlobalTarget actors created via the framescript are top-level 84 // targets. Non top-level WindowGlobalTarget actors are all created by the 85 // DevToolsFrameChild actor. 86 // 87 // createBrowserElementSessionContext only reads browserId attribute 88 const fakeBrowserElement = { 89 browserId: docShell.browsingContext.browserId, 90 }; 91 const actor = new WindowGlobalTargetActor(conn, { 92 docShell, 93 ignoreSubFrames: false, 94 isTopLevelTarget: true, 95 // This is only used when server target switching is off and we create 96 // the target from TabDescriptor. So all config attributes are false. 97 sessionContext: createBrowserElementSessionContext( 98 fakeBrowserElement, 99 {} 100 ), 101 }); 102 actor.manage(actor); 103 104 sendAsyncMessage("debug:actor", { actor: actor.form(), prefix }); 105 }); 106 107 addMessageListener("debug:connect", onConnect); 108 109 const onDisconnect = DevToolsUtils.makeInfallible(function (msg) { 110 const prefix = msg.data.prefix; 111 const conn = connections.get(prefix); 112 if (!conn) { 113 // Several copies of this frame script can be running for a single frame since it 114 // is loaded once for each DevTools connection to the frame. If this disconnect 115 // request doesn't match a connection known here, ignore it. 116 return; 117 } 118 119 removeMessageListener("debug:disconnect", onDisconnect); 120 // Call DevToolsServerConnection.close to destroy all child actors. It should end up 121 // calling DevToolsServerConnection.onTransportClosed that would actually cleanup all actor 122 // pools. 123 conn.close(); 124 connections.delete(prefix); 125 }); 126 addMessageListener("debug:disconnect", onDisconnect); 127 128 // In non-e10s mode, the "debug:disconnect" message isn't always received before the 129 // messageManager connection goes away. Watching for "unload" here ensures we close 130 // any connections when the frame is unloaded. 131 addEventListener("unload", () => { 132 for (const conn of connections.values()) { 133 conn.close(); 134 } 135 connections.clear(); 136 }); 137 138 // Destroy the server once its last connection closes. Note that multiple frame 139 // scripts may be running in parallel and reuse the same server. 140 function destroyLoader() { 141 // Only destroy the server if there is no more connections to it. It may be used 142 // to debug another tab running in the same process. 143 if (DevToolsServer.hasConnection() || DevToolsServer.keepAlive) { 144 return; 145 } 146 DevToolsServer.off("connectionchange", destroyLoader); 147 148 // When debugging chrome pages, we initialized a dedicated loader, also destroy it 149 if (customLoader) { 150 const { releaseDistinctSystemPrincipalLoader } = 151 ChromeUtils.importESModule( 152 "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", 153 { global: "shared" } 154 ); 155 releaseDistinctSystemPrincipalLoader(chromeGlobal); 156 } 157 } 158 DevToolsServer.on("connectionchange", destroyLoader); 159 })(); 160 } catch (e) { 161 dump(`Exception in DevTools frame startup: ${e}\n`); 162 }