Skip to content

Commit 2ea41ef

Browse files
committed
add getNativeURL function and related tests; refactor buildUrl to use native URL constructor
1 parent d4157e4 commit 2ea41ef

3 files changed

Lines changed: 81 additions & 33 deletions

File tree

packages/core/src/tools/utils/urlPolyfill.spec.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { buildUrl, getPathName, isValidUrl, normalizeUrl } from './urlPolyfill'
1+
import { buildUrl, getPathName, isValidUrl, normalizeUrl, getNativeURL } from './urlPolyfill'
22

33
describe('normalize url', () => {
44
it('should resolve absolute paths', () => {
@@ -79,3 +79,51 @@ describe('buildUrl', () => {
7979
expect(['file:///bar', 'file://foo.com/bar']).toContain(buildUrl('/bar', 'file://foo.com/faa').href)
8080
})
8181
})
82+
83+
describe('getNativeURLFromIframe', () => {
84+
it('should get native URL constructor from iframe', () => {
85+
const nativeURL = getNativeURL()
86+
87+
if (nativeURL) {
88+
expect(typeof nativeURL).toBe('function')
89+
const url = new nativeURL('http://example.com')
90+
expect(url.href).toBe('http://example.com/')
91+
}
92+
})
93+
94+
it('should work even if main window URL is overridden', () => {
95+
const originalURL = window.URL
96+
;(window as any).URL = function badURL() {
97+
throw new Error('Bad polyfill')
98+
}
99+
const nativeURL = getNativeURL()
100+
if (nativeURL) {
101+
expect(typeof nativeURL).toBe('function')
102+
const url = new nativeURL('http://example.com')
103+
expect(url.href).toBe('http://example.com/')
104+
}
105+
;(window as any).URL = originalURL
106+
})
107+
108+
it('should cache the native URL constructor', () => {
109+
const firstCall = getNativeURL()
110+
const secondCall = getNativeURL()
111+
112+
expect(firstCall).toBe(secondCall)
113+
})
114+
115+
it('should keep the same constructor and still resolve relative URLs correctly', () => {
116+
const nativeURL1 = getNativeURL()
117+
118+
history.pushState({}, '', '/foo/')
119+
const url1 = buildUrl('./bar', location.href)
120+
expect(url1.href).toBe(`${location.origin}/foo/bar`)
121+
122+
history.pushState({}, '', '/baz/')
123+
const nativeURL2 = getNativeURL()
124+
expect(nativeURL2).toBe(nativeURL1)
125+
126+
const url2 = buildUrl('./qux', location.href)
127+
expect(url2.href).toBe(`${location.origin}/baz/qux`)
128+
})
129+
})

packages/core/src/tools/utils/urlPolyfill.ts

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,40 +18,40 @@ export function getPathName(url: string) {
1818
}
1919

2020
export function buildUrl(url: string, base?: string) {
21-
const supportedURL = getSupportedUrl()
22-
if (supportedURL) {
23-
try {
24-
return base !== undefined ? new supportedURL(url, base) : new supportedURL(url)
25-
} catch (error) {
26-
throw new Error(`Failed to construct URL: ${String(error)} ${jsonStringify({ url, base })!}`)
27-
}
28-
}
29-
if (base === undefined && !/:/.test(url)) {
30-
throw new Error(`Invalid URL: '${url}'`)
31-
}
32-
let doc = document
33-
const anchorElement = doc.createElement('a')
34-
if (base !== undefined) {
35-
doc = document.implementation.createHTMLDocument('')
36-
const baseElement = doc.createElement('base')
37-
baseElement.href = base
38-
doc.head.appendChild(baseElement)
39-
doc.body.appendChild(anchorElement)
21+
const nativeURL = getNativeURL()
22+
const URLConstructor = nativeURL || URL
23+
24+
try {
25+
return base !== undefined ? new URLConstructor(url, base) : new URLConstructor(url)
26+
} catch (error) {
27+
throw new Error(`Failed to construct URL: ${String(error)} ${jsonStringify({ url, base })!}`)
4028
}
41-
anchorElement.href = url
42-
return anchorElement
4329
}
4430

45-
const originalURL = URL
46-
let isURLSupported: boolean | undefined
47-
function getSupportedUrl(): typeof URL | undefined {
48-
if (isURLSupported === undefined) {
49-
try {
50-
const url = new originalURL('http://test/path')
51-
isURLSupported = url.href === 'http://test/path'
52-
} catch {
53-
isURLSupported = false
31+
/**
32+
* Get native URL constructor from a clean iframe
33+
* This avoids polyfill issues by getting the native implementation from a fresh iframe context
34+
* Falls back to the original URL constructor if iframe approach fails
35+
*/
36+
let cachedNativeURL: typeof URL | undefined
37+
export function getNativeURL(): typeof URL | undefined {
38+
if (cachedNativeURL !== undefined) {
39+
return cachedNativeURL
40+
}
41+
42+
const iframe = document.createElement('iframe')
43+
document.body.appendChild(iframe)
44+
45+
const iframeWindow = iframe.contentWindow
46+
if (iframeWindow && (iframeWindow as any).URL) {
47+
const iframeURL = (iframeWindow as any).URL as typeof URL
48+
const testURL = new iframeURL('http://test.com')
49+
if (testURL.href === 'http://test.com/') {
50+
cachedNativeURL = iframeURL
5451
}
5552
}
56-
return isURLSupported ? originalURL : undefined
53+
54+
document.body.removeChild(iframe)
55+
56+
return cachedNativeURL
5757
}

packages/rum/src/domain/record/serialization/serializationUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export function switchToAbsoluteUrl(cssText: string, cssHref: string | null): st
9696
)
9797
}
9898

99-
export function makeUrlAbsolute(url: string, baseUrl: string): string {
99+
function makeUrlAbsolute(url: string, baseUrl: string): string {
100100
try {
101101
return buildUrl(url, baseUrl).href
102102
} catch {

0 commit comments

Comments
 (0)