/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ /* eslint-disable no-unused-vars */ "use strict"; // This head.js file is only imported by debugger mochitests. // Anything that is meant to be used by tests of other panels should be moved to shared-head.js // Also, any symbol that may conflict with other test symbols should stay in head.js // (like EXAMPLE_URL) const EXAMPLE_URL = "https://example.com/browser/devtools/client/debugger/test/mochitest/examples/"; // This URL is remote compared to EXAMPLE_URL, as one uses .com and the other uses .org // Note that this depends on initDebugger to always use EXAMPLE_URL const EXAMPLE_REMOTE_URL = "https://example.org/browser/devtools/client/debugger/test/mochitest/examples/"; const EXAMPLE_URL_WITH_PORT = "http://mochi.test:8888/browser/devtools/client/debugger/test/mochitest/examples/"; // shared-head.js handles imports, constants, and utility functions Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this ); Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/shared-head.js", this ); Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/devtools/client/webconsole/test/browser/shared-head.js", this ); // Cleanup preferences set by the tracer. registerCleanupFunction(() => { for (const pref of ["logging.console", "logging.PageMessages"]) { Services.prefs.clearUserPref(pref); } }); /** * Install a Web Extension which will run a content script against any test page * served from https://example.com * * This content script is meant to be debuggable when devtools.chrome.enabled is true. */ async function installAndStartContentScriptExtension() { function contentScript() { console.log("content script loads"); // This listener prevents the source from being garbage collected // and be missing from the scripts returned by `dbg.findScripts()` // in `ThreadActor._discoverSources`. window.onload = () => {}; } const extension = ExtensionTestUtils.loadExtension({ manifest: { name: "Test content script extension", content_scripts: [ { js: ["content_script.js"], matches: ["https://example.com/*"], run_at: "document_start", }, ], }, files: { "content_script.js": contentScript, }, }); await extension.startup(); return extension; } /** * Return the text content for a given line in the Source Tree. * * @param {Object} dbg * @param {Number} index * Line number in the source tree */ function getSourceTreeLabel(dbg, index) { return ( findElement(dbg, "sourceNode", index) .textContent.trim() // There is some special whitespace character which aren't removed by trim() .replace(/^[\s\u200b]*/g, "") ); } /** * Find and assert the source tree node with the specified text * exists on the source tree. * * @param {Object} dbg * @param {String} text The node text displayed */ async function assertSourceTreeNode(dbg, text) { let node = null; await waitUntil(() => { node = findSourceNodeWithText(dbg, text); return !!node; }); ok(!!node, `Source tree node with text "${text}" exists`); } /** * Assert precisely the list of all breakable line for a given source * * @param {Object} dbg * @param {Object|String} file * The source name or source object to review * @param {Number} numberOfLines * The expected number of lines for this source. * @param {Array} breakableLines * This list of all breakable line numbers */ async function assertBreakableLines( dbg, source, numberOfLines, breakableLines ) { await selectSource(dbg, source); is( getLineCount(dbg), numberOfLines, `We show the expected number of lines in CodeMirror for ${source}` ); for (let line = 1; line <= numberOfLines; line++) { await assertLineIsBreakable( dbg, source, line, breakableLines.includes(line) ); } } /** * Helper alongside assertBreakable lines to ease defining list of breakable lines. * * @param {Number} start * @param {Number} end * @return {Array} * Returns an array of decimal numbers starting from `start` and ending with `end`. */ function getRange(start, end) { const range = []; for (let i = start; i <= end; i++) { range.push(i); } return range; } /** * Get the currently selected line number displayed in the editor's footer. */ function assertCursorPosition(dbg, expectedLine, expectedColumn, message) { const cursorPosition = findElementWithSelector(dbg, ".cursor-position"); if (!cursorPosition) { ok(false, message + " (no cursor displayed in footer)"); } // Cursor position text has the following shape: (L, C) // where L is the line number, and C the column number const match = cursorPosition.innerText.match(/\((\d+), (\d+)\)/); if (!match) { ok( false, message + ` (wrong cursor content in footer : '${cursorPosition.innerText}')` ); } const [_, line, column] = match; is(parseInt(line, 10), expectedLine, message + " (footer line)"); is(parseInt(column, 10), expectedColumn, message + " (footer column)"); const cursor = getCMEditor(dbg).getSelectionCursor(); is(cursor.from.line, expectedLine, message + " (actual cursor line)"); // CodeMirror column is 0-based while the location mentioned in test 1-based. is(cursor.from.ch + 1, expectedColumn, message + " (actual cursor column)"); } /** * @see selectDebuggerContextMenuItem in debugger/test/mochitest/shared-head.js */ function selectContextMenuItem(dbg, selector) { return selectDebuggerContextMenuItem(dbg, selector); } function getEventListenersPanel(dbg) { return findElementWithSelector(dbg, ".event-listeners-pane .event-listeners"); } async function toggleEventBreakpoint( dbg, eventBreakpointGroup, eventBreakpointName ) { const eventCheckbox = await getEventBreakpointCheckbox( dbg, eventBreakpointGroup, eventBreakpointName ); eventCheckbox.scrollIntoView(); info(`Toggle ${eventBreakpointName} breakpoint`); const onEventListenersUpdate = waitForDispatch( dbg.store, "UPDATE_EVENT_LISTENERS" ); const checked = eventCheckbox.checked; eventCheckbox.click(); await onEventListenersUpdate; info("Wait for the event breakpoint checkbox to be toggled"); // Wait for he UI to be toggled, otherwise, the reducer may not be fully updated await waitFor(() => { return eventCheckbox.checked == !checked; }); } async function getEventBreakpointCheckbox( dbg, eventBreakpointGroup, eventBreakpointName ) { if (!getEventListenersPanel(dbg)) { // Event listeners panel is collapsed, expand it findElementWithSelector( dbg, `.event-listeners-pane ._header .header-label` ).click(); await waitFor(() => getEventListenersPanel(dbg)); } const groupCheckbox = findElementWithSelector( dbg, `input[value="${eventBreakpointGroup}"]` ); const groupEl = groupCheckbox.closest(".event-listener-group"); let groupEventsUl = groupEl.querySelector("ul"); if (!groupEventsUl) { info( `Expand ${eventBreakpointGroup} and wait for the sub list to be displayed` ); groupEl.querySelector(".event-listener-expand").click(); groupEventsUl = await waitFor(() => groupEl.querySelector("ul")); } return findElementWithSelector(dbg, `input[value="${eventBreakpointName}"]`); }