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