/* 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 . */ // This test covers changing to a distinct "project root" // i.e. displaying only one particular thread, domain, or directory in the Source Tree. "use strict"; const httpServer = createTestHTTPServer(); const HOST = `localhost:${httpServer.identity.primaryPort}`; const BASE_URL = `http://${HOST}/`; const PAGE_URL = BASE_URL + "index.html"; const PAGE_CONTENT = ` `; const ALL_PAGE_SCRIPTS = [ "root-script.js", "folder-script.js", "sub-folder-script.js", ]; httpServer.registerPathHandler("/index.html", (request, response) => { response.setStatusLine(request.httpVersion, 200, "OK"); response.write(PAGE_CONTENT); }); httpServer.registerPathHandler("/root-script.js", (request, response) => { response.setHeader("Content-Type", "application/javascript"); response.write("console.log('root script')"); }); httpServer.registerPathHandler( "/folder/folder-script.js", (request, response) => { response.setHeader("Content-Type", "application/javascript"); response.write("console.log('folder script')"); } ); httpServer.registerPathHandler( "/folder/sub-folder/sub-folder-script.js", (request, response) => { response.setHeader("Content-Type", "application/javascript"); response.write("console.log('sub folder script')"); } ); const PAGE2_URL = BASE_URL + "index2.html"; const PAGE2_CONTENT = ` `; const ALL_PAGE2_SCRIPTS = [ "script.js", "webpack-script.js", "turbopack-script.js", "angular-script.js", "resource-script.js", "worker-script.js", ]; httpServer.registerPathHandler("/index2.html", (request, response) => { response.setStatusLine(request.httpVersion, 200, "OK"); response.write(PAGE2_CONTENT); }); httpServer.registerPathHandler("/src/script.js", (request, response) => { response.setHeader("Content-Type", "application/javascript"); response.write( "console.log('src script'); const worker = new Worker('src/worker-script.js')" ); }); httpServer.registerPathHandler("/src/worker-script.js", (request, response) => { response.setHeader("Content-Type", "application/javascript"); response.write("console.log('worker script')"); }); const httpServer2 = createTestHTTPServer(); const ALT_HOST = `localhost:${httpServer2.identity.primaryPort}`; const ALT_BASE_URL = `http://${ALT_HOST}/`; const PAGE3_URL = ALT_BASE_URL + "index.html"; const PAGE3_CONTENT = ` `; httpServer2.registerPathHandler("/index.html", (request, response) => { response.setStatusLine(request.httpVersion, 200, "OK"); response.write(PAGE3_CONTENT); }); httpServer2.registerPathHandler("/lib/script.js", (request, response) => { response.setStatusLine(request.httpVersion, 200, "OK"); response.write("console.log('lib script')"); }); add_task(async function testProjectRoot() { await pushPref("devtools.debugger.show-content-scripts", true); const dbg = await initDebuggerWithAbsoluteURL(PAGE_URL, ...ALL_PAGE_SCRIPTS); await waitForSourcesInSourceTree(dbg, ALL_PAGE_SCRIPTS); await selectAndCheckProjectRoots(dbg, [ { label: "Main Thread", tooltip: "Main Thread", sources: ALL_PAGE_SCRIPTS, }, { label: HOST, tooltip: `${BASE_URL.slice(0, -1)} on Main Thread`, sources: ALL_PAGE_SCRIPTS, }, { label: "folder", tooltip: `${BASE_URL}folder on Main Thread`, sources: ["folder-script.js", "sub-folder-script.js"], }, ]); info("Reload and see if project root is preserved"); await reload(dbg, "folder-script.js", "sub-folder-script.js"); await checkProjectRoot(dbg, "folder", `${BASE_URL}folder on Main Thread`, [ "folder-script.js", "sub-folder-script.js", ]); info("Select 'sub-folder' as project root"); await selectAndCheckProjectRoots(dbg, [ { label: "sub-folder", tooltip: `${BASE_URL}folder/sub-folder on Main Thread`, sources: ["sub-folder-script.js"], }, ]); info("Navigate to a different page with the same origin"); await navigateTo(PAGE2_URL); await checkProjectRoot( dbg, "sub-folder", `${BASE_URL}folder/sub-folder on Main Thread`, [] ); info("Clear project root"); await clearProjectRoot(dbg); await waitForSourcesInSourceTree(dbg, ALL_PAGE2_SCRIPTS); checkNoProjectRoot(dbg); info("Load the test extension"); const extension = await installAndStartExtension(); await waitForSourcesInSourceTree(dbg, [ ...ALL_PAGE2_SCRIPTS, "content_script.js", ]); await selectAndCheckProjectRoots(dbg, [ { label: "Test extension", tooltip: `Test extension`, sources: ["content_script.js"], }, { label: "Test extension", tooltip: `moz-extension://${extension.uuid} on Test extension`, sources: ["content_script.js"], }, { label: "src", tooltip: `moz-extension://${extension.uuid}/src on Test extension`, sources: ["content_script.js"], }, ]); info("Check that the project root is cleared when its thread is removed"); await extension.unload(); await waitForSourcesInSourceTree(dbg, ALL_PAGE2_SCRIPTS); checkNoProjectRoot(dbg); await selectAndCheckProjectRoots(dbg, [ { label: "Webpack", tooltip: `webpack:// on Main Thread`, sources: ["webpack-script.js"], }, { label: "src", tooltip: `webpack:///src on Main Thread`, sources: ["webpack-script.js"], }, ]); info("Clear project root"); await clearProjectRoot(dbg); await selectAndCheckProjectRoots(dbg, [ { label: "turbopack://", tooltip: `turbopack:// on Main Thread`, sources: ["turbopack-script.js"], }, { label: "src", tooltip: `turbopack:///src on Main Thread`, sources: ["turbopack-script.js"], }, ]); info("Clear project root"); await clearProjectRoot(dbg); await selectAndCheckProjectRoots(dbg, [ { label: "Angular", tooltip: `ng:// on Main Thread`, sources: ["angular-script.js"], }, { label: "src", tooltip: `ng:///src on Main Thread`, sources: ["angular-script.js"], }, ]); info("Clear project root"); await clearProjectRoot(dbg); await selectAndCheckProjectRoots(dbg, [ { label: "resource://devtools", tooltip: `resource://devtools on Main Thread`, sources: ["resource-script.js"], }, { label: "test", tooltip: `resource://devtools/test on Main Thread`, sources: ["resource-script.js"], }, ]); info("Clear project root"); await clearProjectRoot(dbg); await selectAndCheckProjectRoots(dbg, [ { label: "worker-script.js", tooltip: `worker-script.js`, sources: ["worker-script.js"], }, { label: HOST, tooltip: `${BASE_URL.slice(0, -1)} on worker-script.js`, sources: ["worker-script.js"], }, { label: "src", tooltip: `${BASE_URL}src on worker-script.js`, sources: ["worker-script.js"], }, ]); info("Navigate to a page with a different origin"); await navigateTo(PAGE3_URL); checkNoProjectRoot(dbg); await selectAndCheckProjectRoots(dbg, [ { label: "lib", tooltip: `${ALT_BASE_URL}lib on Main Thread`, sources: ["script.js"], }, ]); info("Navigate to the first page"); await navigateTo(PAGE_URL); checkNoProjectRoot(dbg); info("Navigate to the third page"); await navigateTo(PAGE3_URL); await checkProjectRoot(dbg, "lib", `${ALT_BASE_URL}lib on Main Thread`, [ "script.js", ]); info("Navigate to a data: URL"); const dataURL = "data:text/html,"; await navigateTo(dataURL); checkNoProjectRoot(dbg); await selectAndCheckProjectRoots(dbg, [ { label: "(no domain)", tooltip: `data: on Main Thread`, sources: [dataURL], }, ]); }); async function setProjectRoot(dbg, treeNode) { const dispatched = waitForDispatch(dbg.store, "SET_PROJECT_DIRECTORY_ROOT"); await triggerSourceTreeContextMenu(dbg, treeNode, "#node-set-directory-root"); await dispatched; } async function checkProjectRoot(dbg, label, tooltip, sources) { assertRootLabel(dbg, label); assertRootLabelTooltip(dbg, `Directory root set to ${tooltip}`); if (sources.length) { await waitForSourcesInSourceTree(dbg, sources); } else { ok(dbg.win.document.querySelector(".no-sources-message")); } } async function selectAndCheckProjectRoots(dbg, tests) { for (const test of tests) { const { label, tooltip, sources } = test; info(`Select ${label} as project root`); const item = findSourceNodeWithText(dbg, label); await setProjectRoot(dbg, item); await checkProjectRoot(dbg, label, tooltip, sources); } } async function checkNoProjectRoot(dbg) { ok(!dbg.win.document.querySelector(".sources-clear-root")); } function assertRootLabel(dbg, label) { const rootHeaderLabel = dbg.win.document.querySelector( ".sources-clear-root-label" ); is(rootHeaderLabel.textContent, label); } function assertRootLabelTooltip(dbg, text) { const rootHeader = dbg.win.document.querySelector( ".sources-clear-root-label" ); ok(rootHeader.title.includes(text)); } async function clearProjectRoot(dbg) { const rootHeader = dbg.win.document.querySelector(".sources-clear-root"); rootHeader.click(); } async function installAndStartExtension() { 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 extension", content_scripts: [ { js: ["src/content_script.js"], matches: ["http://*/*"], run_at: "document_start", }, ], }, useAddonManager: "temporary", files: { "src/content_script.js": contentScript, }, }); await extension.startup(); return extension; }