[globals] self.Runtime.cachedResources

This refactors away the cachedResources from an object map
to a normal Map as defined in `Runtime.js`. The Map is used during
boot time by setting the relevant stylesheet contents, which is
included by `build_release_applications`. Moreover, it moves the
`*_module.js` files into the `modules` array rather than scripts.
This ensures that `*_module.js` files can use ES imports.

In a follow-up CL, we can do additional cleanup in the Runtime
to stop retrieving `resources` in `_loadResources`, as that should
no longer be possible. (In both debug and non-debug we build the
appropriate `_module.js` files)

R=aerotwist@chromium.org,aerotwist@chromium.org

Bug: 1058320

Change-Id: I89602b332360338f5914038f6cd505f75b531f8e
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2398829
Reviewed-by: Paul Lewis <aerotwist@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org>
diff --git a/front_end/RuntimeInstantiator.js b/front_end/RuntimeInstantiator.js
index a8a7407..49cca5d 100644
--- a/front_end/RuntimeInstantiator.js
+++ b/front_end/RuntimeInstantiator.js
@@ -37,13 +37,6 @@
 self.Runtime = self.Runtime || {};
 Runtime = Runtime || {};
 
-/**
- * @type {!Object.<string, string>}
- */
-self.Runtime.cachedResources = {
-  __proto__: null
-};
-
 self.Root = self.Root || {};
 Root = Root || {};
 
diff --git a/front_end/bindings_test_runner/BindingsTestRunner.js b/front_end/bindings_test_runner/BindingsTestRunner.js
index 8e4e3fb..3ee6252 100644
--- a/front_end/bindings_test_runner/BindingsTestRunner.js
+++ b/front_end/bindings_test_runner/BindingsTestRunner.js
@@ -46,9 +46,6 @@
         isAdded[index++] = true;
       }
     }
-
-    const addedEntries = diff.filter(entry => entry[0] === Diff.Diff.Operation.Insert).map(entry => entry[1]);
-    addedLines = [].concat.apply([], addedEntries);
   }
 
   TestRunner.addResult(`Removed: ${removedLines.length} uiSourceCodes`);
diff --git a/front_end/component_helpers/component-server-setup.ts b/front_end/component_helpers/component-server-setup.ts
index 99d8338..236d651 100644
--- a/front_end/component_helpers/component-server-setup.ts
+++ b/front_end/component_helpers/component-server-setup.ts
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import * as Root from '../root/root.js';
+
 import {CSS_RESOURCES_TO_LOAD_INTO_RUNTIME} from './get-stylesheet.js';
 
 /**
@@ -9,15 +11,9 @@
  * only populating the runtime CSS cache but may be extended in the future.
  */
 export async function setup() {
-  if (self.Runtime) {
-    console.error('poulateRuntimeCacheWithStylesheets found existing Runtime, refusing to overwrite it.');
-  }
-
-  self.Runtime = {cachedResources: {}};
-
   const allPromises = CSS_RESOURCES_TO_LOAD_INTO_RUNTIME.map(resourcePath => {
     return fetch('/' + resourcePath).then(response => response.text()).then(cssText => {
-      self.Runtime.cachedResources[resourcePath] = cssText;
+      Root.Runtime.cachedResources.set(resourcePath, cssText);
     });
   });
 
diff --git a/front_end/component_helpers/get-stylesheet.ts b/front_end/component_helpers/get-stylesheet.ts
index e1e9705..54e520d 100644
--- a/front_end/component_helpers/get-stylesheet.ts
+++ b/front_end/component_helpers/get-stylesheet.ts
@@ -22,11 +22,7 @@
     return cachedResult.sheets;
   }
 
-  if (!self.Runtime) {
-    return [];
-  }
-
-  const content = self.Runtime.cachedResources[path] || '';
+  const content = Root.Runtime.cachedResources.get(path) || '';
   if (!content) {
     throw new Error(`${path} not preloaded.`);
   }
diff --git a/front_end/elements_test_runner/ElementsTestRunner.js b/front_end/elements_test_runner/ElementsTestRunner.js
index 41d9786..bab6f0a 100644
--- a/front_end/elements_test_runner/ElementsTestRunner.js
+++ b/front_end/elements_test_runner/ElementsTestRunner.js
@@ -267,7 +267,7 @@
   return result;
 };
 
