Web Inspector: Lint JavaScript and CSS for minor issues
[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)
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         // This is a new request, make a new resource and add it to the right frame.
193         resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, timestamp, null, null);
194
195         // Associate the resource with the requestIdentifier so it can be found in future loading events.
196         this._resourceRequestIdentifierMap[requestIdentifier] = resource;
197     },
198
199     markResourceRequestAsServedFromMemoryCache: function(requestIdentifier)
200     {
201         // Called from WebInspector.NetworkObserver.
202
203         // Ignore this while waiting for the whole frame/resource tree.
204         if (this._waitingForMainFrameResourceTreePayload)
205             return;
206
207         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
208
209         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
210         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
211         // just doesn't have a requestIdentifier for us to look it up.
212         if (!resource)
213             return;
214
215         resource.markAsCached();
216     },
217
218     resourceRequestWasServedFromMemoryCache: function(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload, timestamp)
219     {
220         // Called from WebInspector.NetworkObserver.
221
222         // Ignore this while waiting for the whole frame/resource tree.
223         if (this._waitingForMainFrameResourceTreePayload)
224             return;
225
226         console.assert(!(requestIdentifier in this._resourceRequestIdentifierMap));
227
228         var response = cachedResourcePayload.response;
229         var resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, null, null, timestamp, null, null);
230         resource.markAsCached();
231         resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, timestamp);
232         resource.markAsFinished(timestamp);
233
234         if (cachedResourcePayload.sourceMapURL)
235             WebInspector.sourceMapManager.downloadSourceMap(cachedResourcePayload.sourceMapURL, resource.url, resource);
236
237         // No need to associate the resource with the requestIdentifier, since this is the only event
238         // sent for memory cache resource loads.
239     },
240
241     resourceRequestDidReceiveResponse: function(requestIdentifier, frameIdentifier, loaderIdentifier, type, response, timestamp)
242     {
243         // Called from WebInspector.NetworkObserver.
244
245         // Ignore this while waiting for the whole frame/resource tree.
246         if (this._waitingForMainFrameResourceTreePayload)
247             return;
248
249         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
250
251         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
252         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
253         // just doesn't have a requestIdentifier for us to look it up, but we can try to look it up by its URL.
254         if (!resource) {
255             var frame = this.frameForIdentifier(frameIdentifier);
256             if (frame)
257                 resource = frame.resourceForURL(response.url);
258
259             // If we find the resource this way we had marked it earlier as finished via PageAgent.getResourceTree.
260             // Associate the resource with the requestIdentifier so it can be found in future loading events.
261             // and roll it back to an unfinished state, we know now it is still loading.
262             if (resource) {
263                 this._resourceRequestIdentifierMap[requestIdentifier] = resource;
264                 resource.revertMarkAsFinished();
265             }
266         }
267
268         // If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
269         // opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
270         if (!resource) {
271             resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, timestamp, null, null);
272
273             // Associate the resource with the requestIdentifier so it can be found in future loading events.
274             this._resourceRequestIdentifierMap[requestIdentifier] = resource;
275         }
276
277         if (response.fromDiskCache)
278             resource.markAsCached();
279
280         resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, timestamp);
281     },
282
283     resourceRequestDidReceiveData: function(requestIdentifier, dataLength, encodedDataLength, timestamp)
284     {
285         // Called from WebInspector.NetworkObserver.
286
287         // Ignore this while waiting for the whole frame/resource tree.
288         if (this._waitingForMainFrameResourceTreePayload)
289             return;
290
291         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
292
293         // We might not have a resource if the inspector was opened during the page load (after resourceRequestWillBeSent is called).
294         // We don't want to assert in this case since we do likely have the resource, via PageAgent.getResourceTree. The Resource
295         // just doesn't have a requestIdentifier for us to look it up.
296         if (!resource)
297             return;
298
299         resource.increaseSize(dataLength, timestamp);
300
301         if (encodedDataLength !== -1)
302             resource.increaseTransferSize(encodedDataLength);
303     },
304
305     resourceRequestDidFinishLoading: function(requestIdentifier, timestamp, sourceMapURL)
306     {
307         // Called from WebInspector.NetworkObserver.
308
309         // Ignore this while waiting for the whole frame/resource tree.
310         if (this._waitingForMainFrameResourceTreePayload)
311             return;
312
313         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
314         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
315         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
316         console.assert(resource);
317         if (!resource)
318             return;
319
320         resource.markAsFinished(timestamp);
321
322         if (sourceMapURL)
323             WebInspector.sourceMapManager.downloadSourceMap(sourceMapURL, resource.url, resource);
324
325         delete this._resourceRequestIdentifierMap[requestIdentifier];
326     },
327
328     resourceRequestDidFailLoading: function(requestIdentifier, canceled, timestamp)
329     {
330         // Called from WebInspector.NetworkObserver.
331
332         // Ignore this while waiting for the whole frame/resource tree.
333         if (this._waitingForMainFrameResourceTreePayload)
334             return;
335
336         // By now we should always have the Resource. Either it was fetched when the inspector first opened with
337         // PageAgent.getResourceTree, or it was a currently loading resource that we learned about in resourceRequestDidReceiveResponse.
338         var resource = this._resourceRequestIdentifierMap[requestIdentifier];
339         console.assert(resource);
340         if (!resource)
341             return;
342
343         resource.markAsFailed(canceled, timestamp);
344
345         if (resource === resource.parentFrame.provisionalMainResource)
346             resource.parentFrame.clearProvisionalLoad();
347
348         delete this._resourceRequestIdentifierMap[requestIdentifier];
349     },
350
351     executionContextCreated: function(contextPayload)
352     {
353         // Called from WebInspector.RuntimeObserver.
354
355         var frame = this.frameForIdentifier(contextPayload.frameId);
356         console.assert(frame);
357         if (!frame)
358             return;
359
360         var displayName = contextPayload.name || frame.mainResource.displayName;
361         var executionContext = new WebInspector.ExecutionContext(contextPayload.id, displayName, contextPayload.isPageContext, frame);
362         frame.addExecutionContext(executionContext);
363     },
364
365     resourceForURL: function(url)
366     {
367         if (!this._mainFrame)
368             return null;
369
370         if (this._mainFrame.mainResource.url === url)
371             return this._mainFrame.mainResource;
372
373         return this._mainFrame.resourceForURL(url, true);
374     },
375
376     // Private
377
378     _addNewResourceToFrame: function(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, timestamp, frameName, frameSecurityOrigin)
379     {
380         console.assert(!this._waitingForMainFrameResourceTreePayload);
381         if (this._waitingForMainFrameResourceTreePayload)
382             return;
383
384         var resource = null;
385
386         var frame = this.frameForIdentifier(frameIdentifier);
387         if (frame) {
388             // This is a new request for an existing frame, which might be the main resource or a new resource.
389             if (frame.mainResource.url === url && frame.loaderIdentifier === loaderIdentifier)
390                 resource = frame.mainResource;
391             else if (frame.provisionalMainResource && frame.provisionalMainResource.url === url && frame.provisionalLoaderIdentifier === loaderIdentifier)
392                 resource = frame.provisionalMainResource;
393             else {
394                 resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp);
395                 this._addResourceToFrame(frame, resource);
396             }
397         } else {
398             // This is a new request for a new frame, which is always the main resource.
399             resource = new WebInspector.Resource(url, null, type, loaderIdentifier, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp);
400             frame = new WebInspector.Frame(frameIdentifier, frameName, frameSecurityOrigin, loaderIdentifier, resource);
401             this._frameIdentifierMap[frame.id] = frame;
402
403             // If we don't have a main frame, assume this is it. This can change later in
404             // frameDidNavigate when the parent frame is known.
405             if (!this._mainFrame) {
406                 this._mainFrame = frame;
407                 this._mainFrameDidChange(null);
408             }
409
410             this._dispatchFrameWasAddedEvent(frame);
411         }
412
413         console.assert(resource);
414
415         return resource;
416     },
417
418     _addResourceToFrame: function(frame, resource)
419     {
420         console.assert(!this._waitingForMainFrameResourceTreePayload);
421         if (this._waitingForMainFrameResourceTreePayload)
422             return;
423
424         console.assert(frame);
425         console.assert(resource);
426
427         if (resource.loaderIdentifier !== frame.loaderIdentifier && !frame.provisionalLoaderIdentifier) {
428             // This is the start of a provisional load which happens before frameDidNavigate is called.
429             // This resource will be the new mainResource if frameDidNavigate is called.
430             frame.startProvisionalLoad(resource);
431             return;
432         }
433
434         // This is just another resource, either for the main loader or the provisional loader.
435         console.assert(resource.loaderIdentifier === frame.loaderIdentifier || resource.loaderIdentifier === frame.provisionalLoaderIdentifier);
436         frame.addResource(resource);
437     },
438
439     _processMainFrameResourceTreePayload: function(error, mainFramePayload)
440     {
441         console.assert(this._waitingForMainFrameResourceTreePayload);
442         delete this._waitingForMainFrameResourceTreePayload;
443
444         if (error) {
445             console.error(JSON.stringify(error));
446             return;
447         }
448
449         console.assert(mainFramePayload);
450         console.assert(mainFramePayload.frame);
451
452         this._resourceRequestIdentifierMap = {};
453         this._frameIdentifierMap = {};
454
455         var oldMainFrame = this._mainFrame;
456
457         this._mainFrame = this._addFrameTreeFromFrameResourceTreePayload(mainFramePayload, true);
458
459         if (this._mainFrame !== oldMainFrame)
460             this._mainFrameDidChange(oldMainFrame);
461     },
462
463     _createFrame: function(payload)
464     {
465         // If payload.url is missing or empty then this page is likely the special empty page. In that case
466         // we will just say it is "about:blank" so we have a URL, which is required for resources.
467         var mainResource = new WebInspector.Resource(payload.url || "about:blank", payload.mimeType, null, payload.loaderId);
468         var frame = new WebInspector.Frame(payload.id, payload.name, payload.securityOrigin, payload.loaderId, mainResource);
469
470         this._frameIdentifierMap[frame.id] = frame;
471
472         mainResource.markAsFinished();
473
474         return frame;
475     },
476
477     _createResource: function(payload, framePayload)
478     {
479         var resource = new WebInspector.Resource(payload.url, payload.mimeType, payload.type, framePayload.loaderId);
480
481         if (payload.sourceMapURL)
482             WebInspector.sourceMapManager.downloadSourceMap(payload.sourceMapURL, resource.url, resource);
483
484         return resource;
485     },
486
487     _addFrameTreeFromFrameResourceTreePayload: function(payload, isMainFrame)
488     {
489         var frame = this._createFrame(payload.frame);
490         if (isMainFrame)
491             frame.markAsMainFrame();
492
493         for (var i = 0; payload.childFrames && i < payload.childFrames.length; ++i)
494             frame.addChildFrame(this._addFrameTreeFromFrameResourceTreePayload(payload.childFrames[i], false));
495
496         for (var i = 0; payload.resources && i < payload.resources.length; ++i) {
497             var resourcePayload = payload.resources[i];
498
499             // The main resource is included as a resource. We can skip it since we already created
500             // a main resource when we created the Frame. The resource payload does not include anything
501             // didn't already get from the frame payload.
502             if (resourcePayload.type === "Document" && resourcePayload.url === payload.frame.url)
503                 continue;
504
505             var resource = this._createResource(resourcePayload, payload);
506             frame.addResource(resource);
507
508             if (resourcePayload.failed || resourcePayload.canceled)
509                 resource.markAsFailed(resourcePayload.canceled);
510             else
511                 resource.markAsFinished();
512         }
513
514         this._dispatchFrameWasAddedEvent(frame);
515
516         return frame;
517     },
518
519     _dispatchFrameWasAddedEvent: function(frame)
520     {
521         this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.FrameWasAdded, {frame: frame});
522     },
523
524     _mainFrameDidChange: function(oldMainFrame)
525     {
526         if (oldMainFrame)
527             oldMainFrame.unmarkAsMainFrame();
528         if (this._mainFrame)
529             this._mainFrame.markAsMainFrame();
530         this.dispatchEventToListeners(WebInspector.FrameResourceManager.Event.MainFrameDidChange, {oldMainFrame: oldMainFrame});
531     }
532 };
533
534 WebInspector.FrameResourceManager.prototype.__proto__ = WebInspector.Object.prototype;