f5caff6b213251ed3d81aa449c45f6314b7a1dbc
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / NetworkManager.js
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 // FIXME: NetworkManager lacks advanced multi-target support. (Network.loadResource invocations per-target)
27
28 WI.NetworkManager = class NetworkManager extends WI.Object
29 {
30     constructor()
31     {
32         super();
33
34         this._frameIdentifierMap = new Map;
35         this._mainFrame = null;
36         this._resourceRequestIdentifierMap = new Map;
37         this._orphanedResources = new Map;
38         this._webSocketIdentifierToURL = new Map;
39
40         this._waitingForMainFrameResourceTreePayload = true;
41
42         this._sourceMapURLMap = new Map;
43         this._downloadingSourceMaps = new Set;
44
45         WI.notifications.addEventListener(WI.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this);
46         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
47     }
48
49     // Target
50
51     initializeTarget(target)
52     {
53         if (target.PageAgent) {
54             target.PageAgent.enable();
55             target.PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this));
56         }
57
58         // FIXME: ServiceWorkerAgent should only be exposed in the "serviceworker" target type.
59         // Currently it is exposed to "web" targets. Work around this by only using the
60         // ServiceWorker domain if there is no Page domain.
61         if (target.ServiceWorkerAgent && !target.PageAgent)
62             target.ServiceWorkerAgent.getInitializationInfo(this._processServiceWorkerConfiguration.bind(this));
63
64         if (target.NetworkAgent) {
65             target.NetworkAgent.enable();
66
67             // COMPATIBILITY (iOS 10.3): Network.setDisableResourceCaching did not exist.
68             if (NetworkAgent.setResourceCachingDisabled) {
69                 if (WI.settings.resourceCachingDisabled && WI.settings.resourceCachingDisabled.value)
70                     target.NetworkAgent.setResourceCachingDisabled(true);
71             }
72         }
73
74         if (target.type === WI.Target.Type.Worker)
75             this.adoptOrphanedResourcesForTarget(target);
76     }
77
78     // Public
79
80     get mainFrame()
81     {
82         return this._mainFrame;
83     }
84
85     get frames()
86     {
87         return [...this._frameIdentifierMap.values()];
88     }
89
90     frameForIdentifier(frameId)
91     {
92         return this._frameIdentifierMap.get(frameId) || null;
93     }
94
95     resourceForRequestIdentifier(requestIdentifier)
96     {
97         return this._resourceRequestIdentifierMap.get(requestIdentifier) || null;
98     }
99
100     downloadSourceMap(sourceMapURL, baseURL, originalSourceCode)
101     {
102         // The baseURL could have come from a "//# sourceURL". Attempt to get a
103         // reasonable absolute URL for the base by using the main resource's URL.
104         if (WI.networkManager.mainFrame)
105             baseURL = absoluteURL(baseURL, WI.networkManager.mainFrame.url);
106
107         if (sourceMapURL.startsWith("data:")) {
108             this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode);
109             return;
110         }
111
112         sourceMapURL = absoluteURL(sourceMapURL, baseURL);
113         if (!sourceMapURL)
114             return;
115
116         console.assert(originalSourceCode.url);
117         if (!originalSourceCode.url)
118             return;
119
120         // FIXME: <rdar://problem/13265694> Source Maps: Better handle when multiple resources reference the same SourceMap
121
122         if (this._sourceMapURLMap.has(sourceMapURL) || this._downloadingSourceMaps.has(sourceMapURL))
123             return;
124
125         let loadAndParseSourceMap = () => {
126             this._loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode);
127         };
128
129         if (!WI.networkManager.mainFrame) {
130             // If we don't have a main frame, then we are likely in the middle of building the resource tree.
131             // Delaying until the next runloop is enough in this case to then start loading the source map.
132             setTimeout(loadAndParseSourceMap, 0);
133             return;
134         }
135
136         loadAndParseSourceMap();
137     }
138
139     frameDidNavigate(framePayload)
140     {
141         // Called from WI.PageObserver.
142
143         // Ignore this while waiting for the whole frame/resource tree.
144         if (this._waitingForMainFrameResourceTreePayload)
145             return;
146
147         var frameWasLoadedInstantly = false;
148
149         var frame = this.frameForIdentifier(framePayload.id);
150         if (!frame) {
151             // If the frame wasn't known before now, then the main resource was loaded instantly (about:blank, etc.)
152             // Make a new resource (which will make the frame). Mark will mark it as loaded at the end too since we
153             // don't expect any more events about the load finishing for these frames.
154             let resourceOptions = {
155                 loaderIdentifier: framePayload.loaderId,
156             };
157             let frameOptions = {
158                 name: framePayload.name,
159                 securityOrigin: framePayload.securityOrigin,
160             };
161             let frameResource = this._addNewResourceToFrameOrTarget(framePayload.url, framePayload.id, resourceOptions, frameOptions);
162             frame = frameResource.parentFrame;
163             frameWasLoadedInstantly = true;
164
165             console.assert(frame);
166             if (!frame)
167                 return;
168         }
169
170         if (framePayload.loaderId === frame.provisionalLoaderIdentifier) {
171             // There was a provisional load in progress, commit it.
172             frame.commitProvisionalLoad(framePayload.securityOrigin);
173         } else {
174             let mainResource = null;
175             if (frame.mainResource.url !== framePayload.url || frame.loaderIdentifier !== framePayload.loaderId) {
176                 // Navigations like back/forward do not have provisional loads, so create a new main resource here.
177                 mainResource = new WI.Resource(framePayload.url, {
178                     mimeType: framePayload.mimeType,
179                     loaderIdentifier: framePayload.loaderId,
180                 });
181             } else {
182                 // The main resource is already correct, so reuse it.
183                 mainResource = frame.mainResource;
184             }
185
186             frame.initialize(framePayload.name, framePayload.securityOrigin, framePayload.loaderId, mainResource);
187         }
188
189         var oldMainFrame = this._mainFrame;
190
191         if (framePayload.parentId) {
192             var parentFrame = this.frameForIdentifier(framePayload.parentId);
193             console.assert(parentFrame);
194
195             if (frame === this._mainFrame)
196                 this._mainFrame = null;
197
198             if (frame.parentFrame !== parentFrame)
199                 parentFrame.addChildFrame(frame);
200         } else {
201             if (frame.parentFrame)
202                 frame.parentFrame.removeChildFrame(frame);
203             this._mainFrame = frame;
204         }
205
206         if (this._mainFrame !== oldMainFrame)
207             this._mainFrameDidChange(oldMainFrame);
208
209         if (frameWasLoadedInstantly)
210             frame.mainResource.markAsFinished();
211     }
212
213     frameDidDetach(frameId)
214     {
215         // Called from WI.PageObserver.
216
217         // Ignore this while waiting for the whole frame/resource tree.
218         if (this._waitingForMainFrameResourceTreePayload)
219             return;
220
221         var frame = this.frameForIdentifier(frameId);
222         if (!frame)
223             return;
224
225         if (frame.parentFrame)
226             frame.parentFrame.removeChildFrame(frame);
227
228         this._frameIdentifierMap.delete(frame.id);
229
230         var oldMainFrame = this._mainFrame;
231
232         if (frame === this._mainFrame)
233             this._mainFrame = null;
234
235         frame.clearExecutionContexts();
236
237         this.dispatchEventToListeners(WI.NetworkManager.Event.FrameWasRemoved, {frame});
238
239         if (this._mainFrame !== oldMainFrame)
240             this._mainFrameDidChange(oldMainFrame);
241     }
242
243     resourceRequestWillBeSent(requestIdentifier, frameIdentifier, loaderIdentifier, request, type, redirectResponse, timestamp, walltime, initiator, targetId)
244     {
245         // Called from WI.NetworkObserver.
246
247         // Ignore this while waiting for the whole frame/resource tree.
248         if (this._waitingForMainFrameResourceTreePayload)
249             return;
250
251         // COMPATIBILITY (iOS 8): Timeline timestamps for legacy backends are computed
252         // dynamically from the first backend timestamp received. For navigations we
253         // need to reset that base timestamp, and an appropriate timestamp to use is
254         // the new main resource's will be sent timestamp. So save this value on the
255         // resource in case it becomes a main resource.
256         var originalRequestWillBeSentTimestamp = timestamp;
257
258         var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
259         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
260         if (resource) {
261             // This is an existing request which is being redirected, update the resource.
262             console.assert(resource.parentFrame.id === frameIdentifier);
263             console.assert(resource.loaderIdentifier === loaderIdentifier);
264             console.assert(!targetId);
265             resource.updateForRedirectResponse(request, redirectResponse, elapsedTime, walltime);
266             return;
267         }
268
269         // This is a new request, make a new resource and add it to the right frame.
270         resource = this._addNewResourceToFrameOrTarget(request.url, frameIdentifier, {
271             type,
272             loaderIdentifier,
273             targetId,
274             requestIdentifier,
275             requestMethod: request.method,
276             requestHeaders: request.headers,
277             requestData: request.postData,
278             requestSentTimestamp: elapsedTime,
279             requestSentWalltime: walltime,
280             initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator),
281             initiatorNode: this._initiatorNodeFromPayload(initiator),
282             originalRequestWillBeSentTimestamp,
283         });
284
285         // Associate the resource with the requestIdentifier so it can be found in future loading events.
286         this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
287     }
288
289     webSocketCreated(requestId, url)
290     {
291         this._webSocketIdentifierToURL.set(requestId, url);
292     }
293
294     webSocketWillSendHandshakeRequest(requestId, timestamp, walltime, request)
295     {
296         let url = this._webSocketIdentifierToURL.get(requestId);
297         console.assert(url);
298         if (!url)
299             return;
300
301         // COMPATIBILITY(iOS 10.3): `walltime` did not exist in 10.3 and earlier.
302         if (!NetworkAgent.hasEventParameter("webSocketWillSendHandshakeRequest", "walltime")) {
303             request = arguments[2];
304             walltime = NaN;
305         }
306
307         // FIXME: <webkit.org/b/168475> Web Inspector: Correctly display iframe's and worker's WebSockets
308
309         let resource = new WI.WebSocketResource(url, {
310             loaderIdentifier: WI.networkManager.mainFrame.id,
311             requestIdentifier: requestId,
312             requestHeaders: request.headers,
313             timestamp,
314             walltime,
315             requestSentTimestamp: WI.timelineManager.computeElapsedTime(timestamp),
316         });
317
318         let frame = this.frameForIdentifier(WI.networkManager.mainFrame.id);
319         frame.addResource(resource);
320
321         this._resourceRequestIdentifierMap.set(requestId, resource);
322     }
323
324     webSocketHandshakeResponseReceived(requestId, timestamp, response)
325     {
326         let resource = this._resourceRequestIdentifierMap.get(requestId);
327         console.assert(resource);
328         if (!resource)
329             return;
330
331         resource.readyState = WI.WebSocketResource.ReadyState.Open;
332
333         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
334
335         // FIXME: <webkit.org/b/169166> Web Inspector: WebSockets: Implement timing information
336         let responseTiming = response.timing || null;
337
338         resource.updateForResponse(resource.url, resource.mimeType, resource.type, response.headers, response.status, response.statusText, elapsedTime, responseTiming);
339
340         resource.markAsFinished(elapsedTime);
341     }
342
343     webSocketFrameReceived(requestId, timestamp, response)
344     {
345         this._webSocketFrameReceivedOrSent(requestId, timestamp, response);
346     }
347
348     webSocketFrameSent(requestId, timestamp, response)
349     {
350         this._webSocketFrameReceivedOrSent(requestId, timestamp, response);
351     }
352
353     webSocketClosed(requestId, timestamp)
354     {
355         let resource = this._resourceRequestIdentifierMap.get(requestId);
356         console.assert(resource);
357         if (!resource)
358             return;
359
360         resource.readyState = WI.WebSocketResource.ReadyState.Closed;
361
362         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
363         resource.markAsFinished(elapsedTime);
364
365         this._webSocketIdentifierToURL.delete(requestId);
366         this._resourceRequestIdentifierMap.delete(requestId);
367     }
368
369     _webSocketFrameReceivedOrSent(requestId, timestamp, response)
370     {
371         let resource = this._resourceRequestIdentifierMap.get(requestId);
372         console.assert(resource);
373         if (!resource)
374             return;
375
376         // Data going from the client to the server is always masked.
377         let isOutgoing = !!response.mask;
378
379         let {payloadData, payloadLength, opcode} = response;
380         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
381
382         resource.addFrame(payloadData, payloadLength, isOutgoing, opcode, timestamp, elapsedTime);
383     }
384
385     markResourceRequestAsServedFromMemoryCache(requestIdentifier)
386     {
387         // Called from WI.NetworkObserver.
388
389         // Ignore this while waiting for the whole frame/resource tree.
390         if (this._waitingForMainFrameResourceTreePayload)
391             return;
392
393         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
394
395         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
396         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
397         // just doesn't have a requestIdentifier for us to look it up.
398         if (!resource)
399             return;
400
401         resource.legacyMarkServedFromMemoryCache();
402     }
403
404     resourceRequestWasServedFromMemoryCache(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload, timestamp, initiator)
405     {
406         // Called from WI.NetworkObserver.
407
408         // Ignore this while waiting for the whole frame/resource tree.
409         if (this._waitingForMainFrameResourceTreePayload)
410             return;
411
412         console.assert(!this._resourceRequestIdentifierMap.has(requestIdentifier));
413
414         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
415         let response = cachedResourcePayload.response;
416         const responseSource = NetworkAgent.ResponseSource.MemoryCache;
417
418         let resource = this._addNewResourceToFrameOrTarget(cachedResourcePayload.url, frameIdentifier, {
419             type: cachedResourcePayload.type,
420             loaderIdentifier,
421             requestIdentifier,
422             requestMethod: "GET",
423             requestSentTimestamp: elapsedTime,
424             initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator),
425             initiatorNode: this._initiatorNodeFromPayload(initiator),
426         });
427         resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource);
428         resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime);
429         resource.increaseTransferSize(cachedResourcePayload.bodySize);
430         resource.setCachedResponseBodySize(cachedResourcePayload.bodySize);
431         resource.markAsFinished(elapsedTime);
432
433         console.assert(resource.cached, "This resource should be classified as cached since it was served from the MemoryCache", resource);
434
435         if (cachedResourcePayload.sourceMapURL)
436             this.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource);
437
438         // No need to associate the resource with the requestIdentifier, since this is the only event
439         // sent for memory cache resource loads.
440     }
441
442     resourceRequestDidReceiveResponse(requestIdentifier, frameIdentifier, loaderIdentifier, type, response, timestamp)
443     {
444         // Called from WI.NetworkObserver.
445
446         // Ignore this while waiting for the whole frame/resource tree.
447         if (this._waitingForMainFrameResourceTreePayload)
448             return;
449
450         var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
451         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
452
453         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
454         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
455         // just doesn't have a requestIdentifier for us to look it up, but we can try to look it up by its URL.
456         if (!resource) {
457             var frame = this.frameForIdentifier(frameIdentifier);
458             if (frame)
459                 resource = frame.resourceForURL(response.url);
460
461             // If we find the resource this way we had marked it earlier as finished via PageAgent.getResourceTree.
462             // Associate the resource with the requestIdentifier so it can be found in future loading events.
463             // and roll it back to an unfinished state, we know now it is still loading.
464             if (resource) {
465                 this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
466                 resource.revertMarkAsFinished();
467             }
468         }
469
470         // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
471         // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
472         if (!resource) {
473             resource = this._addNewResourceToFrameOrTarget(response.url, frameIdentifier, {
474                 type,
475                 loaderIdentifier,
476                 requestIdentifier,
477                 requestHeaders: response.requestHeaders,
478                 requestSentTimestamp: elapsedTime,
479             });
480
481             // Associate the resource with the requestIdentifier so it can be found in future loading events.
482             this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
483         }
484
485         // COMPATIBILITY (iOS 10.3): `fromDiskCache` is legacy, replaced by `source`.
486         if (response.fromDiskCache)
487             resource.legacyMarkServedFromDiskCache();
488
489         resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing, response.source);
490     }
491
492     resourceRequestDidReceiveData(requestIdentifier, dataLength, encodedDataLength, timestamp)
493     {
494         // Called from WI.NetworkObserver.
495
496         // Ignore this while waiting for the whole frame/resource tree.
497         if (this._waitingForMainFrameResourceTreePayload)
498             return;
499
500         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
501         var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
502
503         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
504         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
505         // just doesn't have a requestIdentifier for us to look it up.
506         if (!resource)
507             return;
508
509         resource.increaseSize(dataLength, elapsedTime);
510
511         if (encodedDataLength !== -1)
512             resource.increaseTransferSize(encodedDataLength);
513     }
514
515     resourceRequestDidFinishLoading(requestIdentifier, timestamp, sourceMapURL, metrics)
516     {
517         // Called from WI.NetworkObserver.
518
519         // Ignore this while waiting for the whole frame/resource tree.
520         if (this._waitingForMainFrameResourceTreePayload)
521             return;
522
523         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
524         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
525         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
526         console.assert(resource);
527         if (!resource)
528             return;
529
530         if (metrics)
531             resource.updateWithMetrics(metrics);
532
533         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
534         resource.markAsFinished(elapsedTime);
535
536         if (sourceMapURL)
537             this.downloadSourceMap(sourceMapURL, resource.url, resource);
538
539         this._resourceRequestIdentifierMap.delete(requestIdentifier);
540     }
541
542     resourceRequestDidFailLoading(requestIdentifier, canceled, timestamp, errorText)
543     {
544         // Called from WI.NetworkObserver.
545
546         // Ignore this while waiting for the whole frame/resource tree.
547         if (this._waitingForMainFrameResourceTreePayload)
548             return;
549
550         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
551         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
552         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
553         console.assert(resource);
554         if (!resource)
555             return;
556
557         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
558         resource.markAsFailed(canceled, elapsedTime, errorText);
559
560         if (resource.parentFrame && resource === resource.parentFrame.provisionalMainResource)
561             resource.parentFrame.clearProvisionalLoad();
562
563         this._resourceRequestIdentifierMap.delete(requestIdentifier);
564     }
565
566     executionContextCreated(contextPayload)
567     {
568         // Called from WI.RuntimeObserver.
569
570         var frame = this.frameForIdentifier(contextPayload.frameId);
571         console.assert(frame);
572         if (!frame)
573             return;
574
575         var displayName = contextPayload.name || frame.mainResource.displayName;
576         var executionContext = new WI.ExecutionContext(WI.mainTarget, contextPayload.id, displayName, contextPayload.isPageContext, frame);
577         frame.addExecutionContext(executionContext);
578     }
579
580     resourceForURL(url)
581     {
582         if (!this._mainFrame)
583             return null;
584
585         if (this._mainFrame.mainResource.url === url)
586             return this._mainFrame.mainResource;
587
588         return this._mainFrame.resourceForURL(url, true);
589     }
590
591     adoptOrphanedResourcesForTarget(target)
592     {
593         let resources = this._orphanedResources.take(target.identifier);
594         if (!resources)
595             return;
596
597         for (let resource of resources)
598             target.adoptResource(resource);
599     }
600
601     // Private
602
603     _addNewResourceToFrameOrTarget(url, frameIdentifier, resourceOptions = {}, frameOptions = {})
604     {
605         console.assert(!this._waitingForMainFrameResourceTreePayload);
606
607         let resource = null;
608
609         if (!frameIdentifier && resourceOptions.targetId) {
610             // This is a new resource for a ServiceWorker target.
611             console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker);
612             console.assert(resourceOptions.targetId === WI.mainTarget.identifier);
613             resource = new WI.Resource(url, resourceOptions);
614             resource.target.addResource(resource);
615             return resource;
616         }
617
618         let frame = this.frameForIdentifier(frameIdentifier);
619         if (frame) {
620             // This is a new request for an existing frame, which might be the main resource or a new resource.
621             if (resourceOptions.type === "Document" && frame.mainResource.url === url && frame.loaderIdentifier === resourceOptions.loaderIdentifier)
622                 resource = frame.mainResource;
623             else if (resourceOptions.type === "Document" && frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === resourceOptions.loaderIdentifier)
624                 resource = frame.provisionalMainResource;
625             else {
626                 resource = new WI.Resource(url, resourceOptions);
627                 if (resource.target === WI.pageTarget)
628                     this._addResourceToFrame(frame, resource);
629                 else if (resource.target)
630                     resource.target.addResource(resource);
631                 else
632                     this._addOrphanedResource(resource, resourceOptions.targetId);
633             }
634         } else {
635             // This is a new request for a new frame, which is always the main resource.
636             console.assert(WI.sharedApp.debuggableType !== WI.DebuggableType.ServiceWorker);
637             console.assert(!resourceOptions.targetId);
638             resource = new WI.Resource(url, resourceOptions);
639             frame = new WI.Frame(frameIdentifier, frameOptions.name, frameOptions.securityOrigin, resourceOptions.loaderIdentifier, resource);
640             this._frameIdentifierMap.set(frame.id, frame);
641
642             // If we don't have a main frame, assume this is it. This can change later in
643             // frameDidNavigate when the parent frame is known.
644             if (!this._mainFrame) {
645                 this._mainFrame = frame;
646                 this._mainFrameDidChange(null);
647             }
648
649             this._dispatchFrameWasAddedEvent(frame);
650         }
651
652         console.assert(resource);
653
654         return resource;
655     }
656
657     _addResourceToFrame(frame, resource)
658     {
659         console.assert(!this._waitingForMainFrameResourceTreePayload);
660         if (this._waitingForMainFrameResourceTreePayload)
661             return;
662
663         console.assert(frame);
664         console.assert(resource);
665
666         if (resource.loaderIdentifier !== frame.loaderIdentifier && !frame.provisionalLoaderIdentifier) {
667             // This is the start of a provisional load which happens before frameDidNavigate is called.
668             // This resource will be the new mainResource if frameDidNavigate is called.
669             frame.startProvisionalLoad(resource);
670             return;
671         }
672
673         // This is just another resource, either for the main loader or the provisional loader.
674         console.assert(resource.loaderIdentifier === frame.loaderIdentifier || resource.loaderIdentifier === frame.provisionalLoaderIdentifier);
675         frame.addResource(resource);
676     }
677
678     _addResourceToTarget(target, resource)
679     {
680         console.assert(target !== WI.pageTarget);
681         console.assert(resource);
682
683         target.addResource(resource);
684     }
685
686     _initiatorSourceCodeLocationFromPayload(initiatorPayload)
687     {
688         if (!initiatorPayload)
689             return null;
690
691         var url = null;
692         var lineNumber = NaN;
693         var columnNumber = 0;
694
695         if (initiatorPayload.stackTrace && initiatorPayload.stackTrace.length) {
696             var stackTracePayload = initiatorPayload.stackTrace;
697             for (var i = 0; i < stackTracePayload.length; ++i) {
698                 var callFramePayload = stackTracePayload[i];
699                 if (!callFramePayload.url || callFramePayload.url === "[native code]")
700                     continue;
701
702                 url = callFramePayload.url;
703
704                 // The lineNumber is 1-based, but we expect 0-based.
705                 lineNumber = callFramePayload.lineNumber - 1;
706
707                 columnNumber = callFramePayload.columnNumber;
708
709                 break;
710             }
711         } else if (initiatorPayload.url) {
712             url = initiatorPayload.url;
713
714             // The lineNumber is 1-based, but we expect 0-based.
715             lineNumber = initiatorPayload.lineNumber - 1;
716         }
717
718         if (!url || isNaN(lineNumber) || lineNumber < 0)
719             return null;
720
721         var sourceCode = WI.networkManager.resourceForURL(url);
722         if (!sourceCode)
723             sourceCode = WI.debuggerManager.scriptsForURL(url, WI.mainTarget)[0];
724
725         if (!sourceCode)
726             return null;
727
728         return sourceCode.createSourceCodeLocation(lineNumber, columnNumber);
729     }
730
731     _initiatorNodeFromPayload(initiatorPayload)
732     {
733         return WI.domManager.nodeForId(initiatorPayload.nodeId);
734     }
735
736     _processServiceWorkerConfiguration(error, initializationPayload)
737     {
738         console.assert(this._waitingForMainFrameResourceTreePayload);
739         this._waitingForMainFrameResourceTreePayload = false;
740
741         if (error) {
742             console.error(JSON.stringify(error));
743             return;
744         }
745
746         console.assert(initializationPayload.targetId.startsWith("serviceworker:"));
747
748         WI.mainTarget.identifier = initializationPayload.targetId;
749         WI.mainTarget.name = initializationPayload.url;
750
751         // Create a main resource with this content in case the content never shows up as a WI.Script.
752         const type = WI.Script.SourceType.Program;
753         let script = new WI.LocalScript(WI.mainTarget, initializationPayload.url, type, initializationPayload.content);
754         WI.mainTarget.mainResource = script;
755
756         InspectorBackend.runAfterPendingDispatches(() => {
757             if (WI.mainTarget.mainResource === script) {
758                 // We've now received all the scripts, if we don't have a better main resource use this LocalScript.
759                 WI.debuggerManager.dataForTarget(WI.mainTarget).addScript(script);
760                 WI.debuggerManager.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptAdded, {script});
761             }
762         });
763     }
764
765     _processMainFrameResourceTreePayload(error, mainFramePayload)
766     {
767         console.assert(this._waitingForMainFrameResourceTreePayload);
768         this._waitingForMainFrameResourceTreePayload = false;
769
770         if (error) {
771             console.error(JSON.stringify(error));
772             return;
773         }
774
775         console.assert(mainFramePayload);
776         console.assert(mainFramePayload.frame);
777
778         this._resourceRequestIdentifierMap = new Map;
779         this._frameIdentifierMap = new Map;
780
781         var oldMainFrame = this._mainFrame;
782
783         this._mainFrame = this._addFrameTreeFromFrameResourceTreePayload(mainFramePayload, true);
784
785         if (this._mainFrame !== oldMainFrame)
786             this._mainFrameDidChange(oldMainFrame);
787     }
788
789     _createFrame(payload)
790     {
791         // If payload.url is missing or empty then this page is likely the special empty page. In that case
792         // we will just say it is "about:blank" so we have a URL, which is required for resources.
793         let mainResource = new WI.Resource(payload.url || "about:blank", {
794             mimeType: payload.mimeType,
795             loaderIdentifier: payload.loaderId,
796         });
797         var frame = new WI.Frame(payload.id, payload.name, payload.securityOrigin, payload.loaderId, mainResource);
798
799         this._frameIdentifierMap.set(frame.id, frame);
800
801         mainResource.markAsFinished();
802
803         return frame;
804     }
805
806     _createResource(payload, framePayload)
807     {
808         let resource = new WI.Resource(payload.url, {
809             mimeType: payload.mimeType,
810             type: payload.type,
811             loaderIdentifier: framePayload.loaderId,
812             targetId: payload.targetId,
813         });
814
815         if (payload.sourceMapURL)
816             this.downloadSourceMap(payload.sourceMapURL, resource.url, resource);
817
818         return resource;
819     }
820
821     _addFrameTreeFromFrameResourceTreePayload(payload, isMainFrame)
822     {
823         var frame = this._createFrame(payload.frame);
824         if (isMainFrame)
825             frame.markAsMainFrame();
826
827         for (var i = 0; payload.childFrames && i < payload.childFrames.length; ++i)
828             frame.addChildFrame(this._addFrameTreeFromFrameResourceTreePayload(payload.childFrames[i], false));
829
830         for (var i = 0; payload.resources && i < payload.resources.length; ++i) {
831             var resourcePayload = payload.resources[i];
832
833             // The main resource is included as a resource. We can skip it since we already created
834             // a main resource when we created the Frame. The resource payload does not include anything
835             // didn't already get from the frame payload.
836             if (resourcePayload.type === "Document" && resourcePayload.url === payload.frame.url)
837                 continue;
838
839             var resource = this._createResource(resourcePayload, payload);
840             if (resource.target === WI.pageTarget)
841                 frame.addResource(resource);
842             else if (resource.target)
843                 resource.target.addResource(resource);
844             else
845                 this._addOrphanedResource(resource, resourcePayload.targetId);
846
847             if (resourcePayload.failed || resourcePayload.canceled)
848                 resource.markAsFailed(resourcePayload.canceled);
849             else
850                 resource.markAsFinished();
851         }
852
853         this._dispatchFrameWasAddedEvent(frame);
854
855         return frame;
856     }
857
858     _addOrphanedResource(resource, targetId)
859     {
860         let resources = this._orphanedResources.get(targetId);
861         if (!resources) {
862             resources = [];
863             this._orphanedResources.set(targetId, resources);
864         }
865
866         resources.push(resource);
867     }
868
869     _dispatchFrameWasAddedEvent(frame)
870     {
871         this.dispatchEventToListeners(WI.NetworkManager.Event.FrameWasAdded, {frame});
872     }
873
874     _mainFrameDidChange(oldMainFrame)
875     {
876         if (oldMainFrame)
877             oldMainFrame.unmarkAsMainFrame();
878         if (this._mainFrame)
879             this._mainFrame.markAsMainFrame();
880
881         this.dispatchEventToListeners(WI.NetworkManager.Event.MainFrameDidChange, {oldMainFrame});
882     }
883
884     _loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode)
885     {
886         this._downloadingSourceMaps.add(sourceMapURL);
887
888         let sourceMapLoaded = (error, content, mimeType, statusCode) => {
889             if (error || statusCode >= 400) {
890                 this._sourceMapLoadAndParseFailed(sourceMapURL);
891                 return;
892             }
893
894             if (content.slice(0, 3) === ")]}") {
895                 let firstNewlineIndex = content.indexOf("\n");
896                 if (firstNewlineIndex === -1) {
897                     this._sourceMapLoadAndParseFailed(sourceMapURL);
898                     return;
899                 }
900
901                 content = content.substring(firstNewlineIndex);
902             }
903
904             try {
905                 let payload = JSON.parse(content);
906                 let baseURL = sourceMapURL.startsWith("data:") ? originalSourceCode.url : sourceMapURL;
907                 let sourceMap = new WI.SourceMap(baseURL, payload, originalSourceCode);
908                 this._sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap);
909             } catch {
910                 this._sourceMapLoadAndParseFailed(sourceMapURL);
911             }
912         };
913
914         if (sourceMapURL.startsWith("data:")) {
915             let {mimeType, base64, data} = parseDataURL(sourceMapURL);
916             let content = base64 ? atob(data) : data;
917             sourceMapLoaded(null, content, mimeType, 0);
918             return;
919         }
920
921         if (!window.NetworkAgent) {
922             this._sourceMapLoadAndParseFailed(sourceMapURL);
923             return;
924         }
925
926         let frameIdentifier = null;
927         if (originalSourceCode instanceof WI.Resource && originalSourceCode.parentFrame)
928             frameIdentifier = originalSourceCode.parentFrame.id;
929
930         if (!frameIdentifier)
931             frameIdentifier = WI.networkManager.mainFrame ? WI.networkManager.mainFrame.id : "";
932
933         NetworkAgent.loadResource(frameIdentifier, sourceMapURL, sourceMapLoaded);
934     }
935
936     _sourceMapLoadAndParseFailed(sourceMapURL)
937     {
938         this._downloadingSourceMaps.delete(sourceMapURL);
939     }
940
941     _sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap)
942     {
943         if (!this._downloadingSourceMaps.has(sourceMapURL))
944             return;
945
946         this._downloadingSourceMaps.delete(sourceMapURL);
947
948         this._sourceMapURLMap.set(sourceMapURL, sourceMap);
949
950         for (let source of sourceMap.sources())
951             sourceMap.addResource(new WI.SourceMapResource(source, sourceMap));
952
953         // Associate the SourceMap with the originalSourceCode.
954         sourceMap.originalSourceCode.addSourceMap(sourceMap);
955
956         // If the originalSourceCode was not a Resource, be sure to also associate with the Resource if one exists.
957         // FIXME: We should try to use the right frame instead of a global lookup by URL.
958         if (!(sourceMap.originalSourceCode instanceof WI.Resource)) {
959             console.assert(sourceMap.originalSourceCode instanceof WI.Script);
960             let resource = sourceMap.originalSourceCode.resource;
961             if (resource)
962                 resource.addSourceMap(sourceMap);
963         }
964     }
965
966     _extraDomainsActivated(event)
967     {
968         if (event.data.domains.includes("Page") && window.PageAgent)
969             PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this));
970     }
971
972     _handleFrameMainResourceDidChange(event)
973     {
974         if (!event.target.isMainFrame())
975             return;
976
977         this._sourceMapURLMap.clear();
978         this._downloadingSourceMaps.clear();
979     }
980 };
981
982 WI.NetworkManager.Event = {
983     FrameWasAdded: "network-manager-frame-was-added",
984     FrameWasRemoved: "network-manager-frame-was-removed",
985     MainFrameDidChange: "network-manager-main-frame-did-change",
986 };