Repository: facebook/react
Pull request: [devtools] Fix "View source" for sources with URLs that aren't normalized

## Summary

Chrome lists resources with their normalized URLs. However, sourcemaps may contain unnormalized URLs in their `sources`. When symbolicating we get the original entry in `sources` not the normalized URL so we need to consider that when calling `openResource`. Otherwise nothing happens and you only see an error when you actually inspect React DevTools e.g. `extensions.js:1 Extension server error: Object not found: webpack-internal:///(app-pages-browser)/./app/page.tsx`

## How did you test this change?

Use a local build (`yarn build:chrome:local`) in a basic Next.js app with Webpack and click "View source" for the root page.

Firefox has "View source" disabled for me even with the published version.


Changed files:
filename: packages/react-devtools-extensions/src/main/fetchFileWithCaching.js
patch: @@ -1,6 +1,6 @@
 /* global chrome */
 
-import {normalizeUrl} from 'react-devtools-shared/src/utils';
+import {normalizeUrlIfValid} from 'react-devtools-shared/src/utils';
 import {__DEBUG__} from 'react-devtools-shared/src/constants';
 
 let debugIDCounter = 0;
@@ -117,7 +117,7 @@ async function fetchFileWithCaching(url: string): Promise<string> {
       chrome.devtools.inspectedWindow.getResources(r => resolve(r)),
     );
 
-    const normalizedReferenceURL = normalizeUrl(url);
+    const normalizedReferenceURL = normalizeUrlIfValid(url);
     const resource = resources.find(r => r.url === normalizedReferenceURL);
 
     if (resource != null) {

filename: packages/react-devtools-extensions/src/main/index.js
patch: @@ -16,6 +16,7 @@ import {
   LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
 } from 'react-devtools-shared/src/constants';
 import {logEvent} from 'react-devtools-shared/src/Logger';
+import {normalizeUrlIfValid} from 'react-devtools-shared/src/utils';
 
 import {
   setBrowserSelectionFromReact,
@@ -128,7 +129,11 @@ function createBridgeAndStore() {
       : source;
 
     // We use 1-based line and column, Chrome expects them 0-based.
-    chrome.devtools.panels.openResource(sourceURL, line - 1, column - 1);
+    chrome.devtools.panels.openResource(
+      normalizeUrlIfValid(sourceURL),
+      line - 1,
+      column - 1,
+    );
   };
 
   // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration.

filename: packages/react-devtools-shared/src/symbolicateSource.js
patch: @@ -7,7 +7,6 @@
  * @flow
  */
 
-import {normalizeUrl} from 'react-devtools-shared/src/utils';
 import SourceMapConsumer from 'react-devtools-shared/src/hooks/SourceMapConsumer';
 
 import type {Source} from 'react-devtools-shared/src/shared/types';
@@ -91,9 +90,8 @@ export async function symbolicateSource(
           try {
             // sourceMapURL = https://react.dev/script.js.map
             void new URL(possiblyURL); // test if it is a valid URL
-            const normalizedURL = normalizeUrl(possiblyURL);
 
-            return {sourceURL: normalizedURL, line, column};
+            return {sourceURL: possiblyURL, line, column};
           } catch (e) {
             // This is not valid URL
             if (

filename: packages/react-devtools-shared/src/utils.js
patch: @@ -996,9 +996,17 @@ export function backendToFrontendSerializedElementMapper(
   };
 }
 
-// Chrome normalizes urls like webpack-internals:// but new URL don't, so cannot use new URL here.
-export function normalizeUrl(url: string): string {
-  return url.replace('/./', '/');
+/**
+ * Should be used when treating url as a Chrome Resource URL.
+ */
+export function normalizeUrlIfValid(url: string): string {
+  try {
+    // TODO: Chrome will use the basepath to create a Resource URL.
+    return new URL(url).toString();
+  } catch {
+    // Giving up if it's not a valid URL without basepath
+    return url;
+  }
 }
 
 export function getIsReloadAndProfileSupported(): boolean {