head.js (9833B)
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 /* eslint no-unused-vars: [2, {"vars": "local"}] */ 8 9 Services.scriptloader.loadSubScript( 10 "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", 11 this 12 ); 13 14 const { 15 DevToolsClient, 16 } = require("resource://devtools/client/devtools-client.js"); 17 const { 18 ActorRegistry, 19 } = require("resource://devtools/server/actors/utils/actor-registry.js"); 20 const { 21 DevToolsServer, 22 } = require("resource://devtools/server/devtools-server.js"); 23 24 const PATH = "browser/devtools/server/tests/browser/"; 25 const TEST_DOMAIN = "http://test1.example.org"; 26 const TEST_DOMAIN_HTTPS = "https://test1.example.org"; 27 const MAIN_DOMAIN = `${TEST_DOMAIN}/${PATH}`; 28 const MAIN_DOMAIN_HTTPS = `${TEST_DOMAIN_HTTPS}/${PATH}`; 29 const ALT_DOMAIN = "http://sectest1.example.org/" + PATH; 30 const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH; 31 32 // GUID to be used as a separator in compound keys. This must match the same 33 // constant in devtools/server/actors/resources/storage/index.js, 34 // devtools/client/storage/ui.js and devtools/client/storage/test/head.js 35 const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}"; 36 37 // All tests are asynchronous. 38 waitForExplicitFinish(); 39 40 // does almost the same thing as addTab, but directly returns an object 41 async function addTabTarget(url) { 42 info(`Adding a new tab with URL: ${url}`); 43 const tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url)); 44 await BrowserTestUtils.browserLoaded(tab.linkedBrowser); 45 info(`Tab added a URL ${url} loaded`); 46 return createAndAttachTargetForTab(tab); 47 } 48 49 async function initAnimationsFrontForUrl(url) { 50 const { inspector, walker, target } = await initInspectorFront(url); 51 const animations = await target.getFront("animations"); 52 53 return { inspector, walker, animations, target }; 54 } 55 56 async function initLayoutFrontForUrl(url) { 57 const { inspector, walker, target } = await initInspectorFront(url); 58 const layout = await walker.getLayoutInspector(); 59 60 return { inspector, walker, layout, target }; 61 } 62 63 async function initAccessibilityFrontsForUrl( 64 url, 65 { enableByDefault = true } = {} 66 ) { 67 const { inspector, walker, target } = await initInspectorFront(url); 68 const parentAccessibility = await target.client.mainRoot.getFront( 69 "parentaccessibility" 70 ); 71 const accessibility = await target.getFront("accessibility"); 72 const a11yWalker = accessibility.accessibleWalkerFront; 73 if (enableByDefault) { 74 await parentAccessibility.enable(); 75 } 76 77 return { 78 inspector, 79 walker, 80 accessibility, 81 parentAccessibility, 82 a11yWalker, 83 target, 84 }; 85 } 86 87 function initDevToolsServer() { 88 try { 89 // Sometimes devtools server does not get destroyed correctly by previous 90 // tests. 91 DevToolsServer.destroy(); 92 } catch (e) { 93 info(`DevToolsServer destroy error: ${e}\n${e.stack}`); 94 } 95 DevToolsServer.init(); 96 DevToolsServer.registerAllActors(); 97 } 98 99 async function initPerfFront() { 100 initDevToolsServer(); 101 const client = new DevToolsClient(DevToolsServer.connectPipe()); 102 await waitUntilClientConnected(client); 103 const front = await client.mainRoot.getFront("perf"); 104 return { front, client }; 105 } 106 107 async function initInspectorFront(url) { 108 const target = await addTabTarget(url); 109 const inspector = await target.getFront("inspector"); 110 const walker = inspector.walker; 111 112 return { inspector, walker, target }; 113 } 114 115 /** 116 * Wait until a DevToolsClient is connected. 117 * 118 * @param {DevToolsClient} client 119 * @return {Promise} Resolves when connected. 120 */ 121 function waitUntilClientConnected(client) { 122 return client.once("connected"); 123 } 124 125 /** 126 * Wait for eventName on target. 127 * 128 * @param {object} target An observable object that either supports on/off or 129 * addEventListener/removeEventListener 130 * @param {string} eventName 131 * @param {boolean} useCapture Optional, for addEventListener/removeEventListener 132 * @return A promise that resolves when the event has been handled 133 */ 134 function once(target, eventName, useCapture = false) { 135 info("Waiting for event: '" + eventName + "' on " + target + "."); 136 137 return new Promise(resolve => { 138 for (const [add, remove] of [ 139 ["addEventListener", "removeEventListener"], 140 ["addListener", "removeListener"], 141 ["on", "off"], 142 ]) { 143 if (add in target && remove in target) { 144 target[add]( 145 eventName, 146 function onEvent(...aArgs) { 147 info("Got event: '" + eventName + "' on " + target + "."); 148 target[remove](eventName, onEvent, useCapture); 149 resolve(...aArgs); 150 }, 151 useCapture 152 ); 153 break; 154 } 155 } 156 }); 157 } 158 159 /** 160 * Forces GC, CC and Shrinking GC to get rid of disconnected docshells and 161 * windows. 162 */ 163 function forceCollections() { 164 Cu.forceGC(); 165 Cu.forceCC(); 166 Cu.forceShrinkingGC(); 167 } 168 169 registerCleanupFunction(function tearDown() { 170 Services.cookies.removeAll(); 171 172 while (gBrowser.tabs.length > 1) { 173 gBrowser.removeCurrentTab(); 174 } 175 }); 176 177 function idleWait(time) { 178 return DevToolsUtils.waitForTime(time); 179 } 180 181 function busyWait(time) { 182 const start = Date.now(); 183 let stack; 184 while (Date.now() - start < time) { 185 stack = Components.stack; // eslint-disable-line no-unused-vars 186 } 187 } 188 189 /** 190 * Waits until a predicate returns true. 191 * 192 * @param function predicate 193 * Invoked once in a while until it returns true. 194 * @param number interval [optional] 195 * How often the predicate is invoked, in milliseconds. 196 */ 197 function waitUntil(predicate, interval = 10) { 198 if (predicate()) { 199 return Promise.resolve(true); 200 } 201 return new Promise(resolve => { 202 setTimeout(function () { 203 waitUntil(predicate).then(() => resolve(true)); 204 }, interval); 205 }); 206 } 207 208 function waitForMarkerType( 209 front, 210 types, 211 predicate, 212 unpackFun = (name, data) => data.markers, 213 eventName = "timeline-data" 214 ) { 215 types = [].concat(types); 216 predicate = 217 predicate || 218 function () { 219 return true; 220 }; 221 let filteredMarkers = []; 222 223 return new Promise(resolve => { 224 info("Waiting for markers of type: " + types); 225 226 function handler(name, data) { 227 if (typeof name === "string" && name !== "markers") { 228 return; 229 } 230 231 const markers = unpackFun(name, data); 232 info("Got markers"); 233 234 filteredMarkers = filteredMarkers.concat( 235 markers.filter(m => types.includes(m.name)) 236 ); 237 238 if ( 239 types.every(t => filteredMarkers.some(m => m.name === t)) && 240 predicate(filteredMarkers) 241 ) { 242 front.off(eventName, handler); 243 resolve(filteredMarkers); 244 } 245 } 246 front.on(eventName, handler); 247 }); 248 } 249 250 function getCookieId(name, domain, path, partitionKey = "") { 251 return `${name}${SEPARATOR_GUID}${domain}${SEPARATOR_GUID}${path}${SEPARATOR_GUID}${partitionKey}`; 252 } 253 254 /** 255 * Trigger DOM activity and wait for the corresponding accessibility event. 256 * 257 * @param {object} emitter Devtools event emitter, usually a front. 258 * @param {Sting} name Accessibility event in question. 259 * @param {Function} handler Accessibility event handler function with checks. 260 * @param {Promise} task A promise that resolves when DOM activity is done. 261 */ 262 async function emitA11yEvent(emitter, name, handler, task) { 263 const promise = emitter.once(name, handler); 264 await task(); 265 await promise; 266 } 267 268 /** 269 * Check that accessibilty front is correct and its attributes are also 270 * up-to-date. 271 * 272 * @param {object} front Accessibility front to be tested. 273 * @param {object} expected A map of a11y front properties to be verified. 274 * @param {object} expectedFront Expected accessibility front. 275 */ 276 function checkA11yFront(front, expected, expectedFront) { 277 ok(front, "The accessibility front is created"); 278 279 if (expectedFront) { 280 is(front, expectedFront, "Matching accessibility front"); 281 } 282 283 // Clone the front so we could modify some values for comparison. 284 front = Object.assign(front); 285 for (const key in expected) { 286 if (key === "checks") { 287 const { CONTRAST } = front[key]; 288 // Contrast values are rounded to two digits after the decimal point. 289 if (CONTRAST && CONTRAST.value) { 290 CONTRAST.value = parseFloat(CONTRAST.value.toFixed(2)); 291 } 292 } 293 294 if (["actions", "states", "attributes", "checks"].includes(key)) { 295 SimpleTest.isDeeply( 296 front[key], 297 expected[key], 298 `Accessible Front has correct ${key}` 299 ); 300 } else { 301 is(front[key], expected[key], `accessibility front has correct ${key}`); 302 } 303 } 304 } 305 306 function getA11yInitOrShutdownPromise() { 307 return new Promise(resolve => { 308 const observe = (subject, topic, data) => { 309 Services.obs.removeObserver(observe, "a11y-init-or-shutdown"); 310 resolve(data); 311 }; 312 Services.obs.addObserver(observe, "a11y-init-or-shutdown"); 313 }); 314 } 315 316 /** 317 * Wait for accessibility service to shut down. We consider it shut down when 318 * an "a11y-init-or-shutdown" event is received with a value of "0". 319 */ 320 async function waitForA11yShutdown(parentAccessibility) { 321 await parentAccessibility.disable(); 322 if (!Services.appinfo.accessibilityEnabled) { 323 return; 324 } 325 326 await getA11yInitOrShutdownPromise().then(data => 327 data === "0" ? Promise.resolve() : Promise.reject() 328 ); 329 } 330 331 /** 332 * Wait for accessibility service to initialize. We consider it initialized when 333 * an "a11y-init-or-shutdown" event is received with a value of "1". 334 */ 335 async function waitForA11yInit() { 336 if (Services.appinfo.accessibilityEnabled) { 337 return; 338 } 339 340 await getA11yInitOrShutdownPromise().then(data => 341 data === "1" ? Promise.resolve() : Promise.reject() 342 ); 343 }