head.js (7762B)
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 /* eslint-disable no-unused-vars */ 6 7 "use strict"; 8 9 // This head.js file is only imported by debugger mochitests. 10 // Anything that is meant to be used by tests of other panels should be moved to shared-head.js 11 // Also, any symbol that may conflict with other test symbols should stay in head.js 12 // (like EXAMPLE_URL) 13 14 const EXAMPLE_URL = 15 "https://example.com/browser/devtools/client/debugger/test/mochitest/examples/"; 16 17 // This URL is remote compared to EXAMPLE_URL, as one uses .com and the other uses .org 18 // Note that this depends on initDebugger to always use EXAMPLE_URL 19 const EXAMPLE_REMOTE_URL = 20 "https://example.org/browser/devtools/client/debugger/test/mochitest/examples/"; 21 22 const EXAMPLE_URL_WITH_PORT = 23 "http://mochi.test:8888/browser/devtools/client/debugger/test/mochitest/examples/"; 24 25 // shared-head.js handles imports, constants, and utility functions 26 Services.scriptloader.loadSubScript( 27 "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", 28 this 29 ); 30 31 Services.scriptloader.loadSubScript( 32 "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/shared-head.js", 33 this 34 ); 35 36 Services.scriptloader.loadSubScript( 37 "chrome://mochitests/content/browser/devtools/client/webconsole/test/browser/shared-head.js", 38 this 39 ); 40 41 // Cleanup preferences set by the tracer. 42 registerCleanupFunction(() => { 43 for (const pref of ["logging.console", "logging.PageMessages"]) { 44 Services.prefs.clearUserPref(pref); 45 } 46 }); 47 48 /** 49 * Install a Web Extension which will run a content script against any test page 50 * served from https://example.com 51 * 52 * This content script is meant to be debuggable when devtools.chrome.enabled is true. 53 */ 54 async function installAndStartContentScriptExtension() { 55 function contentScript() { 56 console.log("content script loads"); 57 58 // This listener prevents the source from being garbage collected 59 // and be missing from the scripts returned by `dbg.findScripts()` 60 // in `ThreadActor._discoverSources`. 61 window.onload = () => {}; 62 } 63 64 const extension = ExtensionTestUtils.loadExtension({ 65 manifest: { 66 name: "Test content script extension", 67 content_scripts: [ 68 { 69 js: ["content_script.js"], 70 matches: ["https://example.com/*"], 71 run_at: "document_start", 72 }, 73 ], 74 }, 75 files: { 76 "content_script.js": contentScript, 77 }, 78 }); 79 80 await extension.startup(); 81 82 return extension; 83 } 84 85 /** 86 * Return the text content for a given line in the Source Tree. 87 * 88 * @param {object} dbg 89 * @param {number} index 90 * Line number in the source tree 91 */ 92 function getSourceTreeLabel(dbg, index) { 93 return ( 94 findElement(dbg, "sourceNode", index) 95 .textContent.trim() 96 // There is some special whitespace character which aren't removed by trim() 97 .replace(/^[\s\u200b]*/g, "") 98 ); 99 } 100 101 /** 102 * Find and assert the source tree node with the specified text 103 * exists on the source tree. 104 * 105 * @param {object} dbg 106 * @param {string} text The node text displayed 107 */ 108 async function assertSourceTreeNode(dbg, text) { 109 let node = null; 110 await waitUntil(() => { 111 node = findSourceNodeWithText(dbg, text); 112 return !!node; 113 }); 114 ok(!!node, `Source tree node with text "${text}" exists`); 115 } 116 117 /** 118 * Assert precisely the list of all breakable line for a given source 119 * 120 * @param {object} dbg 121 * @param {object | string} file 122 * The source name or source object to review 123 * @param {number} numberOfLines 124 * The expected number of lines for this source. 125 * @param {Array<number>} breakableLines 126 * This list of all breakable line numbers 127 */ 128 async function assertBreakableLines( 129 dbg, 130 source, 131 numberOfLines, 132 breakableLines 133 ) { 134 await selectSource(dbg, source); 135 is( 136 getLineCount(dbg), 137 numberOfLines, 138 `We show the expected number of lines in CodeMirror for ${source}` 139 ); 140 for (let line = 1; line <= numberOfLines; line++) { 141 await assertLineIsBreakable( 142 dbg, 143 source, 144 line, 145 breakableLines.includes(line) 146 ); 147 } 148 } 149 150 /** 151 * Helper alongside assertBreakable lines to ease defining list of breakable lines. 152 * 153 * @param {number} start 154 * @param {number} end 155 * @return {Array<number>} 156 * Returns an array of decimal numbers starting from `start` and ending with `end`. 157 */ 158 function getRange(start, end) { 159 const range = []; 160 for (let i = start; i <= end; i++) { 161 range.push(i); 162 } 163 return range; 164 } 165 166 /** 167 * Get the currently selected line number displayed in the editor's footer. 168 */ 169 function assertCursorPosition(dbg, expectedLine, expectedColumn, message) { 170 const cursorPosition = findElementWithSelector(dbg, ".cursor-position"); 171 if (!cursorPosition) { 172 ok(false, message + " (no cursor displayed in footer)"); 173 } 174 // Cursor position text has the following shape: (L, C) 175 // where L is the line number, and C the column number 176 const match = cursorPosition.innerText.match(/\((\d+), (\d+)\)/); 177 if (!match) { 178 ok( 179 false, 180 message + 181 ` (wrong cursor content in footer : '${cursorPosition.innerText}')` 182 ); 183 } 184 const [_, line, column] = match; 185 is(parseInt(line, 10), expectedLine, message + " (footer line)"); 186 is(parseInt(column, 10), expectedColumn, message + " (footer column)"); 187 const cursor = getCMEditor(dbg).getSelectionCursor(); 188 is(cursor.from.line, expectedLine, message + " (actual cursor line)"); 189 // CodeMirror column is 0-based while the location mentioned in test 1-based. 190 is(cursor.from.ch + 1, expectedColumn, message + " (actual cursor column)"); 191 } 192 193 /** 194 * @see selectDebuggerContextMenuItem in debugger/test/mochitest/shared-head.js 195 */ 196 function selectContextMenuItem(dbg, selector) { 197 return selectDebuggerContextMenuItem(dbg, selector); 198 } 199 200 function getEventListenersPanel(dbg) { 201 return findElementWithSelector(dbg, ".event-listeners-pane .event-listeners"); 202 } 203 204 async function toggleEventBreakpoint( 205 dbg, 206 eventBreakpointGroup, 207 eventBreakpointName 208 ) { 209 const eventCheckbox = await getEventBreakpointCheckbox( 210 dbg, 211 eventBreakpointGroup, 212 eventBreakpointName 213 ); 214 eventCheckbox.scrollIntoView(); 215 info(`Toggle ${eventBreakpointName} breakpoint`); 216 const onEventListenersUpdate = waitForDispatch( 217 dbg.store, 218 "UPDATE_EVENT_LISTENERS" 219 ); 220 const checked = eventCheckbox.checked; 221 eventCheckbox.click(); 222 await onEventListenersUpdate; 223 224 info("Wait for the event breakpoint checkbox to be toggled"); 225 // Wait for he UI to be toggled, otherwise, the reducer may not be fully updated 226 await waitFor(() => { 227 return eventCheckbox.checked == !checked; 228 }); 229 } 230 231 async function getEventBreakpointCheckbox( 232 dbg, 233 eventBreakpointGroup, 234 eventBreakpointName 235 ) { 236 if (!getEventListenersPanel(dbg)) { 237 // Event listeners panel is collapsed, expand it 238 findElementWithSelector( 239 dbg, 240 `.event-listeners-pane ._header .header-label` 241 ).click(); 242 await waitFor(() => getEventListenersPanel(dbg)); 243 } 244 245 const groupCheckbox = findElementWithSelector( 246 dbg, 247 `input[value="${eventBreakpointGroup}"]` 248 ); 249 const groupEl = groupCheckbox.closest(".event-listener-group"); 250 let groupEventsUl = groupEl.querySelector("ul"); 251 if (!groupEventsUl) { 252 info( 253 `Expand ${eventBreakpointGroup} and wait for the sub list to be displayed` 254 ); 255 groupEl.querySelector(".event-listener-expand").click(); 256 groupEventsUl = await waitFor(() => groupEl.querySelector("ul")); 257 } 258 259 return findElementWithSelector(dbg, `input[value="${eventBreakpointName}"]`); 260 }