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