1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
4 // Note to run this test similar to try server, you need to run:
6 // ./mach mochitest --appname dist <path to test>
8 // Slow on asan builds.
9 requestLongerTimeout(5);
11 var isDevtools = SimpleTest.harnessParameters.subsuite == "devtools";
13 // This list should contain only path prefixes. It is meant to stop the test
14 // from reporting things that *are* referenced, but for which the test can't
15 // find any reference because the URIs are constructed programatically.
16 // If you need to allowlist specific files, please use the 'allowlist' object.
17 var gExceptionPaths = [
18 "resource://app/defaults/settings/blocklists/",
19 "resource://app/defaults/settings/security-state/",
20 "resource://app/defaults/settings/main/",
21 "resource://app/defaults/preferences/",
22 "resource://gre/modules/commonjs/",
23 "resource://gre/defaults/pref/",
25 // These chrome resources are referenced using relative paths from JS files.
26 "chrome://global/content/certviewer/components/",
28 // https://github.com/mozilla/activity-stream/issues/3053
29 "chrome://activity-stream/content/data/content/tippytop/images/",
30 "chrome://activity-stream/content/data/content/tippytop/favicons/",
31 // These resources are referenced by messages delivered through Remote Settings
32 "chrome://activity-stream/content/data/content/assets/remote/",
33 "chrome://activity-stream/content/data/content/assets/mobile-download-qr-new-user-cn.svg",
34 "chrome://activity-stream/content/data/content/assets/mobile-download-qr-existing-user-cn.svg",
35 "chrome://activity-stream/content/data/content/assets/person-typing.svg",
36 "chrome://browser/content/assets/moz-vpn.svg",
37 "chrome://browser/content/assets/vpn-logo.svg",
38 "chrome://browser/content/assets/focus-promo.png",
39 "chrome://browser/content/assets/klar-qr-code.svg",
41 // toolkit/components/pdfjs/content/build/pdf.js
42 "resource://pdf.js/web/images/",
44 // Exclude the form autofill path that has been moved out of the extensions to
45 // toolkit, see bug 1691821.
46 "resource://gre-resources/autofill/",
47 // Localization file added programatically in FormAutofillUtils.sys.mjs
48 "resource://gre/localization/en-US/toolkit/formautofill",
50 // Exclude all search-extensions because they aren't referenced by filename
51 "resource://search-extensions/",
53 // Exclude all services-automation because they are used through webdriver
54 "resource://gre/modules/services-automation/",
56 // Paths from this folder are constructed in NetErrorParent.sys.mjs based on
57 // the type of cert or net error the user is encountering.
58 "chrome://global/content/neterror/supportpages/",
60 // Points to theme preview images, which are defined in browser/ but only used
61 // in toolkit/mozapps/extensions/content/aboutaddons.js.
62 "resource://usercontext-content/builtin-themes/",
64 // Page data schemas are referenced programmatically.
65 "chrome://browser/content/pagedata/schemas/",
67 // Nimbus schemas are referenced programmatically.
68 "resource://nimbus/schemas/",
70 // Normandy schemas are referenced programmatically.
71 "resource://normandy/schemas/",
73 // ASRouter schemas are referenced programmatically.
74 "chrome://browser/content/asrouter/schemas/",
76 // Localization file added programatically in FeatureCallout.sys.mjs
77 "resource://app/localization/en-US/browser/featureCallout.ftl",
79 // Localization file added programatically in ContentAnalysis.sys.mjs
80 "resource://gre/localization/en-US/toolkit/contentanalysis/",
82 // CSS files are referenced inside JS in an html template
83 "chrome://browser/content/aboutlogins/components/",
85 // Strip on Share parameter lists
86 "chrome://global/content/antitracking/",
89 // These are not part of the omni.ja file, so we find them only when running
90 // the test on a non-packaged build.
91 if (AppConstants.platform == "macosx") {
92 gExceptionPaths.push("resource://gre/res/cursors/");
93 gExceptionPaths.push("resource://gre/res/touchbar/");
96 if (AppConstants.MOZ_BACKGROUNDTASKS) {
97 // These preferences are active only when we're in background task mode.
98 gExceptionPaths.push("resource://gre/defaults/backgroundtasks/");
99 gExceptionPaths.push("resource://app/defaults/backgroundtasks/");
100 // `BackgroundTask_*.sys.mjs` are loaded at runtime by `app --backgroundtask id ...`.
101 gExceptionPaths.push("resource://gre/modules/backgroundtasks/");
102 gExceptionPaths.push("resource://app/modules/backgroundtasks/");
105 if (AppConstants.NIGHTLY_BUILD) {
106 // This is nightly-only debug tool.
107 gExceptionPaths.push(
108 "chrome://browser/content/places/interactionsViewer.html"
112 // Each allowlist entry should have a comment indicating which file is
113 // referencing the listed file in a way that the test can't detect, or a
114 // bug number to remove or use the file if it is indeed currently unreferenced.
116 // security/manager/pki/resources/content/device_manager.js
117 { file: "chrome://pippki/content/load_device.xhtml" },
119 // The l10n build system can't package string files only for some platforms.
120 // See bug 1339424 for why this is hard to fix.
122 file: "chrome://global/locale/fallbackMenubar.properties",
123 platforms: ["linux", "win"],
126 file: "resource://gre/localization/en-US/toolkit/printing/printDialogs.ftl",
127 platforms: ["linux", "macosx"],
130 // This file is referenced by the build system to generate the
131 // Firefox .desktop entry. See bug 1824327 (and perhaps bug 1526672)
133 file: "resource://app/localization/en-US/browser/linuxDesktopEntry.ftl",
136 // toolkit/content/aboutRights-unbranded.xhtml doesn't use aboutRights.css
137 { file: "chrome://global/skin/aboutRights.css", skipUnofficial: true },
139 // devtools/client/inspector/bin/dev-server.js
141 file: "chrome://devtools/content/inspector/markup/markup.xhtml",
142 isFromDevTools: true,
145 // used by devtools/client/memory/index.xhtml
146 { file: "chrome://global/content/third_party/d3/d3.js" },
148 // SpiderMonkey parser API, currently unused in browser/ and toolkit/
149 { file: "resource://gre/modules/reflect.sys.mjs" },
151 // extensions/pref/autoconfig/src/nsReadConfig.cpp
152 { file: "resource://gre/defaults/autoconfig/prefcalls.js" },
154 // browser/components/preferences/moreFromMozilla.js
155 // These files URLs are constructed programatically at run time.
157 file: "chrome://browser/content/preferences/more-from-mozilla-qr-code-simple.svg",
160 file: "chrome://browser/content/preferences/more-from-mozilla-qr-code-simple-cn.svg",
163 { file: "resource://gre/greprefs.js" },
165 // layout/mathml/nsMathMLChar.cpp
166 { file: "resource://gre/res/fonts/mathfontSTIXGeneral.properties" },
167 { file: "resource://gre/res/fonts/mathfontUnicode.properties" },
169 // toolkit/mozapps/extensions/AddonContentPolicy.cpp
170 { file: "resource://gre/localization/en-US/toolkit/global/cspErrors.ftl" },
172 // The l10n build system can't package string files only for some platforms.
174 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/mac/accessible.properties",
175 platforms: ["linux", "win"],
178 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/mac/intl.properties",
179 platforms: ["linux", "win"],
182 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/mac/platformKeys.properties",
183 platforms: ["linux", "win"],
186 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/unix/accessible.properties",
187 platforms: ["macosx", "win"],
190 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/unix/intl.properties",
191 platforms: ["macosx", "win"],
194 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/unix/platformKeys.properties",
195 platforms: ["macosx", "win"],
198 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/win/accessible.properties",
199 platforms: ["linux", "macosx"],
202 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/win/intl.properties",
203 platforms: ["linux", "macosx"],
206 file: "resource://gre/chrome/en-US/locale/en-US/global-platform/win/platformKeys.properties",
207 platforms: ["linux", "macosx"],
210 // Files from upstream library
211 { file: "resource://pdf.js/web/debugger.mjs" },
212 { file: "resource://pdf.js/web/debugger.css" },
214 // Starting from here, files in the allowlist are bugs that need fixing.
215 // Bug 1339424 (wontfix?)
217 file: "chrome://browser/locale/taskbar.properties",
218 platforms: ["linux", "macosx"],
221 { file: "chrome://remote/content/marionette/test_dialog.properties" },
222 { file: "chrome://remote/content/marionette/test_dialog.xhtml" },
223 { file: "chrome://remote/content/marionette/test_menupopup.xhtml" },
224 { file: "chrome://remote/content/marionette/test_no_xul.xhtml" },
225 { file: "chrome://remote/content/marionette/test.xhtml" },
227 { file: "chrome://pippki/content/resetpassword.xhtml" },
229 { file: "resource://gre/modules/Manifest.sys.mjs" },
231 // (The references to these files are dynamically generated, so the test can't
232 // find the references)
234 file: "chrome://devtools/skin/images/aboutdebugging-firefox-aurora.svg",
235 isFromDevTools: true,
238 file: "chrome://devtools/skin/images/aboutdebugging-firefox-beta.svg",
239 isFromDevTools: true,
242 file: "chrome://devtools/skin/images/aboutdebugging-firefox-release.svg",
243 isFromDevTools: true,
245 { file: "chrome://devtools/skin/images/next.svg", isFromDevTools: true },
249 file: "resource://app/localization/en-US/browser/touchbar/touchbar.ftl",
250 platforms: ["linux", "win"],
252 // Referenced by the webcompat system addon for localization
253 { file: "resource://gre/localization/en-US/toolkit/about/aboutCompat.ftl" },
255 // dom/media/mediacontrol/MediaControlService.cpp
256 { file: "resource://gre/localization/en-US/dom/media.ftl" },
258 // dom/xml/nsXMLPrettyPrinter.cpp
259 { file: "resource://gre/localization/en-US/dom/XMLPrettyPrint.ftl" },
261 // tookit/mozapps/update/BackgroundUpdate.sys.mjs
263 file: "resource://gre/localization/en-US/toolkit/updates/backgroundupdate.ftl",
266 // Bug 1713242 - referenced by aboutThirdParty.html which is only for Windows
268 file: "resource://gre/localization/en-US/toolkit/about/aboutThirdParty.ftl",
269 platforms: ["linux", "macosx"],
271 // Bug 1854618 - referenced by aboutWebauthn.html which is only for Linux and Mac
273 file: "resource://gre/localization/en-US/toolkit/about/aboutWebauthn.ftl",
274 platforms: ["win", "android"],
276 // Bug 1973834 - referenced by aboutWindowsMessages.html which is only for Windows
278 file: "resource://gre/localization/en-US/toolkit/about/aboutWindowsMessages.ftl",
279 platforms: ["linux", "macosx"],
282 // (The references to these files are dynamically generated, so the test can't
283 // find the references)
284 { file: "chrome://browser/content/screenshots/copied-notification.svg" },
287 { file: "chrome://global/content/ml/SummarizerModel.sys.mjs" },
290 { file: "chrome://global/content/ml/ModelHub.sys.mjs" },
292 // toolkit/xre/MacRunFromDmgUtils.mm
293 { file: "resource://gre/localization/en-US/toolkit/global/run-from-dmg.ftl" },
295 // Referenced by screenshots extension
296 { file: "chrome://browser/content/screenshots/cancel.svg" },
297 { file: "chrome://browser/content/screenshots/copy.svg" },
298 { file: "chrome://browser/content/screenshots/download.svg" },
299 { file: "chrome://browser/content/screenshots/download-white.svg" },
301 // A debug tool that is only available in Nightly builds, and is accessed
302 // directly by developers via the chrome URI (bug 1888491)
303 { file: "chrome://browser/content/backup/debug.html" },
306 if (AppConstants.platform != "win") {
307 // toolkit/mozapps/defaultagent/Notification.cpp
308 // toolkit/mozapps/defaultagent/ScheduledTask.cpp
309 // toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
310 // Bug 1854425 - referenced by default browser agent which is not detected
312 file: "resource://app/localization/en-US/browser/backgroundtasks/defaultagent.ftl",
315 if (AppConstants.NIGHTLY_BUILD) {
316 // This path is refereneced in nsFxrCommandLineHandler.cpp, which is only
317 // compiled in Windows. This path is allowed so that non-Windows builds
318 // can access the FxR UI via --chrome rather than --fxr (which includes VR-
319 // specific functionality)
320 allowlist.push({ file: "chrome://fxr/content/fxrui.html" });
324 if (AppConstants.platform == "android") {
325 // The l10n build system can't package string files only for some platforms.
326 // Referenced by aboutGlean.html
328 file: "resource://gre/localization/en-US/toolkit/about/aboutGlean.ftl",
332 if (AppConstants.MOZ_UPDATE_AGENT && !AppConstants.MOZ_BACKGROUNDTASKS) {
333 // Task scheduling is only used for background updates right now.
335 file: "resource://gre/modules/TaskScheduler.sys.mjs",
343 "isFromDevTools" in item == isDevtools &&
344 (!item.skipUnofficial || !AppConstants.MOZILLA_OFFICIAL) &&
345 (!item.platforms || item.platforms.includes(AppConstants.platform))
347 .map(item => item.file)
350 const ignorableAllowlist = new Set([
351 // The following files are outside of the omni.ja file, so we only catch them
352 // when testing on a non-packaged build.
354 // toolkit/mozapps/extensions/nsBlocklistService.js
355 "resource://app/blocklist.xml",
357 // dom/media/gmp/GMPParent.cpp
358 "resource://gre/gmp-clearkey/0.1/manifest.json",
360 // Bug 1351669 - obsolete test file
361 "resource://gre/res/test.properties",
363 for (let entry of ignorableAllowlist) {
364 allowlist.add(entry);
368 // services/sync/modules/service.sys.mjs
377 "extension-storage.sys.mjs",
379 allowlist.add("resource://services-sync/engines/" + module);
381 // resource://devtools/shared/worker/loader.js,
382 // resource://devtools/shared/loader/builtin-modules.js
383 if (!AppConstants.ENABLE_WEBDRIVER) {
384 allowlist.add("resource://gre/modules/jsdebugger.sys.mjs");
388 if (AppConstants.MOZ_CODE_COVERAGE) {
390 "chrome://remote/content/marionette/PerTestCoverageUtils.sys.mjs"
394 const gInterestingCategories = new Set([
395 "addon-provider-module",
396 "webextension-modules",
397 "webextension-scripts",
398 "webextension-schemas",
399 "webextension-scripts-addon",
400 "webextension-scripts-content",
401 "webextension-scripts-devtools",
404 var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
407 var gChromeMap = new Map();
408 var gOverrideMap = new Map();
409 var gComponentsSet = new Set();
411 // In this map when the value is a Set of URLs, the file is referenced if any
412 // of the files in the Set is referenced.
413 // When the value is null, the file is referenced unconditionally.
414 // When the value is a string, "allowlist-direct" means that we have not found
415 // any reference in the code, but have a matching allowlist entry for this file.
416 // "allowlist" means that the file is indirectly allowlisted, ie. a allowlisted
417 // file causes this file to be referenced.
418 var gReferencesFromCode = new Map();
420 var resHandler = Services.io
421 .getProtocolHandler("resource")
422 .QueryInterface(Ci.nsIResProtocolHandler);
423 var gResourceMap = [];
424 function trackResourcePrefix(prefix) {
425 let uri = Services.io.newURI("resource://" + prefix + "/");
426 gResourceMap.unshift([prefix, resHandler.resolveURI(uri)]);
428 trackResourcePrefix("gre");
429 trackResourcePrefix("app");
431 function getBaseUriForChromeUri(chromeUri) {
432 let chromeFile = chromeUri + "nonexistentfile.reallynothere";
433 let uri = Services.io.newURI(chromeFile);
434 let fileUri = gChromeReg.convertChromeURL(uri);
435 return fileUri.resolve(".");
438 function trackChromeUri(uri) {
439 gChromeMap.set(getBaseUriForChromeUri(uri), uri);
442 // formautofill registers resource://formautofill/ and
443 // chrome://formautofill/content/ dynamically at runtime.
444 // Bug 1480276 is about addressing this without this hard-coding.
445 trackResourcePrefix("autofill");
446 trackChromeUri("chrome://formautofill/content/");
448 function parseManifest(manifestUri) {
449 return fetchFile(manifestUri.spec).then(data => {
450 for (let line of data.split("\n")) {
451 let [type, ...argv] = line.split(/\s+/);
452 if (type == "content" || type == "skin" || type == "locale") {
453 let chromeUri = `chrome://${argv[0]}/${type}/`;
454 // The webcompat reporter's locale directory may not exist if
455 // the addon is preffed-off, and since it's a hack until we
456 // get bz1425104 landed, we'll just skip it for now.
457 if (chromeUri === "chrome://report-site-issue/locale/") {
458 gChromeMap.set("chrome://report-site-issue/locale/", true);
460 trackChromeUri(chromeUri);
462 } else if (type == "override" || type == "overlay") {
463 // Overlays aren't really overrides, but behave the same in
464 // that the overlay is only referenced if the original xul
465 // file is referenced somewhere.
466 let os = "os=" + Services.appinfo.OS;
467 if (!argv.some(s => s.startsWith("os=") && s != os)) {
469 Services.io.newURI(argv[1]).specIgnoringRef,
470 Services.io.newURI(argv[0]).specIgnoringRef
473 } else if (type == "category" && gInterestingCategories.has(argv[0])) {
474 gReferencesFromCode.set(argv[2], null);
475 } else if (type == "resource") {
476 trackResourcePrefix(argv[0]);
477 } else if (type == "component") {
478 gComponentsSet.add(argv[1]);
484 // If the given URI is a webextension manifest, extract files used by
485 // any of its APIs (scripts, icons, style sheets, theme images).
486 // Returns the passed in URI if the manifest is not a webextension
487 // manifest, null otherwise.
488 async function parseJsonManifest(uri) {
489 uri = Services.io.newURI(convertToCodeURI(uri.spec));
491 let raw = await fetchFile(uri.spec);
494 data = JSON.parse(raw);
499 // Simplistic test for whether this is a webextension manifest:
500 if (data.manifest_version !== 2) {
504 if (data.background?.scripts) {
505 for (let bgscript of data.background.scripts) {
506 gReferencesFromCode.set(uri.resolve(bgscript), null);
511 for (let icon of Object.values(data.icons)) {
512 gReferencesFromCode.set(uri.resolve(icon), null);
516 if (data.experiment_apis) {
517 for (let api of Object.values(data.experiment_apis)) {
518 if (api.parent && api.parent.script) {
519 let script = uri.resolve(api.parent.script);
520 gReferencesFromCode.set(script, null);
524 gReferencesFromCode.set(uri.resolve(api.schema), null);
529 if (data.theme_experiment && data.theme_experiment.stylesheet) {
530 let stylesheet = uri.resolve(data.theme_experiment.stylesheet);
531 gReferencesFromCode.set(stylesheet, null);
534 for (let themeKey of ["theme", "dark_theme"]) {
535 if (data?.[themeKey]?.images?.additional_backgrounds) {
536 for (let background of data[themeKey].images.additional_backgrounds) {
537 gReferencesFromCode.set(uri.resolve(background), null);
545 function addCodeReference(url, fromURI) {
546 let from = convertToCodeURI(fromURI.spec);
548 // Ignore self references.
554 if (gReferencesFromCode.has(url)) {
555 ref = gReferencesFromCode.get(url);
561 gReferencesFromCode.set(url, ref);
566 function listCodeReferences(refs) {
569 for (let ref of refs) {
573 return refList.join(",");
576 function parseCSSFile(fileUri) {
577 return fetchFile(fileUri.spec).then(data => {
578 for (let line of data.split("\n")) {
579 let urls = line.match(/url\([^()]+\)/g);
581 // @import rules can take a string instead of a url.
582 let importMatch = line.match(/@import ['"]?([^'"]*)['"]?/);
583 if (importMatch && importMatch[1]) {
584 let url = Services.io.newURI(importMatch[1], null, fileUri).spec;
585 addCodeReference(convertToCodeURI(url), fileUri);
590 for (let url of urls) {
591 // Remove the url(" prefix and the ") suffix.
593 .replace(/url\(([^)]*)\)/, "$1")
594 .replace(/^"(.*)"$/, "$1")
595 .replace(/^'(.*)'$/, "$1");
596 if (url.startsWith("data:")) {
601 url = Services.io.newURI(url, null, fileUri).specIgnoringRef;
602 addCodeReference(convertToCodeURI(url), fileUri);
604 ok(false, "unexpected error while resolving this URI: " + url);
611 function parseCodeFile(fileUri) {
612 return fetchFile(fileUri.spec).then(data => {
614 for (let line of data.split("\n")) {
615 let urls = line.match(
616 /["'`]chrome:\/\/[a-zA-Z0-9-]+\/(content|skin|locale)\/[^"'` ]*["'`]/g
620 urls = line.match(/["']resource:\/\/[^"']+["']/g);
624 /baseURI: "resource:\/\/devtools\//.test(line)
626 baseUri = Services.io.newURI(urls[0].slice(1, -1));
632 urls = line.match(/[a-z0-9_\/-]+\.ftl/i);
635 let grePrefix = Services.io.newURI(
636 "resource://gre/localization/en-US/"
638 let appPrefix = Services.io.newURI(
639 "resource://app/localization/en-US/"
642 let grePrefixUrl = Services.io.newURI(urls, null, grePrefix).spec;
643 let appPrefixUrl = Services.io.newURI(urls, null, appPrefix).spec;
645 addCodeReference(grePrefixUrl, fileUri);
646 addCodeReference(appPrefixUrl, fileUri);
652 // If there's no absolute chrome URL, look for relative ones in
653 // src and href attributes.
654 let match = line.match("(?:src|href)=[\"']([^$&\"']+)");
655 if (match && match[1]) {
656 let url = Services.io.newURI(match[1], null, fileUri).spec;
657 addCodeReference(convertToCodeURI(url), fileUri);
660 // This handles `import` lines which may be multi-line.
661 // We have an ESLint rule, `import/no-unassigned-import` which prevents
662 // using bare `import "foo.js"`, so we don't need to handle that case
664 match = line.match(/from\W*['"](.*?)['"]/);
667 url = Services.io.newURI(url, null, baseUri || fileUri).spec;
668 url = convertToCodeURI(url);
669 addCodeReference(url, fileUri);
674 ["devtools/client/locales", "chrome://devtools/locale"],
675 ["devtools/shared/locales", "chrome://devtools-shared/locale"],
677 "devtools/shared/platform",
678 "resource://devtools/shared/platform/chrome",
680 ["devtools", "resource://devtools"],
683 match = line.match(/["']((?:devtools)\/[^\\#"']+)["']/);
684 if (match && match[1]) {
686 for (let rule of rules) {
687 if (path.startsWith(rule[0] + "/")) {
688 path = path.replace(rule[0], rule[1]);
689 if (!/\.(properties|js|jsm|mjs|json|css)$/.test(path)) {
692 addCodeReference(path, fileUri);
698 match = line.match(/require\(['"](\.[^'"]+)['"]\)/);
699 if (match && match[1]) {
701 url = Services.io.newURI(url, null, baseUri || fileUri).spec;
702 url = convertToCodeURI(url);
703 if (!/\.(properties|js|jsm|mjs|json|css)$/.test(url)) {
706 if (url.startsWith("resource://")) {
707 addCodeReference(url, fileUri);
709 // if we end up with a chrome:// url here, it's likely because
710 // a baseURI to a resource:// path has been defined in another
711 // .js file that is loaded in the same scope, we can't detect it.
718 for (let url of urls) {
720 url = url.slice(1, -1);
721 // Remove ? or \ trailing characters.
722 if (url.endsWith("\\")) {
723 url = url.slice(0, -1);
726 let pos = url.indexOf("?");
728 url = url.slice(0, pos);
731 // Make urls like chrome://browser/skin/ point to an actual file,
732 // and remove the ref if any.
734 url = Services.io.newURI(url).specIgnoringRef;
741 line.includes("require(") &&
742 !/\.(properties|js|jsm|mjs|json|css)$/.test(url)
747 addCodeReference(url, fileUri);
753 function convertToCodeURI(fileUri) {
754 let baseUri = fileUri;
757 let slashPos = baseUri.lastIndexOf("/", baseUri.length - 2);
759 // File not accessible from chrome protocol, try resource://
760 for (let res of gResourceMap) {
761 if (fileUri.startsWith(res[1])) {
762 return fileUri.replace(res[1], "resource://" + res[0] + "/");
765 // Give up and return the original URL.
768 path = baseUri.slice(slashPos + 1) + path;
769 baseUri = baseUri.slice(0, slashPos + 1);
770 if (gChromeMap.has(baseUri)) {
771 return gChromeMap.get(baseUri) + path;
776 async function chromeFileExists(aURI) {
778 return await PerfTestHelpers.checkURIExists(aURI);
780 todo(false, `Failed to check if ${aURI} exists: ${e}`);
785 function findChromeUrlsFromArray(array, prefix) {
786 // Find the first character of the prefix...
789 (index = array.indexOf(prefix.charCodeAt(0), index)) != -1;
792 // Then ensure we actually have the whole prefix.
794 for (let i = 1; i < prefix.length; ++i) {
795 if (array[index + i] != prefix.charCodeAt(i)) {
804 // C strings are null terminated, but " also terminates urls
805 // (nsIndexedToHTML.cpp contains an HTML fragment with several chrome urls)
806 // Let's also terminate the string on the # character to skip references.
808 array.indexOf(0, index),
809 array.indexOf('"'.charCodeAt(0), index),
810 array.indexOf(")".charCodeAt(0), index),
811 array.indexOf("#".charCodeAt(0), index)
814 for (; index < end; ++index) {
815 string += String.fromCharCode(array[index]);
818 // Only keep strings that look like real chrome or resource urls.
820 /chrome:\/\/[a-zA-Z09-]+\/(content|skin|locale)\//.test(string) ||
821 /resource:\/\/[a-zA-Z09-]*\/.*\.[a-z]+/.test(string)
823 gReferencesFromCode.set(string, null);
828 add_task(async function checkAllTheFiles() {
829 TestUtils.assertPackagedBuild();
831 const libxul = await IOUtils.read(PathUtils.xulLibraryPath);
832 findChromeUrlsFromArray(libxul, "chrome://");
833 findChromeUrlsFromArray(libxul, "resource://");
834 // Handle NS_LITERAL_STRING.
835 let uint16 = new Uint16Array(libxul.buffer);
836 findChromeUrlsFromArray(uint16, "chrome://");
837 findChromeUrlsFromArray(uint16, "resource://");
839 const kCodeExtensions = [
850 let appDir = Services.dirsvc.get("GreD", Ci.nsIFile);
851 // This asynchronously produces a list of URLs (sadly, mostly sync on our
852 // test infrastructure because it runs against jarfiles there, and
853 // our zipreader APIs are all sync)
854 let uris = await generateURIsFromDirTree(
866 ].concat(kCodeExtensions)
869 // Parse and remove all manifests from the list.
870 // NOTE that this must be done before filtering out devtools paths
871 // so that all chrome paths can be recorded.
872 let manifestURIs = [];
873 let jsonManifests = [];
874 uris = uris.filter(uri => {
875 let path = uri.pathQueryRef;
876 if (path.endsWith(".manifest")) {
877 manifestURIs.push(uri);
879 } else if (path.endsWith("/manifest.json")) {
880 jsonManifests.push(uri);
887 // Wait for all manifest to be parsed
888 await PerfTestHelpers.throttledMapPromises(manifestURIs, parseManifest);
890 for (let esModule of Components.manager.getComponentESModules()) {
891 gReferencesFromCode.set(esModule, null);
894 // manifest.json is a common name, it is used for WebExtension manifests
895 // but also for other things. To tell them apart, we have to actually
896 // read the contents. This will populate gExtensionRoots with all
897 // embedded extension APIs, and return any manifest.json files that aren't
899 let nonWebextManifests = (
900 await Promise.all(jsonManifests.map(parseJsonManifest))
901 ).filter(uri => !!uri);
902 uris.push(...nonWebextManifests);
904 // We build a list of promises that get resolved when their respective
905 // files have loaded and produced no errors.
906 let allPromises = [];
908 for (let uri of uris) {
909 let path = uri.pathQueryRef;
910 if (path.endsWith(".css")) {
911 allPromises.push([parseCSSFile, uri]);
912 } else if (kCodeExtensions.some(ext => path.endsWith(ext))) {
913 allPromises.push([parseCodeFile, uri]);
917 // Wait for all the files to have actually loaded:
918 await PerfTestHelpers.throttledMapPromises(allPromises, ([task, uri]) =>
922 // Keep only chrome:// files, and filter out either the devtools paths or
923 // the non-devtools paths:
924 let devtoolsPrefixes = [
926 "resource://devtools/",
927 "resource://devtools-shared-images/",
928 "resource://devtools-highlighter-styles/",
929 "resource://app/modules/devtools",
930 "resource://gre/modules/devtools",
931 "resource://app/localization/en-US/startup/aboutDevTools.ftl",
932 "resource://app/localization/en-US/devtools/",
934 let hasDevtoolsPrefix = uri =>
935 devtoolsPrefixes.some(prefix => uri.startsWith(prefix));
936 let chromeFiles = [];
937 for (let uri of uris) {
938 uri = convertToCodeURI(uri.spec);
940 (uri.startsWith("chrome://") || uri.startsWith("resource://")) &&
941 isDevtools == hasDevtoolsPrefix(uri)
943 chromeFiles.push(uri);
948 // chrome://devtools/skin/devtools-browser.css is included from browser.xhtml
949 gReferencesFromCode.set(AppConstants.BROWSER_CHROME_URL, null);
950 // devtools' css is currently included from browser.css, see bug 1204810.
951 gReferencesFromCode.set("chrome://browser/skin/browser.css", null);
954 let isUnreferenced = file => {
955 if (gExceptionPaths.some(e => file.startsWith(e))) {
958 if (gReferencesFromCode.has(file)) {
959 let refs = gReferencesFromCode.get(file);
963 for (let ref of refs) {
966 ref.startsWith("resource://app/components/") ||
967 (file.startsWith("chrome://") && ref.startsWith("resource://"))
973 if (gReferencesFromCode.has(ref)) {
974 let refType = gReferencesFromCode.get(ref);
976 refType === null || // unconditionally referenced
977 refType == "allowlist" ||
978 refType == "allowlist-direct"
985 return !gOverrideMap.has(file) || isUnreferenced(gOverrideMap.get(file));
988 let unreferencedFiles = chromeFiles;
990 let removeReferenced = useAllowlist => {
991 let foundReference = false;
992 unreferencedFiles = unreferencedFiles.filter(f => {
993 let rv = isUnreferenced(f);
994 if (rv && f.startsWith("resource://app/")) {
995 rv = isUnreferenced(f.replace("resource://app/", "resource:///"));
997 if (rv && /^resource:\/\/(?:app|gre)\/components\/[^/]+\.js$/.test(f)) {
998 rv = !gComponentsSet.has(f.replace(/.*\//, ""));
1001 foundReference = true;
1004 "indirectly allowlisted file: " +
1007 listCodeReferences(gReferencesFromCode.get(f))
1010 gReferencesFromCode.set(f, useAllowlist ? "allowlist" : null);
1014 return foundReference;
1016 // First filter out the files that are referenced.
1017 while (removeReferenced(false)) {
1018 // As long as removeReferenced returns true, some files have been marked
1019 // as referenced, so we need to run it again.
1021 // Marked as referenced the files that have been explicitly allowed.
1022 unreferencedFiles = unreferencedFiles.filter(file => {
1023 if (allowlist.has(file)) {
1024 allowlist.delete(file);
1025 gReferencesFromCode.set(file, "allowlist-direct");
1030 // Run the process again, this time when more files are marked as referenced,
1031 // it's a consequence of the allowlist.
1032 while (removeReferenced(true)) {
1033 // As long as removeReferenced returns true, we need to run it again.
1036 unreferencedFiles.sort();
1039 // Bug 1351878 - handle devtools resource files
1040 unreferencedFiles = unreferencedFiles.filter(file => {
1041 if (file.startsWith("resource://")) {
1042 info("unreferenced devtools resource file: " + file);
1049 is(unreferencedFiles.length, 0, "there should be no unreferenced files");
1050 for (let file of unreferencedFiles) {
1051 let refs = gReferencesFromCode.get(file);
1052 if (refs === undefined) {
1053 ok(false, "unreferenced file: " + file);
1055 let refList = listCodeReferences(refs);
1056 let msg = "file only referenced from unreferenced files: " + file;
1058 msg += " referenced from " + refList;
1064 for (let file of allowlist) {
1065 if (ignorableAllowlist.has(file)) {
1066 info("ignored unused allowlist entry: " + file);
1068 ok(false, "unused allowlist entry: " + file);
1072 for (let [file, refs] of gReferencesFromCode) {
1074 isDevtools != devtoolsPrefixes.some(prefix => file.startsWith(prefix))
1080 (file.startsWith("chrome://") || file.startsWith("resource://")) &&
1081 !(await chromeFileExists(file))
1083 // Ignore chrome prefixes that have been automatically expanded.
1085 file.match("chrome://([^/]+)/content/([^/.]+).xul") ||
1086 file.match("chrome://([^/]+)/skin/([^/.]+).css");
1087 if (pathParts && pathParts[1] == pathParts[2]) {
1091 // TODO: bug 1349010 - add a allowlist and make this reliable enough
1092 // that we could make the test fail when this catches something new.
1093 let refList = listCodeReferences(refs);
1094 let msg = "missing file: " + file;
1096 msg += " referenced from " + refList;