inspector-helpers.js (4557B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /* exported assertOwnershipTrees, checkMissing, waitForMutation, 7 isSrcChange, isUnretained, isChildList */ 8 9 function serverOwnershipTree(walkerArg) { 10 return SpecialPowers.spawn( 11 gBrowser.selectedBrowser, 12 [[walkerArg.actorID]], 13 function (actorID) { 14 const { require } = ChromeUtils.importESModule( 15 "resource://devtools/shared/loader/Loader.sys.mjs" 16 ); 17 const { 18 DevToolsServer, 19 } = require("resource://devtools/server/devtools-server.js"); 20 const { 21 DocumentWalker, 22 } = require("resource://devtools/server/actors/inspector/document-walker.js"); 23 24 // Convert actorID to current compartment string otherwise 25 // searchAllConnectionsForActor is confused and won't find the actor. 26 actorID = String(actorID); 27 const serverWalker = DevToolsServer.searchAllConnectionsForActor(actorID); 28 29 function sortOwnershipChildrenContentScript(children) { 30 return children.sort((a, b) => a.name.localeCompare(b.name)); 31 } 32 33 function serverOwnershipSubtree(walker, node) { 34 const actor = walker.getNode(node); 35 if (!actor) { 36 return undefined; 37 } 38 39 const children = []; 40 const docwalker = new DocumentWalker(node, content); 41 let child = docwalker.firstChild(); 42 while (child) { 43 const item = serverOwnershipSubtree(walker, child); 44 if (item) { 45 children.push(item); 46 } 47 child = docwalker.nextSibling(); 48 } 49 return { 50 name: actor.actorID, 51 children: sortOwnershipChildrenContentScript(children), 52 }; 53 } 54 return { 55 root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc), 56 orphaned: [...serverWalker._orphaned].map(o => 57 serverOwnershipSubtree(serverWalker, o.rawNode) 58 ), 59 retained: [...serverWalker._retainedOrphans].map(o => 60 serverOwnershipSubtree(serverWalker, o.rawNode) 61 ), 62 }; 63 } 64 ); 65 } 66 67 function sortOwnershipChildren(children) { 68 return children.sort((a, b) => a.name.localeCompare(b.name)); 69 } 70 71 function clientOwnershipSubtree(node) { 72 return { 73 name: node.actorID, 74 children: sortOwnershipChildren( 75 node.treeChildren().map(child => clientOwnershipSubtree(child)) 76 ), 77 }; 78 } 79 80 function clientOwnershipTree(walker) { 81 return { 82 root: clientOwnershipSubtree(walker.rootNode), 83 orphaned: [...walker._orphaned].map(o => clientOwnershipSubtree(o)), 84 retained: [...walker._retainedOrphans].map(o => clientOwnershipSubtree(o)), 85 }; 86 } 87 88 function ownershipTreeSize(tree) { 89 let size = 1; 90 for (const child of tree.children) { 91 size += ownershipTreeSize(child); 92 } 93 return size; 94 } 95 96 async function assertOwnershipTrees(walker) { 97 const serverTree = await serverOwnershipTree(walker); 98 const clientTree = clientOwnershipTree(walker); 99 is( 100 JSON.stringify(clientTree, null, " "), 101 JSON.stringify(serverTree, null, " "), 102 "Server and client ownership trees should match." 103 ); 104 105 return ownershipTreeSize(clientTree.root); 106 } 107 108 // Verify that an actorID is inaccessible both from the client library and the server. 109 async function checkMissing({ client }, actorID) { 110 const front = client.getFrontByID(actorID); 111 ok( 112 !front, 113 "Front shouldn't be accessible from the client for actorID: " + actorID 114 ); 115 116 try { 117 await client.request({ 118 to: actorID, 119 type: "request", 120 }); 121 ok(false, "The actor wasn't missing as the request worked"); 122 } catch (e) { 123 is( 124 e.error, 125 "noSuchActor", 126 "node list actor should no longer be contactable." 127 ); 128 } 129 } 130 131 // Load mutations aren't predictable, so keep accumulating mutations until 132 // the one we're looking for shows up. 133 function waitForMutation(walker, test, mutations = []) { 134 return new Promise(resolve => { 135 for (const change of mutations) { 136 if (test(change)) { 137 resolve(mutations); 138 } 139 } 140 141 walker.once("mutations", newMutations => { 142 waitForMutation(walker, test, mutations.concat(newMutations)).then( 143 finalMutations => { 144 resolve(finalMutations); 145 } 146 ); 147 }); 148 }); 149 } 150 151 function isSrcChange(change) { 152 return change.type === "attributes" && change.attributeName === "src"; 153 } 154 155 function isUnretained(change) { 156 return change.type === "unretained"; 157 } 158 159 function isChildList(change) { 160 return change.type === "childList"; 161 }