frame-connector.js (5266B)
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 var DevToolsUtils = require("devtools/shared/DevToolsUtils"); 8 var { dumpn } = DevToolsUtils; 9 10 loader.lazyRequireGetter( 11 this, 12 "DevToolsServer", 13 "resource://devtools/server/devtools-server.js", 14 true 15 ); 16 loader.lazyRequireGetter( 17 this, 18 "ChildDebuggerTransport", 19 "resource://devtools/shared/transport/child-transport.js", 20 true 21 ); 22 23 loader.lazyRequireGetter( 24 this, 25 "EventEmitter", 26 "resource://devtools/shared/event-emitter.js" 27 ); 28 29 /** 30 * Start a DevTools server in a remote frame's process and add it as a child server for 31 * an active connection. 32 * 33 * @param object connection 34 * The devtools server connection to use. 35 * @param Element frame 36 * The frame element with remote content to connect to. 37 * @param function [onDestroy] 38 * Optional function to invoke when the child process closes or the connection 39 * shuts down. (Need to forget about the related target actor.) 40 * @return object 41 * A promise object that is resolved once the connection is established. 42 */ 43 function connectToFrame(connection, frame, onDestroy) { 44 return new Promise(resolve => { 45 // Get messageManager from XUL browser (which might be a specialized tunnel for RDM) 46 // or else fallback to asking the frameLoader itself. 47 const mm = frame.messageManager || frame.frameLoader.messageManager; 48 mm.loadFrameScript("resource://devtools/server/startup/frame.js", false); 49 50 const trackMessageManager = () => { 51 if (!actor) { 52 mm.addMessageListener("debug:actor", onActorCreated); 53 } 54 }; 55 56 const untrackMessageManager = () => { 57 if (!actor) { 58 mm.removeMessageListener("debug:actor", onActorCreated); 59 } 60 }; 61 62 let actor, childTransport; 63 const prefix = connection.allocID("child"); 64 // Compute the same prefix that's used by DevToolsServerConnection 65 const connPrefix = prefix + "/"; 66 67 const onActorCreated = DevToolsUtils.makeInfallible(function (msg) { 68 if (msg.json.prefix != prefix) { 69 return; 70 } 71 mm.removeMessageListener("debug:actor", onActorCreated); 72 73 // Pipe Debugger message from/to parent/child via the message manager 74 childTransport = new ChildDebuggerTransport(mm, prefix); 75 childTransport.hooks = { 76 // Pipe all the messages from content process actors back to the client 77 // through the parent process connection. 78 onPacket: connection.send.bind(connection), 79 }; 80 childTransport.ready(); 81 82 connection.setForwarding(prefix, childTransport); 83 84 dumpn(`Start forwarding for frame with prefix ${prefix}`); 85 86 actor = msg.json.actor; 87 resolve(actor); 88 }); 89 90 const destroy = DevToolsUtils.makeInfallible(function () { 91 connection.off("closed", destroy); 92 Services.obs.removeObserver( 93 onMessageManagerClose, 94 "message-manager-close" 95 ); 96 97 // TODO: Remove this deprecated path once it's no longer needed by add-ons. 98 DevToolsServer.emit("disconnected-from-child:" + connPrefix, { 99 mm, 100 prefix: connPrefix, 101 }); 102 103 if (actor) { 104 actor = null; 105 } 106 107 // Notify the tab descriptor about the destruction before the call to 108 // `cancelForwarding`, so that we notify about the target destruction 109 // *before* we purge all request for this prefix. 110 // When we purge the requests, we also destroy all related fronts, 111 // including the target front. This clears all event listeners 112 // and ultimately prevent target-destroyed from firing. 113 if (onDestroy) { 114 onDestroy(mm); 115 } 116 117 if (childTransport) { 118 // If we have a child transport, the actor has already 119 // been created. We need to stop using this message manager. 120 childTransport.close(); 121 childTransport = null; 122 connection.cancelForwarding(prefix); 123 124 // ... and notify the child process to clean the target-scoped actors. 125 try { 126 // Bug 1169643: Ignore any exception as the child process 127 // may already be destroyed by now. 128 mm.sendAsyncMessage("debug:disconnect", { prefix }); 129 } catch (e) { 130 // Nothing to do 131 } 132 } else { 133 // Otherwise, the frame has been closed before the actor 134 // had a chance to be created, so we are not able to create 135 // the actor. 136 resolve(null); 137 } 138 139 // Cleanup all listeners 140 untrackMessageManager(); 141 }); 142 143 // Listen for various messages and frame events 144 trackMessageManager(); 145 146 // Listen for app process exit 147 const onMessageManagerClose = function (subject) { 148 if (subject == mm) { 149 destroy(); 150 } 151 }; 152 Services.obs.addObserver(onMessageManagerClose, "message-manager-close"); 153 154 // Listen for connection close to cleanup things 155 // when user unplug the device or we lose the connection somehow. 156 connection.on("closed", destroy); 157 158 mm.sendAsyncMessage("debug:connect", { 159 prefix, 160 }); 161 }); 162 } 163 164 exports.connectToFrame = connectToFrame;