Hook up the initiator info and show it in the Resource details sidebar.
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / FrameResourceManager.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 WebInspector.FrameResourceManager = function()
27 {
28     WebInspector.Object.call(this);
29
30     PageAgent.enable();
31     NetworkAgent.enable();
32
33     this.initialize();
34 };
35
36 WebInspector.Object.addConstructorFunctions(WebInspector.FrameResourceManager);
37
38 WebInspector.FrameResourceManager.Event = {
39     FrameWasAdded: "frame-resource-manager-frame-was-added",
40     FrameWasRemoved: "frame-resource-manager-frame-was-removed",
41     MainFrameDidChange: "frame-resource-manager-main-frame-did-change"
42 };
43
44 WebInspector.FrameResourceManager.prototype = {
45     constructor: WebInspector.FrameResourceManager,
46
47     // Public
48
49     initialize: function()
50     {
51         var oldMainFrame = this._mainFrame;
52
53         this._frameIdentifierMap = {};
54         this._mainFrame = null;
55         this._resourceRequestIdentifierMap = {};
56
57         if (this._mainFrame !== oldMainFrame)
58             this._mainFrameDidChange(oldMainFrame);
59
60         this._waitingForMainFrameResourceTreePayload = true;
61         PageAgent.getResourceTree(this._processMainFrameResourceTreePayload.bind(this));
62     },
63
64     get mainFrame()
65     {
66         return this._mainFrame;
67     },
68
69     get frames()
70     {
71         var frames = [];
72         for (var key in this._frameIdentifierMap)
73             frames.push(this._frameIdentifierMap[key]);
74
75         return frames;
76     },
77
78     frameForIdentifier: function(frameId)
79     {
80         return this._frameIdentifierMap[frameId] || null;
81     },
82
83     frameDidNavigate: function(framePayload)
84     {
85         // Called from WebInspector.PageObserver.
86
87         // Ignore this while waiting for the whole frame/resource tree.
88         if (this._waitingForMainFrameResourceTreePayload)
89             return;
90
91         var frameWasLoadedInstantly = false;
92
93         var frame = this.frameForIdentifier(framePayload.id);
94         if (!frame) {
95             // If the frame wasn't known before now, then the main resource was loaded instantly (about:blank, etc.)
96             // Make a new resource (which will make the frame). Mark will mark it as loaded at the end too since we
97             // don't expect any more events about the load finishing for these frames.
98             var frameResource = this._addNewResourceToFrame(null, framePayload.id, framePayload.loaderId, framePayload.url, null, null, null, null, null, framePayload.name, framePayload.securityOrigin);
99             frame = frameResource.parentFrame;
100             frameWasLoadedInstantly = true;
101
102             console.assert(frame);
103             if (!frame)
104                 return;
105         }
106
107         if (framePayload.loaderId === frame.provisionalLoaderIdentifier) {
108             // There was a provisional load in progress, commit it.
109             frame.commitProvisionalLoad(framePayload.securityOrigin);
110         } else {
111             if (frame.mainResource.url !== framePayload.url || frame.loaderIdentifier !== framePayload.loaderId) {
112                 // Navigations like back/forward do not have provisional loads, so create a new main resource here.
113                 var mainResource = new WebInspector.Resource(framePayload.url, framePayload.mimeType, null, framePayload.loaderId);
114             } else {
115                 // The main resource is already correct, so reuse it.
116                 var mainResource = frame.mainResource;
117             }
118
119             frame.initialize(framePayload.name, framePayload.securityOrigin, framePayload.loaderId, mainResource);
120         }
121
122         var oldMainFrame = this._mainFrame;
123
124         if (framePayload.parentId) {
125             var parentFrame = this.frameForIdentifier(framePayload.parentId);
126             console.assert(parentFrame);
127
128             if (frame === this._mainFrame)
129                 this._mainFrame = null;
130
131             if (frame.parentFrame !== parentFrame)
132                 parentFrame.addChildFrame(frame);
133         } else {
134             if (frame.parentFrame)
135                 frame.parentFrame.removeChildFrame(frame);
136             this._mainFrame = frame;
137         }
138
139         if (this._mainFrame !== oldMainFrame)
140             this._mainFrameDidChange(oldMainFrame);
141
142         if (frameWasLoadedInstantly)
143             frame.mainResource.markAsFinished();
144     },
145
146     frameDidDetach: function(frameId)
147     {
148         // Called from WebInspector.PageObserver.
149
150         // Ignore this while waiting for the whole frame/resource tree.
151         if (this._waitingForMainFrameResourceTreePayload)
152             return;
153
154         var frame = this.frameForIdentifier(frameId);
155         if (!frame)
156             return;
157
158         if (frame.parentFrame)
159             frame.parentFrame.removeChildFrame(frame);
160
161         delete this._frameIdentifierMap[frame.id];
162
163         var oldMainFrame = this._mainFrame;
164
165         if (frame === this._mainFrame)
166             this._mainFrame = null;
167
168         frame.clearExecutionContexts();
169
170         this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.FrameWasRemoved, {frame: frame});
171
172         if (this._mainFrame !== oldMainFrame)
173             this._mainFrameDidChange(oldMainFrame);
174     },
175
176     resourceRequestWillBeSent: function(requestIdentifier, frameIdentifier, loaderIdentifier, request, type, redirectResponse, timestamp, initiator)
177     {
178         // Called from WebInspector.NetworkObserver.
179
180         // Ignore this while waiting for the whole frame/resource tree.
181         if (this._waitingForMainFrameResourceTreePayload)
182             return;
183
184         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
185         if (resource) {
186             // This is an existing request which is being redirected, update the resource.
187             console.assert(redirectResponse);
188             resource.updateForRedirectResponse(request.url, request.headers, timestamp);
189             return;
190         }
191
192         var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
193
194         // This is a new request, make a new resource and add it to the right frame.
195         resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, timestamp, null, null, initiatorSourceCodeLocation);
196
197         // Associate the resource with the requestIdentifier so it can be found in future loading events.
198         this._resourceRequestIdentifierMap[requestIdentifier] = resource;
199     },
200
201     markResourceRequestAsServedFromMemoryCache: function(requestIdentifier)
202     {
203         // Called from WebInspector.NetworkObserver.
204
205         // Ignore this while waiting for the whole frame/resource tree.
206         if (this._waitingForMainFrameResourceTreePayload)
207             return;
208
209         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
210
211         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
212         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
213         // just doesn't have a requestIdentifier for us to look it up.
214         if (!resource)
215             return;
216
217         resource.markAsCached();
218     },
219
220     resourceRequestWasServedFromMemoryCache: function(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload, timestamp, initiator)
221     {
222         // Called from WebInspector.NetworkObserver.
223
224         // Ignore this while waiting for the whole frame/resource tree.
225         if (this._waitingForMainFrameResourceTreePayload)
226             return;
227
228         console.assert(!(requestIdentifier in this._resourceRequestIdentifierMap));
229
230         var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
231
232         var response = cachedResourcePayload.response;
233         var resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, null, null, timestamp, null, null, initiatorSourceCodeLocation);
234         resource.markAsCached();
235         resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, timestamp);
236         resource.markAsFinished(timestamp);
237
238         if (cachedResourcePayload.sourceMapURL)
239             WebInspector.sourceMapManager.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource);
240
241         // No need to associate the resource with the requestIdentifier, since this is the only event
242         // sent for memory cache resource loads.
243     },
244
245     resourceRequestDidReceiveResponse: function(requestIdentifier, frameIdentifier, loaderIdentifier, type, response, timestamp)
246     {
247         // Called from WebInspector.NetworkObserver.
248
249         // Ignore this while waiting for the whole frame/resource tree.
250         if (this._waitingForMainFrameResourceTreePayload)
251             return;
252
253         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
254
255         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
256         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
257         // just doesn't have a requestIdentifier for us to look it up, but we can try to look it up by its URL.
258         if (!resource) {
259             var frame = this.frameForIdentifier(frameIdentifier);
260             if (frame)
261                 resource = frame.resourceForURL(response.url);
262
263             // If we find the resource this way we had marked it earlier as finished via PageAgent.getResourceTree.
264             // Associate the resource with the requestIdentifier so it can be found in future loading events.
265             // and roll it back to an unfinished state, we know now it is still loading.
266             if (resource) {
267                 this._resourceRequestIdentifierMap[requestIdentifier] = resource;
268                 resource.revertMarkAsFinished();
269             }
270         }
271
272         // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
273         // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
274         if (!resource) {
275             resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, timestamp, null, null);
276
277             // Associate the resource with the requestIdentifier so it can be found in future loading events.
278             this._resourceRequestIdentifierMap[requestIdentifier] = resource;
279         }
280
281         if (response.fromDiskCache)
282             resource.markAsCached();
283
284         resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, timestamp);
285     },
286
287     resourceRequestDidReceiveData: function(requestIdentifier, dataLength, encodedDataLength, timestamp)
288     {
289         // Called from WebInspector.NetworkObserver.
290
291         // Ignore this while waiting for the whole frame/resource tree.
292         if (this._waitingForMainFrameResourceTreePayload)
293             return;
294
295         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
296
297         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
298         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
299         // just doesn't have a requestIdentifier for us to look it up.
300         if (!resource)
301             return;
302
303         resource.increaseSize(dataLength, timestamp);
304
305         if (encodedDataLength !== -1)
306             resource.increaseTransferSize(encodedDataLength);
307     },
308
309     resourceRequestDidFinishLoading: function(requestIdentifier, timestamp, sourceMapURL)
310     {
311         // Called from WebInspector.NetworkObserver.
312
313         // Ignore this while waiting for the whole frame/resource tree.
314         if (this._waitingForMainFrameResourceTreePayload)
315             return;
316
317         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
318         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
319         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
320         console.assert(resource);
321         if (!resource)
322             return;
323
324         resource.markAsFinished(timestamp);
325
326         if (sourceMapURL)
327             WebInspector.sourceMapManager.downloadSourceMap(sourceMapURL, resource.url, resource);
328
329         delete this._resourceRequestIdentifierMap[requestIdentifier];
330     },
331
332     resourceRequestDidFailLoading: function(requestIdentifier, canceled, timestamp)
333     {
334         // Called from WebInspector.NetworkObserver.
335
336         // Ignore this while waiting for the whole frame/resource tree.
337         if (this._waitingForMainFrameResourceTreePayload)
338             return;
339
340         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
341         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
342         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
343         console.assert(resource);
344         if (!resource)
345             return;
346
347         resource.markAsFailed(canceled, timestamp);
348
349         if (resource === resource.parentFrame.provisionalMainResource)
350             resource.parentFrame.clearProvisionalLoad();
351
352         delete this._resourceRequestIdentifierMap[requestIdentifier];
353     },
354
355     executionContextCreated: function(contextPayload)
356     {
357         // Called from WebInspector.RuntimeObserver.
358
359         var frame = this.frameForIdentifier(contextPayload.frameId);
360         console.assert(frame);
361         if (!frame)
362             return;
363
364         var displayName = contextPayload.name || frame.mainResource.displayName;
365         var executionContext = new WebInspector.ExecutionContext(contextPayload.id, displayName, contextPayload.isPageContext, frame);
366         frame.addExecutionContext(executionContext);
367     },
368
369     resourceForURL: function(url)
370     {
371         if (!this._mainFrame)
372             return null;
373
374         if (this._mainFrame.mainResource.url === url)
375             return this._mainFrame.mainResource;
376
377         return this._mainFrame.resourceForURL(url, true);
378     },
379
380     // Private
381
382     _addNewResourceToFrame: function(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, timestamp, frameName, frameSecurityOrigin, initiatorSourceCodeLocation)
383     {
384         console.assert(!this._waitingForMainFrameResourceTreePayload);
385         if (this._waitingForMainFrameResourceTreePayload)
386             return;
387
388         var resource = null;
389
390         var frame = this.frameForIdentifier(frameIdentifier);
391         if (frame) {
392             // This is a new request for an existing frame, which might be the main resource or a new resource.
393             if (frame.mainResource.url === url && frame.loaderIdentifier === loaderIdentifier)
394                 resource = frame.mainResource;
395             else if (frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === loaderIdentifier)
396                 resource = frame.provisionalMainResource;
397             else {
398                 resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, initiatorSourceCodeLocation);
399                 this._addResourceToFrame(frame, resource);
400             }
401         } else {
402             // This is a new request for a new frame, which is always the main resource.
403             resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, initiatorSourceCodeLocation);
404             frame = new WebInspector.Frame(frameIdentifier, frameName, frameSecurityOrigin, loaderIdentifier, resource);
405             this._frameIdentifierMap[frame.id] = frame;
406
407             // If we don't have a main frame, assume this is it. This can change later in
408             // frameDidNavigate when the parent frame is known.
409             if (!this._mainFrame) {
410                 this._mainFrame = frame;
411                 this._mainFrameDidChange(null);
412             }
413
414             this._dispatchFrameWasAddedEvent(frame);
415         }
416
417         console.assert(resource);
418
419         return resource;
420     },
421
422     _addResourceToFrame: function(frame, resource)
423     {
424         console.assert(!this._waitingForMainFrameResourceTreePayload);
425         if (this._waitingForMainFrameResourceTreePayload)
426             return;
427
428         console.assert(frame);
429         console.assert(resource);
430
431         if (resource.loaderIdentifier !== frame.loaderIdentifier && !frame.provisionalLoaderIdentifier) {
432             // This is the start of a provisional load which happens before frameDidNavigate is called.
433             // This resource will be the new mainResource if frameDidNavigate is called.
434             frame.startProvisionalLoad(resource);
435             return;
436         }
437
438         // This is just another resource, either for the main loader or the provisional loader.
439         console.assert(resource.loaderIdentifier === frame.loaderIdentifier || resource.loaderIdentifier === frame.provisionalLoaderIdentifier);
440         frame.addResource(resource);
441     },
442
443     _initiatorSourceCodeLocationFromPayload: function(initiatorPayload)
444     {
445         if (!initiatorPayload)
446             return null;
447
448         var url = null;
449         var lineNumber = NaN;
450         var columnNumber = 0;
451
452         if (initiatorPayload.stackTrace && initiatorPayload.stackTrace.length) {
453             var stackTracePayload = initiatorPayload.stackTrace;
454             for (var i = 0; i < stackTracePayload.length; ++i) {
455                 var callFramePayload = stackTracePayload[i];
456                 if (!callFramePayload.url || callFramePayload.url === "[native code]")
457                     continue;
458
459                 url = callFramePayload.url;
460
461                 // The lineNumber is 1-based, but we expect 0-based.
462                 lineNumber = callFramePayload.lineNumber - 1;
463
464                 columnNumber = callFramePayload.columnNumber;
465
466                 break;
467             }
468         } else if (initiatorPayload.url) {
469             url = initiatorPayload.url;
470
471             // The lineNumber is 1-based, but we expect 0-based.
472             lineNumber = initiatorPayload.lineNumber - 1;
473         }
474
475         if (!url || isNaN(lineNumber) || lineNumber < 0)
476             return null;
477
478         var sourceCode = WebInspector.frameResourceManager.resourceForURL(url);
479         if (!sourceCode)
480             sourceCode = WebInspector.debuggerManager.scriptsForURL(url)[0];
481
482         if (!sourceCode)
483             return null;
484
485         return sourceCode.createSourceCodeLocation(lineNumber, columnNumber);
486     },
487
488     _processMainFrameResourceTreePayload: function(error, mainFramePayload)
489     {
490         console.assert(this._waitingForMainFrameResourceTreePayload);
491         delete this._waitingForMainFrameResourceTreePayload;
492
493         if (error) {
494             console.error(JSON.stringify(error));
495             return;
496         }
497
498         console.assert(mainFramePayload);
499         console.assert(mainFramePayload.frame);
500
501         this._resourceRequestIdentifierMap = {};
502         this._frameIdentifierMap = {};
503
504         var oldMainFrame = this._mainFrame;
505
506         this._mainFrame = this._addFrameTreeFromFrameResourceTreePayload(mainFramePayload, true);
507
508         if (this._mainFrame !== oldMainFrame)
509             this._mainFrameDidChange(oldMainFrame);
510     },
511
512     _createFrame: function(payload)
513     {
514         // If payload.url is missing or empty then this page is likely the special empty page. In that case
515         // we will just say it is "about:blank" so we have a URL, which is required for resources.
516         var mainResource = new WebInspector.Resource(payload.url || "about:blank", payload.mimeType, null, payload.loaderId);
517         var frame = new WebInspector.Frame(payload.id, payload.name, payload.securityOrigin, payload.loaderId, mainResource);
518
519         this._frameIdentifierMap[frame.id] = frame;
520
521         mainResource.markAsFinished();
522
523         return frame;
524     },
525
526     _createResource: function(payload, framePayload)
527     {
528         var resource = new WebInspector.Resource(payload.url, payload.mimeType, payload.type, framePayload.loaderId);
529
530         if (payload.sourceMapURL)
531             WebInspector.sourceMapManager.downloadSourceMap(payload.sourceMapURL, resource.url, resource);
532
533         return resource;
534     },
535
536     _addFrameTreeFromFrameResourceTreePayload: function(payload, isMainFrame)
537     {
538         var frame = this._createFrame(payload.frame);
539         if (isMainFrame)
540             frame.markAsMainFrame();
541
542         for (var i = 0; payload.childFrames && i < payload.childFrames.length; ++i)
543             frame.addChildFrame(this._addFrameTreeFromFrameResourceTreePayload(payload.childFrames[i], false));
544
545         for (var i = 0; payload.resources && i < payload.resources.length; ++i) {
546             var resourcePayload = payload.resources[i];
547
548             // The main resource is included as a resource. We can skip it since we already created
549             // a main resource when we created the Frame. The resource payload does not include anything
550             // didn't already get from the frame payload.
551             if (resourcePayload.type === "Document" && resourcePayload.url === payload.frame.url)
552                 continue;
553
554             var resource = this._createResource(resourcePayload, payload);
555             frame.addResource(resource);
556
557             if (resourcePayload.failed || resourcePayload.canceled)
558                 resource.markAsFailed(resourcePayload.canceled);
559             else
560                 resource.markAsFinished();
561         }
562
563         this._dispatchFrameWasAddedEvent(frame);
564
565         return frame;
566     },
567
568     _dispatchFrameWasAddedEvent: function(frame)
569     {
570         this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.FrameWasAdded, {frame: frame});
571     },
572
573     _mainFrameDidChange: function(oldMainFrame)
574     {
575         if (oldMainFrame)
576             oldMainFrame.unmarkAsMainFrame();
577         if (this._mainFrame)
578             this._mainFrame.markAsMainFrame();
579         this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.MainFrameDidChange, {oldMainFrame: oldMainFrame});
580     }
581 };
582
583 WebInspector.FrameResourceManager.prototype.__proto__ = WebInspector.Object.prototype;