287724061a55353edcfe672fc0d4d3d6c42e62db
[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             initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator),
310             initiatorNode: this._initiatorNodeFromPayload(initiator),
311             originalRequestWillBeSentTimestamp,
312         });
313
314         // Associate the resource with the requestIdentifier so it can be found in future loading events.
315         this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
316     }
317
318     webSocketCreated(requestId, url)
319     {
320         this._webSocketIdentifierToURL.set(requestId, url);
321     }
322
323     webSocketWillSendHandshakeRequest(requestId, timestamp, walltime, request)
324     {
325         let url = this._webSocketIdentifierToURL.get(requestId);
326         console.assert(url);
327         if (!url)
328             return;
329
330         // COMPATIBILITY(iOS 10.3): `walltime` did not exist in 10.3 and earlier.
331         if (!InspectorBackend.domains.Network.hasEventParameter("webSocketWillSendHandshakeRequest", "walltime")) {
332             request = arguments[2];
333             walltime = NaN;
334         }
335
336         // FIXME: <webkit.org/b/168475> Web Inspector: Correctly display iframe's and worker's WebSockets
337
338         let resource = new WI.WebSocketResource(url, {
339             loaderIdentifier: WI.networkManager.mainFrame.id,
340             requestIdentifier: requestId,
341             requestHeaders: request.headers,
342             timestamp,
343             walltime,
344             requestSentTimestamp: WI.timelineManager.computeElapsedTime(timestamp),
345         });
346
347         let frame = this.frameForIdentifier(WI.networkManager.mainFrame.id);
348         frame.addResource(resource);
349
350         this._resourceRequestIdentifierMap.set(requestId, resource);
351     }
352
353     webSocketHandshakeResponseReceived(requestId, timestamp, response)
354     {
355         let resource = this._resourceRequestIdentifierMap.get(requestId);
356         console.assert(resource);
357         if (!resource)
358             return;
359
360         resource.readyState = WI.WebSocketResource.ReadyState.Open;
361
362         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
363
364         // FIXME: <webkit.org/b/169166> Web Inspector: WebSockets: Implement timing information
365         let responseTiming = response.timing || null;
366
367         resource.updateForResponse(resource.url, resource.mimeType, resource.type, response.headers, response.status, response.statusText, elapsedTime, responseTiming);
368
369         resource.markAsFinished(elapsedTime);
370     }
371
372     webSocketFrameReceived(requestId, timestamp, response)
373     {
374         this._webSocketFrameReceivedOrSent(requestId, timestamp, response);
375     }
376
377     webSocketFrameSent(requestId, timestamp, response)
378     {
379         this._webSocketFrameReceivedOrSent(requestId, timestamp, response);
380     }
381
382     webSocketClosed(requestId, timestamp)
383     {
384         let resource = this._resourceRequestIdentifierMap.get(requestId);
385         console.assert(resource);
386         if (!resource)
387             return;
388
389         resource.readyState = WI.WebSocketResource.ReadyState.Closed;
390
391         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
392         resource.markAsFinished(elapsedTime);
393
394         this._webSocketIdentifierToURL.delete(requestId);
395         this._resourceRequestIdentifierMap.delete(requestId);
396     }
397
398     _webSocketFrameReceivedOrSent(requestId, timestamp, response)
399     {
400         let resource = this._resourceRequestIdentifierMap.get(requestId);
401         console.assert(resource);
402         if (!resource)
403             return;
404
405         // Data going from the client to the server is always masked.
406         let isOutgoing = !!response.mask;
407
408         let {payloadData, payloadLength, opcode} = response;
409         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
410
411         resource.addFrame(payloadData, payloadLength, isOutgoing, opcode, timestamp, elapsedTime);
412     }
413
414     markResourceRequestAsServedFromMemoryCache(requestIdentifier)
415     {
416         // Called from WI.NetworkObserver.
417
418         // Ignore this while waiting for the whole frame/resource tree.
419         if (this._waitingForMainFrameResourceTreePayload)
420             return;
421
422         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
423
424         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
425         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
426         // just doesn't have a requestIdentifier for us to look it up.
427         if (!resource)
428             return;
429
430         resource.legacyMarkServedFromMemoryCache();
431     }
432
433     resourceRequestWasServedFromMemoryCache(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload, timestamp, initiator)
434     {
435         // Called from WI.NetworkObserver.
436
437         // Ignore this while waiting for the whole frame/resource tree.
438         if (this._waitingForMainFrameResourceTreePayload)
439             return;
440
441         console.assert(!this._resourceRequestIdentifierMap.has(requestIdentifier));
442
443         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
444         let response = cachedResourcePayload.response;
445         const responseSource = NetworkAgent.ResponseSource.MemoryCache;
446
447         let resource = this._addNewResourceToFrameOrTarget(cachedResourcePayload.url, frameIdentifier, {
448             type: cachedResourcePayload.type,
449             loaderIdentifier,
450             requestIdentifier,
451             requestMethod: "GET",
452             requestSentTimestamp: elapsedTime,
453             initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator),
454             initiatorNode: this._initiatorNodeFromPayload(initiator),
455         });
456         resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource, response.security);
457         resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime);
458         resource.increaseTransferSize(cachedResourcePayload.bodySize);
459         resource.setCachedResponseBodySize(cachedResourcePayload.bodySize);
460         resource.markAsFinished(elapsedTime);
461
462         console.assert(resource.cached, "This resource should be classified as cached since it was served from the MemoryCache", resource);
463
464         if (cachedResourcePayload.sourceMapURL)
465             this.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource);
466
467         // No need to associate the resource with the requestIdentifier, since this is the only event
468         // sent for memory cache resource loads.
469     }
470
471     resourceRequestDidReceiveResponse(requestIdentifier, frameIdentifier, loaderIdentifier, type, response, timestamp)
472     {
473         // Called from WI.NetworkObserver.
474
475         // Ignore this while waiting for the whole frame/resource tree.
476         if (this._waitingForMainFrameResourceTreePayload)
477             return;
478
479         var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
480         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
481
482         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
483         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
484         // just doesn't have a requestIdentifier for us to look it up, but we can try to look it up by its URL.
485         if (!resource) {
486             var frame = this.frameForIdentifier(frameIdentifier);
487             if (frame)
488                 resource = frame.resourceForURL(response.url);
489
490             // If we find the resource this way we had marked it earlier as finished via PageAgent.getResourceTree.
491             // Associate the resource with the requestIdentifier so it can be found in future loading events.
492             // and roll it back to an unfinished state, we know now it is still loading.
493             if (resource) {
494                 this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
495                 resource.revertMarkAsFinished();
496             }
497         }
498
499         // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
500         // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
501         if (!resource) {
502             resource = this._addNewResourceToFrameOrTarget(response.url, frameIdentifier, {
503                 type,
504                 loaderIdentifier,
505                 requestIdentifier,
506                 requestHeaders: response.requestHeaders,
507                 requestSentTimestamp: elapsedTime,
508             });
509
510             // Associate the resource with the requestIdentifier so it can be found in future loading events.
511             this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
512         }
513
514         // COMPATIBILITY (iOS 10.3): `fromDiskCache` is legacy, replaced by `source`.
515         if (response.fromDiskCache)
516             resource.legacyMarkServedFromDiskCache();
517
518         resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing, response.source, response.security);
519     }
520
521     resourceRequestDidReceiveData(requestIdentifier, dataLength, encodedDataLength, timestamp)
522     {
523         // Called from WI.NetworkObserver.
524
525         // Ignore this while waiting for the whole frame/resource tree.
526         if (this._waitingForMainFrameResourceTreePayload)
527             return;
528
529         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
530         var elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
531
532         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
533         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
534         // just doesn't have a requestIdentifier for us to look it up.
535         if (!resource)
536             return;
537
538         resource.increaseSize(dataLength, elapsedTime);
539
540         if (encodedDataLength !== -1)
541             resource.increaseTransferSize(encodedDataLength);
542     }
543
544     resourceRequestDidFinishLoading(requestIdentifier, timestamp, sourceMapURL, metrics)
545     {
546         // Called from WI.NetworkObserver.
547
548         // Ignore this while waiting for the whole frame/resource tree.
549         if (this._waitingForMainFrameResourceTreePayload)
550             return;
551
552         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
553         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
554         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
555         console.assert(resource);
556         if (!resource)
557             return;
558
559         if (metrics)
560             resource.updateWithMetrics(metrics);
561
562         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
563         resource.markAsFinished(elapsedTime);
564
565         if (sourceMapURL)
566             this.downloadSourceMap(sourceMapURL, resource.url, resource);
567
568         this._resourceRequestIdentifierMap.delete(requestIdentifier);
569     }
570
571     resourceRequestDidFailLoading(requestIdentifier, canceled, timestamp, errorText)
572     {
573         // Called from WI.NetworkObserver.
574
575         // Ignore this while waiting for the whole frame/resource tree.
576         if (this._waitingForMainFrameResourceTreePayload)
577             return;
578
579         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
580         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
581         let resource = this._resourceRequestIdentifierMap.get(requestIdentifier);
582         console.assert(resource);
583         if (!resource)
584             return;
585
586         let elapsedTime = WI.timelineManager.computeElapsedTime(timestamp);
587         resource.markAsFailed(canceled, elapsedTime, errorText);
588
589         if (resource.parentFrame && resource === resource.parentFrame.provisionalMainResource)
590             resource.parentFrame.clearProvisionalLoad();
591
592         this._resourceRequestIdentifierMap.delete(requestIdentifier);
593     }
594
595     executionContextCreated(contextPayload)
596     {
597         // Called from WI.RuntimeObserver.
598
599         let frame = this.frameForIdentifier(contextPayload.frameId);
600         console.assert(frame);
601         if (!frame)
602             return;
603
604         let displayName = contextPayload.name || frame.mainResource.displayName;
605         let target = frame.mainResource.target;
606         let executionContext = new WI.ExecutionContext(target, contextPayload.id, displayName, contextPayload.isPageContext, frame);
607         frame.addExecutionContext(executionContext);
608     }
609
610     localResourceForURL(url)
611     {
612         return this._localResourcesMap.get(url);
613     }
614
615     resourceForURL(url)
616     {
617         if (!this._mainFrame)
618             return null;
619
620         if (this._mainFrame.mainResource.url === url)
621             return this._mainFrame.mainResource;
622
623         return this._mainFrame.resourceForURL(url, true);
624     }
625
626     adoptOrphanedResourcesForTarget(target)
627     {
628         let resources = this._orphanedResources.take(target.identifier);
629         if (!resources)
630             return;
631
632         for (let resource of resources)
633             target.adoptResource(resource);
634     }
635
636     processHAR({json, error})
637     {
638         if (error) {
639             WI.NetworkManager.synthesizeImportError(error);
640             return null;
641         }
642
643         if (typeof json !== "object" || json === null) {
644             WI.NetworkManager.synthesizeImportError(WI.UIString("invalid JSON"));
645             return null;
646         }
647
648         if (typeof json.log !== "object" || typeof json.log.version !== "string") {
649             WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR"));
650             return null;
651         }
652
653         if (json.log.version !== "1.2") {
654             WI.NetworkManager.synthesizeImportError(WI.UIString("unsupported HAR version"));
655             return null;
656         }
657
658         if (!Array.isArray(json.log.entries) || !Array.isArray(json.log.pages) || !json.log.pages[0] || !json.log.pages[0].startedDateTime) {
659             WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR"));
660             return null;
661         }
662
663         let mainResourceSentWalltime = WI.HARBuilder.dateFromHARDate(json.log.pages[0].startedDateTime) / 1000;
664         if (isNaN(mainResourceSentWalltime)) {
665             WI.NetworkManager.synthesizeImportError(WI.UIString("invalid HAR"));
666             return null;
667         }
668
669         let localResources = [];
670
671         for (let entry of json.log.entries) {
672             let localResource = WI.LocalResource.fromHAREntry(entry, mainResourceSentWalltime);
673             this._localResourcesMap.set(localResource.url, localResource);
674             localResources.push(localResource);
675         }
676
677         return localResources;
678     }
679
680     // Private
681
682     _addNewResourceToFrameOrTarget(url, frameIdentifier, resourceOptions = {}, frameOptions = {})
683     {
684         console.assert(!this._waitingForMainFrameResourceTreePayload);
685
686         let resource = null;
687
688         if (!frameIdentifier && resourceOptions.targetId) {
689             // This is a new resource for a ServiceWorker target.
690             console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker);
691             console.assert(resourceOptions.targetId === WI.mainTarget.identifier);
692             resource = new WI.Resource(url, resourceOptions);
693             resource.target.addResource(resource);
694             return resource;
695         }
696
697         let frame = this.frameForIdentifier(frameIdentifier);
698         if (frame) {
699             // This is a new request for an existing frame, which might be the main resource or a new resource.
700             if (resourceOptions.type === "Document" && frame.mainResource.url === url && frame.loaderIdentifier === resourceOptions.loaderIdentifier)
701                 resource = frame.mainResource;
702             else if (resourceOptions.type === "Document" && frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === resourceOptions.loaderIdentifier)
703                 resource = frame.provisionalMainResource;
704             else {
705                 resource = new WI.Resource(url, resourceOptions);
706                 if (resource.target === WI.pageTarget)
707                     this._addResourceToFrame(frame, resource);
708                 else if (resource.target)
709                     resource.target.addResource(resource);
710                 else
711                     this._addOrphanedResource(resource, resourceOptions.targetId);
712             }
713         } else {
714             // This is a new request for a new frame, which is always the main resource.
715             console.assert(WI.sharedApp.debuggableType !== WI.DebuggableType.ServiceWorker);
716             console.assert(!resourceOptions.targetId);
717             resource = new WI.Resource(url, resourceOptions);
718             frame = new WI.Frame(frameIdentifier, frameOptions.name, frameOptions.securityOrigin, resourceOptions.loaderIdentifier, resource);
719             this._frameIdentifierMap.set(frame.id, frame);
720
721             // If we don't have a main frame, assume this is it. This can change later in
722             // frameDidNavigate when the parent frame is known.
723             if (!this._mainFrame) {
724                 this._mainFrame = frame;
725                 this._mainFrameDidChange(null);
726             }
727
728             this._dispatchFrameWasAddedEvent(frame);
729         }
730
731         console.assert(resource);
732
733         return resource;
734     }
735
736     _addResourceToFrame(frame, resource)
737     {
738         console.assert(!this._waitingForMainFrameResourceTreePayload);
739         if (this._waitingForMainFrameResourceTreePayload)
740             return;
741
742         console.assert(frame);
743         console.assert(resource);
744
745         if (resource.loaderIdentifier !== frame.loaderIdentifier && !frame.provisionalLoaderIdentifier) {
746             // This is the start of a provisional load which happens before frameDidNavigate is called.
747             // This resource will be the new mainResource if frameDidNavigate is called.
748             frame.startProvisionalLoad(resource);
749             return;
750         }
751
752         // This is just another resource, either for the main loader or the provisional loader.
753         console.assert(resource.loaderIdentifier === frame.loaderIdentifier || resource.loaderIdentifier === frame.provisionalLoaderIdentifier);
754         frame.addResource(resource);
755     }
756
757     _addResourceToTarget(target, resource)
758     {
759         console.assert(target !== WI.pageTarget);
760         console.assert(resource);
761
762         target.addResource(resource);
763     }
764
765     _initiatorSourceCodeLocationFromPayload(initiatorPayload)
766     {
767         if (!initiatorPayload)
768             return null;
769
770         var url = null;
771         var lineNumber = NaN;
772         var columnNumber = 0;
773
774         if (initiatorPayload.stackTrace && initiatorPayload.stackTrace.length) {
775             var stackTracePayload = initiatorPayload.stackTrace;
776             for (var i = 0; i < stackTracePayload.length; ++i) {
777                 var callFramePayload = stackTracePayload[i];
778                 if (!callFramePayload.url || callFramePayload.url === "[native code]")
779                     continue;
780
781                 url = callFramePayload.url;
782
783                 // The lineNumber is 1-based, but we expect 0-based.
784                 lineNumber = callFramePayload.lineNumber - 1;
785
786                 columnNumber = callFramePayload.columnNumber;
787
788                 break;
789             }
790         } else if (initiatorPayload.url) {
791             url = initiatorPayload.url;
792
793             // The lineNumber is 1-based, but we expect 0-based.
794             lineNumber = initiatorPayload.lineNumber - 1;
795         }
796
797         if (!url || isNaN(lineNumber) || lineNumber < 0)
798             return null;
799
800         var sourceCode = WI.networkManager.resourceForURL(url);
801         if (!sourceCode)
802             sourceCode = WI.debuggerManager.scriptsForURL(url, WI.mainTarget)[0];
803
804         if (!sourceCode)
805             return null;
806
807         return sourceCode.createSourceCodeLocation(lineNumber, columnNumber);
808     }
809
810     _initiatorNodeFromPayload(initiatorPayload)
811     {
812         return WI.domManager.nodeForId(initiatorPayload.nodeId);
813     }
814
815     _processServiceWorkerConfiguration(error, initializationPayload)
816     {
817         console.assert(this._waitingForMainFrameResourceTreePayload);
818         this._waitingForMainFrameResourceTreePayload = false;
819
820         if (error) {
821             console.error(JSON.stringify(error));
822             return;
823         }
824
825         console.assert(initializationPayload.targetId.startsWith("serviceworker:"));
826
827         WI.mainTarget.identifier = initializationPayload.targetId;
828         WI.mainTarget.name = initializationPayload.url;
829
830         // Create a main resource with this content in case the content never shows up as a WI.Script.
831         const type = WI.Script.SourceType.Program;
832         let script = new WI.LocalScript(WI.mainTarget, initializationPayload.url, type, initializationPayload.content);
833         WI.mainTarget.mainResource = script;
834
835         InspectorBackend.runAfterPendingDispatches(() => {
836             if (WI.mainTarget.mainResource === script) {
837                 // We've now received all the scripts, if we don't have a better main resource use this LocalScript.
838                 WI.debuggerManager.dataForTarget(WI.mainTarget).addScript(script);
839                 WI.debuggerManager.dispatchEventToListeners(WI.DebuggerManager.Event.ScriptAdded, {script});
840             }
841         });
842     }
843
844     _processMainFrameResourceTreePayload(error, mainFramePayload)
845     {
846         console.assert(this._waitingForMainFrameResourceTreePayload);
847         this._waitingForMainFrameResourceTreePayload = false;
848
849         if (error) {
850             console.error(JSON.stringify(error));
851             return;
852         }
853
854         console.assert(mainFramePayload);
855         console.assert(mainFramePayload.frame);
856
857         this._resourceRequestIdentifierMap = new Map;
858         this._frameIdentifierMap = new Map;
859
860         var oldMainFrame = this._mainFrame;
861
862         this._mainFrame = this._addFrameTreeFromFrameResourceTreePayload(mainFramePayload, true);
863
864         if (this._mainFrame !== oldMainFrame)
865             this._mainFrameDidChange(oldMainFrame);
866
867         // Emulate a main resource change within this page even though we are swapping out main frames.
868         // This is because many managers listen only for main resource change events to perform work,
869         // but they don't listen for main frame changes.
870         if (this._transitioningPageTarget) {
871             this._transitioningPageTarget = false;
872             this._mainFrame._dispatchMainResourceDidChangeEvent(oldMainFrame.mainResource);
873         }
874     }
875
876     _createFrame(payload)
877     {
878         // If payload.url is missing or empty then this page is likely the special empty page. In that case
879         // we will just say it is "about:blank" so we have a URL, which is required for resources.
880         let mainResource = new WI.Resource(payload.url || "about:blank", {
881             mimeType: payload.mimeType,
882             loaderIdentifier: payload.loaderId,
883         });
884         var frame = new WI.Frame(payload.id, payload.name, payload.securityOrigin, payload.loaderId, mainResource);
885
886         this._frameIdentifierMap.set(frame.id, frame);
887
888         mainResource.markAsFinished();
889
890         return frame;
891     }
892
893     _createResource(payload, framePayload)
894     {
895         let resource = new WI.Resource(payload.url, {
896             mimeType: payload.mimeType,
897             type: payload.type,
898             loaderIdentifier: framePayload.loaderId,
899             targetId: payload.targetId,
900         });
901
902         if (payload.sourceMapURL)
903             this.downloadSourceMap(payload.sourceMapURL, resource.url, resource);
904
905         return resource;
906     }
907
908     _addFrameTreeFromFrameResourceTreePayload(payload, isMainFrame)
909     {
910         var frame = this._createFrame(payload.frame);
911         if (isMainFrame)
912             frame.markAsMainFrame();
913
914         for (var i = 0; payload.childFrames && i < payload.childFrames.length; ++i)
915             frame.addChildFrame(this._addFrameTreeFromFrameResourceTreePayload(payload.childFrames[i], false));
916
917         for (var i = 0; payload.resources && i < payload.resources.length; ++i) {
918             var resourcePayload = payload.resources[i];
919
920             // The main resource is included as a resource. We can skip it since we already created
921             // a main resource when we created the Frame. The resource payload does not include anything
922             // didn't already get from the frame payload.
923             if (resourcePayload.type === "Document" && resourcePayload.url === payload.frame.url)
924                 continue;
925
926             var resource = this._createResource(resourcePayload, payload);
927             if (resource.target === WI.pageTarget)
928                 frame.addResource(resource);
929             else if (resource.target)
930                 resource.target.addResource(resource);
931             else
932                 this._addOrphanedResource(resource, resourcePayload.targetId);
933
934             if (resourcePayload.failed || resourcePayload.canceled)
935                 resource.markAsFailed(resourcePayload.canceled);
936             else
937                 resource.markAsFinished();
938         }
939
940         this._dispatchFrameWasAddedEvent(frame);
941
942         return frame;
943     }
944
945     _addOrphanedResource(resource, targetId)
946     {
947         let resources = this._orphanedResources.get(targetId);
948         if (!resources) {
949             resources = [];
950             this._orphanedResources.set(targetId, resources);
951         }
952
953         resources.push(resource);
954     }
955
956     _dispatchFrameWasAddedEvent(frame)
957     {
958         this.dispatchEventToListeners(WI.NetworkManager.Event.FrameWasAdded, {frame});
959     }
960
961     _mainFrameDidChange(oldMainFrame)
962     {
963         if (oldMainFrame)
964             oldMainFrame.unmarkAsMainFrame();
965         if (this._mainFrame)
966             this._mainFrame.markAsMainFrame();
967
968         this.dispatchEventToListeners(WI.NetworkManager.Event.MainFrameDidChange, {oldMainFrame});
969     }
970
971     _loadAndParseSourceMap(sourceMapURL, baseURL, originalSourceCode)
972     {
973         this._downloadingSourceMaps.add(sourceMapURL);
974
975         let sourceMapLoaded = (error, content, mimeType, statusCode) => {
976             if (error || statusCode >= 400) {
977                 this._sourceMapLoadAndParseFailed(sourceMapURL);
978                 return;
979             }
980
981             if (content.slice(0, 3) === ")]}") {
982                 let firstNewlineIndex = content.indexOf("\n");
983                 if (firstNewlineIndex === -1) {
984                     this._sourceMapLoadAndParseFailed(sourceMapURL);
985                     return;
986                 }
987
988                 content = content.substring(firstNewlineIndex);
989             }
990
991             try {
992                 let payload = JSON.parse(content);
993                 let baseURL = sourceMapURL.startsWith("data:") ? originalSourceCode.url : sourceMapURL;
994                 let sourceMap = new WI.SourceMap(baseURL, payload, originalSourceCode);
995                 this._sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap);
996             } catch {
997                 this._sourceMapLoadAndParseFailed(sourceMapURL);
998             }
999         };
1000
1001         if (sourceMapURL.startsWith("data:")) {
1002             let {mimeType, base64, data} = parseDataURL(sourceMapURL);
1003             let content = base64 ? atob(data) : data;
1004             sourceMapLoaded(null, content, mimeType, 0);
1005             return;
1006         }
1007
1008         if (!window.NetworkAgent) {
1009             this._sourceMapLoadAndParseFailed(sourceMapURL);
1010             return;
1011         }
1012
1013         let frameIdentifier = null;
1014         if (originalSourceCode instanceof WI.Resource && originalSourceCode.parentFrame)
1015             frameIdentifier = originalSourceCode.parentFrame.id;
1016
1017         if (!frameIdentifier)
1018             frameIdentifier = WI.networkManager.mainFrame ? WI.networkManager.mainFrame.id : "";
1019
1020         NetworkAgent.loadResource(frameIdentifier, sourceMapURL, sourceMapLoaded);
1021     }
1022
1023     _sourceMapLoadAndParseFailed(sourceMapURL)
1024     {
1025         this._downloadingSourceMaps.delete(sourceMapURL);
1026     }
1027
1028     _sourceMapLoadAndParseSucceeded(sourceMapURL, sourceMap)
1029     {
1030         if (!this._downloadingSourceMaps.has(sourceMapURL))
1031             return;
1032
1033         this._downloadingSourceMaps.delete(sourceMapURL);
1034
1035         this._sourceMapURLMap.set(sourceMapURL, sourceMap);
1036
1037         for (let source of sourceMap.sources())
1038             sourceMap.addResource(new WI.SourceMapResource(source, sourceMap));
1039
1040         // Associate the SourceMap with the originalSourceCode.
1041         sourceMap.originalSourceCode.addSourceMap(sourceMap);
1042
1043         // If the originalSourceCode was not a Resource, be sure to also associate with the Resource if one exists.
1044         // FIXME: We should try to use the right frame instead of a global lookup by URL.
1045         if (!(sourceMap.originalSourceCode instanceof WI.Resource)) {
1046             console.assert(sourceMap.originalSourceCode instanceof WI.Script);
1047             let resource = sourceMap.originalSourceCode.resource;
1048             if (resource)
1049                 resource.addSourceMap(sourceMap);
1050         }
1051     }
1052
1053     _extraDomainsActivated(event)
1054     {
1055         if (event.data.domains.includes("Page") && window.PageAgent)
1056             PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this));
1057     }
1058
1059     _handleFrameMainResourceDidChange(event)
1060     {
1061         if (!event.target.isMainFrame())
1062             return;
1063
1064         this._sourceMapURLMap.clear();
1065         this._downloadingSourceMaps.clear();
1066     }
1067 };
1068
1069 WI.NetworkManager.Event = {
1070     FrameWasAdded: "network-manager-frame-was-added",
1071     FrameWasRemoved: "network-manager-frame-was-removed",
1072     MainFrameDidChange: "network-manager-main-frame-did-change",
1073 };