diff --git a/src/domain/javascriptStaticAnalysisHelpers.ts b/src/domain/javascriptStaticAnalysisHelpers.ts index 96d0d2023f..d7ce5c79e5 100644 --- a/src/domain/javascriptStaticAnalysisHelpers.ts +++ b/src/domain/javascriptStaticAnalysisHelpers.ts @@ -410,7 +410,7 @@ const BROWSING_CONTEXT_KEYWORDS = new Set([ * `window.open(url, "_blank")` share the callee name. A literal method must * be an HTTP method; with a computed method, the second literal is a URL * unless it is provably an fs flag or a browsing-context keyword, or the - * receiver is spelled as a browsing context. XHR normalizes the standard + * receiver is spelled as a browsing context or storage. XHR normalizes the standard * methods' case; extension methods such as WebDAV `PROPFIND` are * conventionally uppercase tokens. */ @@ -422,13 +422,18 @@ const xhrOpenUrl = ( const url = stringValue(urlNode); if (url === undefined) return undefined; const method = stringValue(methodNode); + // IndexedDB and Cache Storage `open` take a name and a version, so a + // receiver spelled as storage needs a standard HTTP method literal. + const storage = storageKind(name) !== undefined; if (method === undefined) - return WINDOW_OPEN_CALL.test(name) || + return storage || + WINDOW_OPEN_CALL.test(name) || FS_OPEN_FLAGS.has(url) || BROWSING_CONTEXT_KEYWORDS.has(url.toLowerCase()) ? undefined : url; - return XHR_METHODS.has(method.toUpperCase()) || /^[A-Z][A-Z-]*$/u.test(method) + return XHR_METHODS.has(method.toUpperCase()) || + (!storage && /^[A-Z][A-Z-]*$/u.test(method)) ? url : undefined; }; @@ -483,11 +488,7 @@ export const endpointArgument = ( ): string | undefined => { if (name === "fetch" || name.endsWith(".fetch") || name === "WebSocket") return stringValue(args[0]); - // IndexedDB and Cache Storage `open` take a name and a version, not a URL. - if (name.endsWith(".open")) - return storageKind(name) === undefined - ? xhrOpenUrl(name, args[0], args[1]) - : undefined; + if (name.endsWith(".open")) return xhrOpenUrl(name, args[0], args[1]); const method = ["get", "post", "put", "patch", "delete", "request"].find( (candidate) => name === candidate || name.endsWith(`.${candidate}`), ); diff --git a/src/domain/webBundleAnalyzer.test.ts b/src/domain/webBundleAnalyzer.test.ts index d6248dc4ee..753d458f8f 100644 --- a/src/domain/webBundleAnalyzer.test.ts +++ b/src/domain/webBundleAnalyzer.test.ts @@ -213,15 +213,23 @@ describe("web bundle static-analysis parity", () => { const source = ` indexedDB.open(databaseName, "2"); window.indexedDB.open(databaseName, "/v3"); + indexedDB.open("APP", "4"); + indexedDB.open("GET", "/storage-named-xhr"); `; expect( - analyzeCapturedWebBundle(inspection(source)).observations.endpoints, - ).toEqual([]); + analyzeCapturedWebBundle(inspection(source)).observations.endpoints.map( + ({ value }) => value, + ), + ).toEqual(["/storage-named-xhr"]); const analysis = analyzeJavaScriptStaticSource(source); - expect(analysis.endpoints).toEqual([]); + expect(analysis.endpoints.map(({ value }) => value)).toEqual([ + "/storage-named-xhr", + ]); expect(analysis.storage.map(({ kind }) => kind)).toEqual([ "indexed-db", "indexed-db", + "indexed-db", + "indexed-db", ]); });