@@ -21,6 +21,12 @@ import {HttpErrorResponse, HttpEvent, HttpEventType, HttpResponse, HttpStatusCod
2121// is shared among all applications on the page.
2222let nextRequestId : number = 0 ;
2323
24+ /**
25+ * When a pending <script> is unsubscribed we'll move it to this document, so it won't be
26+ * executed.
27+ */
28+ let foreignDocument : Document | undefined ;
29+
2430// Error text given when a JSONP script is injected, but doesn't invoke the callback
2531// passed in its URL.
2632export const JSONP_ERR_NO_CALLBACK = 'JSONP injected script did not invoke callback.' ;
@@ -101,22 +107,13 @@ export class JsonpClientBackend implements HttpBackend {
101107 // Whether the response callback has been called.
102108 let finished : boolean = false ;
103109
104- // Whether the request has been cancelled (and thus any other callbacks)
105- // should be ignored.
106- let cancelled : boolean = false ;
107-
108110 // Set the response callback in this.callbackMap (which will be the window
109111 // object in the browser. The script being loaded via the <script> tag will
110112 // eventually call this callback.
111113 this . callbackMap [ callback ] = ( data ?: any ) => {
112114 // Data has been received from the JSONP script. Firstly, delete this callback.
113115 delete this . callbackMap [ callback ] ;
114116
115- // Next, make sure the request wasn't cancelled in the meantime.
116- if ( cancelled ) {
117- return ;
118- }
119-
120117 // Set state to indicate data was received.
121118 body = data ;
122119 finished = true ;
@@ -125,29 +122,22 @@ export class JsonpClientBackend implements HttpBackend {
125122 // cleanup() is a utility closure that removes the <script> from the page and
126123 // the response callback from the window. This logic is used in both the
127124 // success, error, and cancellation paths, so it's extracted out for convenience.
128- const cleanup = ( removeCallback = true ) => {
125+ const cleanup = ( ) => {
129126 // Remove the <script> tag if it's still on the page.
130127 if ( node . parentNode ) {
131128 node . parentNode . removeChild ( node ) ;
132129 }
133130
134- if ( removeCallback ) {
135- // Remove the response callback from the callbackMap (window object in the
136- // browser).
137- delete this . callbackMap [ callback ] ;
138- }
131+ // Remove the response callback from the callbackMap (window object in the
132+ // browser).
133+ delete this . callbackMap [ callback ] ;
139134 } ;
140135
141136 // onLoad() is the success callback which runs after the response callback
142137 // if the JSONP script loads successfully. The event itself is unimportant.
143138 // If something went wrong, onLoad() may run without the response callback
144139 // having been invoked.
145140 const onLoad = ( event : Event ) => {
146- // Do nothing if the request has been cancelled.
147- if ( cancelled ) {
148- return ;
149- }
150-
151141 // We wrap it in an extra Promise, to ensure the microtask
152142 // is scheduled after the loaded endpoint has executed any potential microtask itself,
153143 // which is not guaranteed in Internet Explorer and EdgeHTML. See issue #39496
@@ -186,10 +176,6 @@ export class JsonpClientBackend implements HttpBackend {
186176 // a Javascript error. It emits the error via the Observable error channel as
187177 // a HttpErrorResponse.
188178 const onError : any = ( error : Error ) => {
189- // If the request was already cancelled, no need to emit anything.
190- if ( cancelled ) {
191- return ;
192- }
193179 cleanup ( ) ;
194180
195181 // Wrap the error in a HttpErrorResponse.
@@ -212,20 +198,25 @@ export class JsonpClientBackend implements HttpBackend {
212198
213199 // Cancellation handler.
214200 return ( ) => {
215- // Track the cancellation so event listeners won't do anything even if already scheduled.
216- cancelled = true ;
217-
218- // Remove the event listeners so they won't run if the events later fire.
219- node . removeEventListener ( 'load' , onLoad ) ;
220- node . removeEventListener ( 'error' , onError ) ;
201+ if ( ! finished ) {
202+ this . removeListeners ( node ) ;
203+ }
221204
222205 // And finally, clean up the page.
223- // Unsubscription won't remove the callback handler because there's no way to stop
224- // loading <script> once it has been added to DOM.
225- cleanup ( false ) ;
206+ cleanup ( ) ;
226207 } ;
227208 } ) ;
228209 }
210+
211+ private removeListeners ( script : HTMLScriptElement ) : void {
212+ // Issue #34818
213+ // Changing <script>'s ownerDocument will prevent it from execution.
214+ // https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
215+ if ( ! foreignDocument ) {
216+ foreignDocument = ( this . document . implementation as DOMImplementation ) . createHTMLDocument ( ) ;
217+ }
218+ foreignDocument . adoptNode ( script ) ;
219+ }
229220}
230221
231222/**
0 commit comments