@@ -155,4 +155,154 @@ describe('GPT script guard', () => {
155155 '/integrations/gpt/pagead/managed/js/gpt/current/pubads_impl.js?foo=bar'
156156 ) ;
157157 } ) ;
158+
159+ // -----------------------------------------------------------------------
160+ // document.write edge-cases (DOMParser-based rewriting)
161+ // -----------------------------------------------------------------------
162+
163+ it ( 'rewrites document.write script src with single-quoted attribute' , ( ) => {
164+ const nativeWriteSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
165+ document . write = nativeWriteSpy as unknown as typeof document . write ;
166+
167+ installGptGuard ( ) ;
168+
169+ document . write (
170+ "<script src='https://securepubads.g.doubleclick.net/pagead/managed/js/gpt/current/pubads_impl.js'></script>"
171+ ) ;
172+
173+ expect ( nativeWriteSpy ) . toHaveBeenCalledTimes ( 1 ) ;
174+ const [ writtenHtml ] = nativeWriteSpy . mock . calls [ 0 ] ?? [ ] ;
175+ expect ( writtenHtml ) . toContain ( window . location . host ) ;
176+ expect ( writtenHtml ) . toContain ( '/integrations/gpt/pagead/managed/js/gpt/current/pubads_impl.js' ) ;
177+ expect ( writtenHtml ) . not . toContain ( 'securepubads.g.doubleclick.net' ) ;
178+ } ) ;
179+
180+ it ( 'rewrites document.write script src with extra whitespace around =' , ( ) => {
181+ const nativeWriteSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
182+ document . write = nativeWriteSpy as unknown as typeof document . write ;
183+
184+ installGptGuard ( ) ;
185+
186+ document . write (
187+ '<script src = "https://securepubads.g.doubleclick.net/pagead/managed/js/gpt/current/pubads_impl.js" ></script>'
188+ ) ;
189+
190+ expect ( nativeWriteSpy ) . toHaveBeenCalledTimes ( 1 ) ;
191+ const [ writtenHtml ] = nativeWriteSpy . mock . calls [ 0 ] ?? [ ] ;
192+ expect ( writtenHtml ) . toContain ( window . location . host ) ;
193+ expect ( writtenHtml ) . toContain ( '/integrations/gpt/pagead/managed/js/gpt/current/pubads_impl.js' ) ;
194+ expect ( writtenHtml ) . not . toContain ( 'securepubads.g.doubleclick.net' ) ;
195+ } ) ;
196+
197+ it ( 'rewrites multiple script tags in a single document.write call' , ( ) => {
198+ const nativeWriteSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
199+ document . write = nativeWriteSpy as unknown as typeof document . write ;
200+
201+ installGptGuard ( ) ;
202+
203+ document . write (
204+ '<script src="https://securepubads.g.doubleclick.net/pagead/a.js"></script>' +
205+ '<script src="https://securepubads.g.doubleclick.net/pagead/b.js"></script>'
206+ ) ;
207+
208+ expect ( nativeWriteSpy ) . toHaveBeenCalledTimes ( 1 ) ;
209+ const [ writtenHtml ] = nativeWriteSpy . mock . calls [ 0 ] ?? [ ] ;
210+ expect ( writtenHtml ) . toContain ( '/integrations/gpt/pagead/a.js' ) ;
211+ expect ( writtenHtml ) . toContain ( '/integrations/gpt/pagead/b.js' ) ;
212+ expect ( writtenHtml ) . not . toContain ( 'securepubads.g.doubleclick.net' ) ;
213+ } ) ;
214+
215+ it ( 'rewrites document.writeln the same as document.write' , ( ) => {
216+ const nativeWritelnSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
217+ document . writeln = nativeWritelnSpy as unknown as typeof document . writeln ;
218+
219+ installGptGuard ( ) ;
220+
221+ document . writeln (
222+ '<script src="https://securepubads.g.doubleclick.net/pagead/managed/js/gpt/current/pubads_impl.js"></script>'
223+ ) ;
224+
225+ expect ( nativeWritelnSpy ) . toHaveBeenCalledTimes ( 1 ) ;
226+ const [ writtenHtml ] = nativeWritelnSpy . mock . calls [ 0 ] ?? [ ] ;
227+ expect ( writtenHtml ) . toContain ( window . location . host ) ;
228+ expect ( writtenHtml ) . toContain ( '/integrations/gpt/pagead/managed/js/gpt/current/pubads_impl.js' ) ;
229+ expect ( writtenHtml ) . not . toContain ( 'securepubads.g.doubleclick.net' ) ;
230+ } ) ;
231+
232+ it ( 'passes through HTML with no GPT domain reference unchanged' , ( ) => {
233+ const nativeWriteSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
234+ document . write = nativeWriteSpy as unknown as typeof document . write ;
235+
236+ installGptGuard ( ) ;
237+
238+ const html = '<script src="https://example.com/tracker.js"></script>' ;
239+ document . write ( html ) ;
240+
241+ expect ( nativeWriteSpy ) . toHaveBeenCalledWith ( html ) ;
242+ } ) ;
243+
244+ it ( 'rewrites protocol-relative GPT URLs in document.write' , ( ) => {
245+ const nativeWriteSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
246+ document . write = nativeWriteSpy as unknown as typeof document . write ;
247+
248+ installGptGuard ( ) ;
249+
250+ document . write (
251+ '<script src="//securepubads.g.doubleclick.net/pagead/managed/js/gpt/current/pubads_impl.js"></script>'
252+ ) ;
253+
254+ expect ( nativeWriteSpy ) . toHaveBeenCalledTimes ( 1 ) ;
255+ const [ writtenHtml ] = nativeWriteSpy . mock . calls [ 0 ] ?? [ ] ;
256+ expect ( writtenHtml ) . toContain ( window . location . host ) ;
257+ expect ( writtenHtml ) . toContain ( '/integrations/gpt/pagead/managed/js/gpt/current/pubads_impl.js' ) ;
258+ expect ( writtenHtml ) . not . toContain ( 'securepubads.g.doubleclick.net' ) ;
259+ } ) ;
260+
261+ // -----------------------------------------------------------------------
262+ // Fail-closed behaviour
263+ // -----------------------------------------------------------------------
264+
265+ it ( 'fails closed when DOMParser is unavailable' , ( ) => {
266+ const nativeWriteSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
267+ document . write = nativeWriteSpy as unknown as typeof document . write ;
268+
269+ const originalDOMParser = globalThis . DOMParser ;
270+ // @ts -expect-error — simulating an environment without DOMParser
271+ delete globalThis . DOMParser ;
272+
273+ try {
274+ installGptGuard ( ) ;
275+
276+ document . write ( '<script src="https://securepubads.g.doubleclick.net/pagead/a.js"></script>' ) ;
277+
278+ expect ( nativeWriteSpy ) . toHaveBeenCalledTimes ( 1 ) ;
279+ expect ( nativeWriteSpy ) . toHaveBeenCalledWith ( '' ) ;
280+ } finally {
281+ globalThis . DOMParser = originalDOMParser ;
282+ }
283+ } ) ;
284+
285+ it ( 'fails closed when DOMParser throws' , ( ) => {
286+ const nativeWriteSpy = vi . fn < ( ...args : string [ ] ) => void > ( ) ;
287+ document . write = nativeWriteSpy as unknown as typeof document . write ;
288+
289+ const originalDOMParser = globalThis . DOMParser ;
290+ // @ts -expect-error — injecting a broken DOMParser
291+ globalThis . DOMParser = class {
292+ parseFromString ( ) {
293+ throw new Error ( 'boom' ) ;
294+ }
295+ } ;
296+
297+ try {
298+ installGptGuard ( ) ;
299+
300+ document . write ( '<script src="https://securepubads.g.doubleclick.net/pagead/a.js"></script>' ) ;
301+
302+ expect ( nativeWriteSpy ) . toHaveBeenCalledTimes ( 1 ) ;
303+ expect ( nativeWriteSpy ) . toHaveBeenCalledWith ( '' ) ;
304+ } finally {
305+ globalThis . DOMParser = originalDOMParser ;
306+ }
307+ } ) ;
158308} ) ;
0 commit comments