-function waitForStylesRebuild(matchFunction, callback, requireRebuild) {
+globalThis.waitForStylesRebuild = function(matchFunction, callback, requireRebuild) {
   (function sniff(node, rebuild) {
     if ((rebuild || !requireRebuild) && node && matchFunction(node)) {
       callback();
@@ -276,7 +276,7 @@
 
     TestRunner.addSniffer(Elements.StylesSidebarPane.prototype, '_nodeStylesUpdatedForTest', sniff);
   })(null);
-}
+};
 
 ElementsTestRunner.waitForStyles = function(idValue, callback, requireRebuild) {
   callback = TestRunner.safeWrap(callback);
@@ -285,7 +285,7 @@
     return node.getAttribute('id') === idValue;
   }
 
-  waitForStylesRebuild(nodeWithId, callback, requireRebuild);
+  globalThis.waitForStylesRebuild(nodeWithId, callback, requireRebuild);
 };
 
 ElementsTestRunner.waitForStyleCommitted = function(next) {
@@ -302,7 +302,7 @@
     return classAttr && classAttr.indexOf(classValue) > -1;
   }
 
-  waitForStylesRebuild(nodeWithClass, callback, requireRebuild);
+  globalThis.waitForStylesRebuild(nodeWithClass, callback, requireRebuild);
 };
 
 ElementsTestRunner.waitForSelectorCommitted = function(callback) {
@@ -343,7 +343,7 @@
 ElementsTestRunner.selectPseudoElementAndWaitForStyles = function(parentId, pseudoType, callback) {
   callback = TestRunner.safeWrap(callback);
   let targetNode;
-  waitForStylesRebuild(isPseudoElement, stylesUpdated, true);
+  globalThis.waitForStylesRebuild(isPseudoElement, stylesUpdated, true);
   ElementsTestRunner.findNode(isPseudoElement, nodeFound);
 
   function nodeFound(node) {
diff --git a/front_end/issues/MarkdownIssueDescription.js b/front_end/issues/MarkdownIssueDescription.js
index d4cbe53..04b3a5a 100644
--- a/front_end/issues/MarkdownIssueDescription.js
+++ b/front_end/issues/MarkdownIssueDescription.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import * as Marked from '../marked/marked.js';
+import * as Root from '../root/root.js';
 import * as SDK from '../sdk/sdk.js';
 
 import {createMarkdownView} from './MarkdownView_bridge.js';
@@ -21,7 +22,7 @@
  * @return {string}
  */
 function getMarkdownFileContent(filename) {
-  const rawMarkdown = self.Runtime.cachedResources[filename];
+  const rawMarkdown = Root.Runtime.cachedResources.get(filename);
   if (!rawMarkdown) {
     throw new Error(`Markdown file ${filename} not found. Declare it as a resource in the module.json file`);
   }
diff --git a/front_end/legacy/legacy-defs.d.ts b/front_end/legacy/legacy-defs.d.ts
index 74b0d4e..e4a5e5f 100644
--- a/front_end/legacy/legacy-defs.d.ts
+++ b/front_end/legacy/legacy-defs.d.ts
@@ -45,10 +45,6 @@
 
 declare let ls: (template: ITemplateArray, ...args: any[]) => string;
 
-declare namespace Runtime {
-  const cachedResources: {[cachePath: string]: string};
-}
-
 declare class AnchorBox {
   x: number;
   y: number;
diff --git a/front_end/lighthouse/LighthousePanel.js b/front_end/lighthouse/LighthousePanel.js
index 491e8b3..9a92ac2 100644
--- a/front_end/lighthouse/LighthousePanel.js
+++ b/front_end/lighthouse/LighthousePanel.js
@@ -8,6 +8,7 @@
 import * as Common from '../common/common.js';
 import * as Emulation from '../emulation/emulation.js';  // eslint-disable-line no-unused-vars
 import * as HostModule from '../host/host.js';
+import * as Root from '../root/root.js';
 import * as SDK from '../sdk/sdk.js';
 import * as UI from '../ui/ui.js';
 
@@ -198,7 +199,7 @@
     const dom = new DOM(/** @type {!Document} */ (this._auditResultsElement.ownerDocument));
     const renderer = new LighthouseReportRenderer(dom);
 
-    const templatesHTML = self.Runtime.cachedResources['third_party/lighthouse/report-assets/templates.html'];
+    const templatesHTML = Root.Runtime.cachedResources.get('third_party/lighthouse/report-assets/templates.html');
     const templatesDOM = new DOMParser().parseFromString(templatesHTML, 'text/html');
     if (!templatesDOM) {
       return;
diff --git a/front_end/lighthouse/LighthouseReportRenderer.js b/front_end/lighthouse/LighthouseReportRenderer.js
index 5d2d95f..62a53a7 100644
--- a/front_end/lighthouse/LighthouseReportRenderer.js
+++ b/front_end/lighthouse/LighthouseReportRenderer.js
@@ -9,6 +9,7 @@
 import * as Components from '../components/components.js';
 import * as HostModule from '../host/host.js';
 import * as Platform from '../platform/platform.js';
+import * as Root from '../root/root.js';
 import * as SDK from '../sdk/sdk.js';
 import * as ThemeSupport from '../theme_support/theme_support.js';
 import * as Timeline from '../timeline/timeline.js';
@@ -172,7 +173,7 @@
     const clonedReport = document.querySelector('.lh-root').cloneNode(true /* deep */);
     const printWindow = window.open('', '_blank', 'channelmode=1,status=1,resizable=1');
     const style = printWindow.document.createElement('style');
-    style.textContent = self.Runtime.cachedResources['third_party/lighthouse/report-assets/report.css'];
+    style.textContent = Root.Runtime.cachedResources.get('third_party/lighthouse/report-assets/report.css');
     printWindow.document.head.appendChild(style);
     printWindow.document.body.replaceWith(clonedReport);
     // Linkified nodes are shadow elements, which aren't exposed via `cloneNode`.
diff --git a/front_end/lighthouse_worker/LighthouseService.js b/front_end/lighthouse_worker/LighthouseService.js
index 2402d10..ddfec4c 100644
--- a/front_end/lighthouse_worker/LighthouseService.js
+++ b/front_end/lighthouse_worker/LighthouseService.js
@@ -163,11 +163,11 @@
 }
 
 // Make lighthouse and traceviewer happy.
-global = self;
-global.isVinn = true;
-global.document = {};
-global.document.documentElement = {};
-global.document.documentElement.style = {
+globalThis.global = self;
+globalThis.global.isVinn = true;
+globalThis.global.document = {};
+globalThis.global.document.documentElement = {};
+globalThis.global.document.documentElement.style = {
   WebkitAppearance: 'WebkitAppearance'
 };
-global.LighthouseService = LighthouseService;
+globalThis.global.LighthouseService = LighthouseService;
diff --git a/front_end/lighthouse_worker/module.json b/front_end/lighthouse_worker/module.json
index 037e542..38c3b9e 100644
--- a/front_end/lighthouse_worker/module.json
+++ b/front_end/lighthouse_worker/module.json
@@ -10,8 +10,8 @@
     }
   ],
   "scripts": [
-    "../third_party/lighthouse/lighthouse-dt-bundle.js",
-    "LighthouseService.js"
+    "LighthouseService.js",
+    "../third_party/lighthouse/lighthouse-dt-bundle.js"
   ],
   "skip_compilation": [
     "../third_party/lighthouse/lighthouse-dt-bundle.js"
diff --git a/front_end/root/Runtime.js b/front_end/root/Runtime.js
index 761d15a..c8142f0 100644
--- a/front_end/root/Runtime.js
+++ b/front_end/root/Runtime.js
@@ -601,7 +601,7 @@
    */
   resource(name) {
     const fullName = this._name + '/' + name;
-    const content = self.Runtime.cachedResources[fullName];
+    const content = cachedResources.get(fullName);
     if (!content) {
       throw new Error(fullName + ' not preloaded. Check module.json');
     }
@@ -642,7 +642,7 @@
    * @return {!Promise.<void>}
    * @this {Module}
    */
-  _loadResources() {
+  async _loadResources() {
     const resources = this._descriptor['resources'];
     if (!resources || !resources.length) {
       return Promise.resolve();
@@ -656,9 +656,9 @@
     return Promise.all(promises).then(undefined);
   }
 
-  _loadModules() {
+  async _loadModules() {
     if (!this._descriptor.modules || !this._descriptor.modules.length) {
-      return Promise.resolve();
+      return;
     }
 
     const namespace = this._computeNamespace();
@@ -666,10 +666,27 @@
     self[namespace] = self[namespace] || {};
 
     const legacyFileName = `${this._name}-legacy.js`;
-    const fileName = this._descriptor.modules.includes(legacyFileName) ? legacyFileName : `${this._name}.js`;
+    const moduleFileName = `${this._name}_module.js`;
+    const entrypointFileName = `${this._name}.js`;
+
+    // If a module has resources, they are part of the `_module.js` files that are generated
+    // by `build_release_applications`. These need to be loaded before any other code is
+    // loaded, to make sure that the resource content is properly cached in `cachedResources`.
+    if (this._descriptor.modules.includes(moduleFileName)) {
+      // TODO(crbug.com/1011811): Remove eval when we use TypeScript which does support dynamic imports
+      await eval(`import('../${this._name}/${moduleFileName}')`);
+    }
+
+    // All `*_test_runner` directories don't necessarily have an entrypoint
+    // These runners are typically included in the `_module.js` file already.
+    if (!this._descriptor.modules.includes(entrypointFileName)) {
+      return;
+    }
+
+    const fileName = this._descriptor.modules.includes(legacyFileName) ? legacyFileName : entrypointFileName;
 
     // TODO(crbug.com/1011811): Remove eval when we use TypeScript which does support dynamic imports
-    return eval(`import('../${this._name}/${fileName}')`);
+    await eval(`import('../${this._name}/${fileName}')`);
   }
 
   /**
@@ -1176,7 +1193,7 @@
       return;
     }
     const sourceURL = appendSourceURL ? Runtime.resolveSourceURL(path) : '';
-    self.Runtime.cachedResources[path] = content + sourceURL;
+    cachedResources.set(path, content + sourceURL);
   }
 }
 
@@ -1222,3 +1239,13 @@
 
 // This must be constructed after the query parameters have been parsed.
 export const experiments = new ExperimentsSupport();
+
+/**
+ * @type {!Map<string, string>}
+ */
+export const cachedResources = new Map();
+
+// Only exported for LightHouse, which uses it in `report-generator.js`.
+// Do not use this global in DevTools' implementation.
+// TODO(crbug.com/1127292): remove this global
+globalThis.EXPORTED_CACHED_RESOURCES_ONLY_FOR_LIGHTHOUSE = cachedResources;
diff --git a/front_end/sdk_test_runner/PageMockTestRunner.js b/front_end/sdk_test_runner/PageMockTestRunner.js
index 2e5ab1e..74fbc7c 100644
--- a/front_end/sdk_test_runner/PageMockTestRunner.js
+++ b/front_end/sdk_test_runner/PageMockTestRunner.js
@@ -264,7 +264,7 @@
   }
 };
 
-MockPageConnection = class {
+class MockPageConnection {
   constructor(page) {
     this._page = page;
   }
@@ -292,4 +292,4 @@
     this._onMessage = null;
     return Promise.resolve();
   }
-};
+}
diff --git a/front_end/sources_test_runner/SearchTestRunner.js b/front_end/sources_test_runner/SearchTestRunner.js
index 753482f..b48900a 100644
--- a/front_end/sources_test_runner/SearchTestRunner.js
+++ b/front_end/sources_test_runner/SearchTestRunner.js
@@ -92,7 +92,7 @@
   const modifiersString = (modifiers.length ? ' (' + modifiers.join(', ') + ')' : '');
   TestRunner.addResult(
       'Running replace test for /' + searchConfig.query + '/' + replacement + '/ ' + modifiersString + ':');
-  editor = sourceFrame._textEditor;
+  const editor = sourceFrame._textEditor;
   const oldLines = [];
 
   for (let i = 0; i < editor.linesCount; ++i) {
diff --git a/front_end/theme_support/theme_support_impl.js b/front_end/theme_support/theme_support_impl.js
index 6d3444e..1d8e228 100644
--- a/front_end/theme_support/theme_support_impl.js
+++ b/front_end/theme_support/theme_support_impl.js
@@ -34,6 +34,7 @@
 
 import * as Common from '../common/common.js';
 import * as Platform from '../platform/platform.js';
+import * as Root from '../root/root.js';
 
 /**
  * @type {!ThemeSupport}
@@ -132,7 +133,7 @@
    * @suppressGlobalPropertiesCheck
    */
   _appendStyle(node, cssFile) {
-    const content = self.Runtime.cachedResources[cssFile] || '';
+    const content = Root.Runtime.cachedResources.get(cssFile) || '';
     if (!content) {
       console.error(cssFile + ' not preloaded. Check module.json');
     }
@@ -143,7 +144,7 @@
     const themeStyleSheet = ThemeSupport.instance().themeStyleSheet(cssFile, content);
     if (themeStyleSheet) {
       styleElement = createElement('style');
-      styleElement.textContent = themeStyleSheet + '\n' + Root.Runtime.resolveSourceURL(cssFile + '.theme');
+      styleElement.textContent = themeStyleSheet + '\n' + Root.Runtime.Runtime.resolveSourceURL(cssFile + '.theme');
       node.appendChild(styleElement);
     }
   }
diff --git a/front_end/third_party/lighthouse/lighthouse-dt-bundle.js b/front_end/third_party/lighthouse/lighthouse-dt-bundle.js
index b69345a..4b2f682 100644
--- a/front_end/third_party/lighthouse/lighthouse-dt-bundle.js
+++ b/front_end/third_party/lighthouse/lighthouse-dt-bundle.js
@@ -1,6 +1,6 @@
 // lighthouse, browserified. 6.3.0 (c2b7fcb99cf83fe4088f2ce5d40f6ae5724233d6)
 // @ts-nocheck
-require=function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a;}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r);},p,p.exports,r,e,n,t);}return n[i].exports;}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o;}return r;}()({"../audits/accessibility/accesskeys":[function(require,module,exports){
+globalThis.require=function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a;}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r);},p,p.exports,r,e,n,t);}return n[i].exports;}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o;}return r;}()({"../audits/accessibility/accesskeys":[function(require,module,exports){
 (function(__filename){
 
 
diff --git a/front_end/third_party/lighthouse/report-assets/report-generator.js b/front_end/third_party/lighthouse/report-assets/report-generator.js
index 5d3ad32..b51cf6e 100644
--- a/front_end/third_party/lighthouse/report-assets/report-generator.js
+++ b/front_end/third_party/lighthouse/report-assets/report-generator.js
@@ -15,7 +15,7 @@
 /* global Runtime */
 
 // @ts-expect-error: Runtime.cachedResources exists in Devtools. https://source.chromium.org/chromium/chromium/src/+/master:third_party/devtools-frontend/src/front_end/root/Runtime.js;l=1169
-const cachedResources = Runtime.cachedResources;
+const cachedResources = globalThis.EXPORTED_CACHED_RESOURCES_ONLY_FOR_LIGHTHOUSE;
 
 // Getters are necessary because the DevTools bundling processes
 // resources after this module is resolved. These properties are not
@@ -23,16 +23,16 @@
 // is going to be OK.
 module.exports = {
   get REPORT_CSS() {
-    return cachedResources['third_party/lighthouse/report-assets/report.css'];
+    return cachedResources.get('third_party/lighthouse/report-assets/report.css');
   },
   get REPORT_JAVASCRIPT() {
-    return cachedResources['third_party/lighthouse/report-assets/report.js'];
+    return cachedResources.get('third_party/lighthouse/report-assets/report.js');
   },
   get REPORT_TEMPLATE() {
-    return cachedResources['third_party/lighthouse/report-assets/template.html'];
+    return cachedResources.get('third_party/lighthouse/report-assets/template.html');
   },
   get REPORT_TEMPLATES() {
-    return cachedResources['third_party/lighthouse/report-assets/templates.html'];
+    return cachedResources.get('third_party/lighthouse/report-assets/templates.html');
   },
 };
 
diff --git a/front_end/ui/utils/BUILD.gn b/front_end/ui/utils/BUILD.gn
index e62b991..d3b32a9 100644
--- a/front_end/ui/utils/BUILD.gn
+++ b/front_end/ui/utils/BUILD.gn
@@ -15,7 +15,10 @@
     "register-custom-element.js",
   ]
 
-  deps = [ "../../theme_support:bundle" ]
+  deps = [
+    "../../root:bundle",
+    "../../theme_support:bundle",
+  ]
 }
 
 devtools_entrypoint("bundle") {
diff --git a/front_end/ui/utils/append-style.js b/front_end/ui/utils/append-style.js
index c9c5ff9..ec929d9 100644
--- a/front_end/ui/utils/append-style.js
+++ b/front_end/ui/utils/append-style.js
@@ -5,6 +5,7 @@
 // @ts-nocheck
 // TODO(crbug.com/1011811): Enable TypeScript compiler checks
 
+import * as Root from '../../root/root.js';
 import * as ThemeSupport from '../../theme_support/theme_support.js';
 
 /**
@@ -13,7 +14,7 @@
  * @suppressGlobalPropertiesCheck
  */
 export function appendStyle(node, cssFile) {
-  const content = self.Runtime.cachedResources[cssFile] || '';
+  const content = Root.Runtime.cachedResources.get(cssFile) || '';
   if (!content) {
     console.error(cssFile + ' not preloaded. Check module.json');
   }
@@ -24,7 +25,7 @@
   const themeStyleSheet = ThemeSupport.ThemeSupport.instance().themeStyleSheet(cssFile, content);
   if (themeStyleSheet) {
     styleElement = createElement('style');
-    styleElement.textContent = themeStyleSheet + '\n' + Root.Runtime.resolveSourceURL(cssFile + '.theme');
+    styleElement.textContent = themeStyleSheet + '\n' + Root.Runtime.Runtime.resolveSourceURL(cssFile + '.theme');
     node.appendChild(styleElement);
   }
 }