Web Inspector: bind registerDomainDispatcher to domain names.
[WebKit-https.git] / Source / WebCore / inspector / front-end / ResourceTreeModel.js
1 /*
2  * Copyright (C) 2011 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31
32 WebInspector.ResourceTreeModel = function(networkManager)
33 {
34     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this);
35     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this);
36     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this);
37
38     WebInspector.console.addEventListener(WebInspector.ConsoleView.Events.MessageAdded, this._consoleMessageAdded, this);
39     WebInspector.console.addEventListener(WebInspector.ConsoleView.Events.ConsoleCleared, this._consoleCleared, this);
40
41     this.frontendReused();
42     InspectorBackend.registerPageDispatcher(new WebInspector.PageDispatcher(this));
43     
44     this._pendingConsoleMessages = {};
45 }
46
47 WebInspector.ResourceTreeModel.EventTypes = {
48     FrameAdded: "FrameAdded",
49     FrameNavigated: "FrameNavigated",
50     FrameDetached: "FrameDetached",
51     ResourceAdded: "ResourceAdded",
52     WillLoadCachedResources: "WillLoadCachedResources",
53     CachedResourcesLoaded: "CachedResourcesLoaded",
54     DOMContentLoaded: "DOMContentLoaded",
55     OnLoad: "OnLoad",
56     InspectedURLChanged: "InspectedURLChanged"
57 }
58
59 WebInspector.ResourceTreeModel.prototype = {
60     frontendReused: function()
61     {
62         this._resourcesByURL = {};
63         this._resourcesByFrameId = {};
64         this._subframes = {};
65         this._frameIds = {};
66         delete this._cachedResourcesProcessed;
67         PageAgent.getResourceTree(this._processCachedResources.bind(this));
68     },
69
70     _processCachedResources: function(error, mainFramePayload)
71     {
72         if (error) {
73             console.error(JSON.stringify(error));
74             return;
75         }
76
77         this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources);
78         this._addFramesRecursively(mainFramePayload);
79         this._dispatchInspectedURLChanged(WebInspector.mainResource.url);
80         this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded);
81         WebInspector.Resource.restoreRevisions();
82
83         this._cachedResourcesProcessed = true;
84     },
85
86     _dispatchInspectedURLChanged: function(url)
87     {
88         InspectorFrontendHost.inspectedURLChanged(url);
89         this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, url);
90     },
91
92     _addFrame: function(frame)
93     {
94         frame.parentId = frame.parentId || "";
95         this._frameIds[frame.id] = frame;
96         var subframes = this._subframes[frame.parentId];
97         if (!subframes) {
98             subframes = [];
99             this._subframes[frame.parentId] = subframes;
100         }
101
102         subframes.push(frame);
103         this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, frame);
104     },
105
106     subframes: function(parentFrameId)
107     {
108         return this._subframes[parentFrameId] || [];
109     },
110
111     resources: function(frameId)
112     {
113         var result = [];
114         var resources = this._resourcesByFrameId[frameId] || {};
115         for (var url in resources)
116             result.push(resources[url]);
117         return result;
118     },
119
120     _frameNavigated: function(frame, loaderId)
121     {
122         var isMainFrame = !frame.parentId;
123
124         if (isMainFrame) {
125             this._cleanupFramesAfterNavigation(frame);
126             if (this.resourceForURL(frame.url))
127                 WebInspector.mainResource = this.resourceForURL(frame.url);
128         }
129         // Do nothing unless cached resource tree is processed - it will overwrite everything.
130         if (!this._cachedResourcesProcessed)
131             return;
132
133         // Add frame in case it is seen for the first time, otherwise, do a within-frame cleanup.
134         if (!this._frameIds[frame.id])
135             this._addFrame(frame);
136         else {
137             this._clearChildFramesAndResources(frame.id, loaderId);
138             frame.parentId = frame.parentId || "";
139             this._frameIds[frame.id] = frame;
140         }
141         // Dispatch frame navigated event to clients prior to filling it with the resources. 
142         this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, { frame: frame, loaderId: loaderId, isMainFrame: isMainFrame });
143
144         // Fill frame with retained resources (the ones loaded using new loader). 
145         var resourcesForFrame = this._resourcesByFrameId[frame.id];
146         if (resourcesForFrame) {
147             for (var url in resourcesForFrame)
148                 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resourcesForFrame[url]);
149         }
150
151         if (isMainFrame)
152             this._dispatchInspectedURLChanged(frame.url);
153     },
154
155     _cleanupFramesAfterNavigation: function(newMainFrame)
156     {
157         if (this._currentMainFrameId)
158             this._frameDetached(this._currentMainFrameId);
159         this._currentMainFrameId = newMainFrame.id;
160     },
161
162     _frameDetached: function(frameId)
163     {
164         // Do nothing unless cached resource tree is processed - it will overwrite everything.
165         if (!this._cachedResourcesProcessed)
166             return;
167
168         this._clearChildFramesAndResources(frameId, "");
169         var frame = this._frameIds[frameId];
170
171         if (frame) {
172             var siblings = this._subframes[frame.parentId];
173             if (siblings)
174                 siblings.remove(frame);
175             delete this._frameIds[frameId];
176         }
177
178         this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, frameId);
179     },
180
181     _onResourceStarted: function(event)
182     {
183         if (!this._cachedResourcesProcessed)
184             return;
185         this._bindResourceURL(event.data);
186     },
187
188     _onResourceUpdated: function(event)
189     {
190         if (!this._cachedResourcesProcessed)
191             return;
192
193         var resource = event.data;
194         if (resource.failed) {
195             this._unbindResourceURL(resource);
196             return;
197         }
198
199         if (resource.type === WebInspector.Resource.Type.XHR) {
200             this._unbindResourceURL(resource);
201             return;
202         }
203                 
204         if (resource.finished)
205             this._addResourceToFrame(resource);
206     },
207
208     _addResourceToFrame: function(resource)
209     {
210         var frameId = resource.frameId;
211         var resourcesForFrame = this._resourcesByFrameId[frameId];
212         if (!resourcesForFrame) {
213             resourcesForFrame = {};
214             this._resourcesByFrameId[frameId] = resourcesForFrame;
215         }
216         if (resourcesForFrame[resource.url] === resource) {
217             // Already in the tree, we just got an extra update.
218             return;
219         }
220
221         resourcesForFrame[resource.url] = resource;
222         this._bindResourceURL(resource);
223         this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource);
224     },
225
226     forAllResources: function(callback)
227     {
228         return this._callForFrameResources("", callback);
229     },
230
231     _consoleMessageAdded: function(event)
232     {
233         var msg = event.data;
234         var resource = this.resourceForURL(msg.url);
235         if (resource)
236             this._addConsoleMessageToResource(msg, resource);
237         else
238             this._addPendingConsoleMessage(msg);
239     },
240
241     _addPendingConsoleMessage: function(msg)
242     {
243         if (!msg.url)
244             return;
245         if (!this._pendingConsoleMessages[msg.url])
246             this._pendingConsoleMessages[msg.url] = [];
247         this._pendingConsoleMessages[msg.url].push(msg);
248     },
249
250     _addPendingConsoleMessagesToResource: function(resource)
251     {
252         var messages = this._pendingConsoleMessages[resource.url];
253         if (messages) {
254             for (var i = 0; i < messages.length; i++)
255                 this._addConsoleMessageToResource(messages[i], resource);
256             delete this._pendingConsoleMessages[resource.url];
257         }
258     },
259
260     _addConsoleMessageToResource: function(msg, resource)
261     {
262         switch (msg.level) {
263         case WebInspector.ConsoleMessage.MessageLevel.Warning:
264             resource.warnings += msg.repeatDelta;
265             break;
266         case WebInspector.ConsoleMessage.MessageLevel.Error:
267             resource.errors += msg.repeatDelta;
268             break;
269         }
270         resource.addMessage(msg);
271     },
272
273     _consoleCleared: function()
274     {
275         function callback(resource)
276         {
277             resource.clearErrorsAndWarnings();
278         }
279         
280         this._pendingConsoleMessages = {};
281         this.forAllResources(callback);
282     },
283
284     resourceForURL: function(url)
285     {
286         return this._resourcesByURL[url];
287     },
288
289     _bindResourceURL: function(resource)
290     {
291         this._resourcesByURL[resource.url] = resource;
292         
293         this._addPendingConsoleMessagesToResource(resource);
294     },
295
296     _clearChildFramesAndResources: function(frameId, loaderToPreserveId)
297     {
298         this._clearResources(frameId, loaderToPreserveId);
299         var subframes = this._subframes[frameId];
300         for (var i = 0; subframes && i < subframes.length; ++ i) {
301             this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, subframes[i].id);
302             this._clearChildFramesAndResources(subframes[i].id, loaderToPreserveId);
303         }
304         delete this._subframes[frameId];
305     },
306
307     _clearResources: function(frameId, loaderToPreserveId)
308     {
309         var resourcesForFrame = this._resourcesByFrameId[frameId];
310         if (!resourcesForFrame)
311             return;
312
313         var preservedResourcesForFrame = [];
314         for (var url in resourcesForFrame) {
315             var resource = resourcesForFrame[url];
316             if (resource.loaderId === loaderToPreserveId) {
317                 preservedResourcesForFrame[url] = resource;
318                 continue;
319             }
320             this._unbindResourceURL(resource);
321         }
322
323         delete this._resourcesByFrameId[frameId];
324         if (preservedResourcesForFrame.length) {
325             this._resourcesByFrameId[frameId] = preservedResourcesForFrame;
326         }
327     },
328
329     _callForFrameResources: function(frameId, callback)
330     {
331         var resources = this._resourcesByFrameId[frameId];
332
333         for (var url in resources) {
334             if (callback(resources[url]))
335                 return true;
336         }
337
338         var frames = this._subframes[frameId];
339         for (var i = 0; frames && i < frames.length; ++i) {
340             if (this._callForFrameResources(frames[i].id, callback))
341                 return true;
342         }
343         return false;
344     },
345
346     _unbindResourceURL: function(resource)
347     {
348         delete this._resourcesByURL[resource.url];
349     },
350
351     _addFramesRecursively: function(frameTreePayload)
352     {
353         var framePayload = frameTreePayload.frame;
354
355         // Create frame resource.
356         var frameResource = this._createResource(framePayload, framePayload.url);
357         frameResource.type = WebInspector.Resource.Type.Document;
358         frameResource.finished = true;
359
360         if (!framePayload.parentId) {
361             WebInspector.mainResource = frameResource;
362             this._currentMainFrameId = framePayload.id;
363         }
364         this._addFrame(framePayload);
365         this._addResourceToFrame(frameResource);
366
367         for (var i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i)
368             this._addFramesRecursively(frameTreePayload.childFrames[i]);
369
370         if (!frameTreePayload.resources)
371             return;
372
373         // Create frame subresources.
374         for (var i = 0; i < frameTreePayload.resources.length; ++i) {
375             var subresource = frameTreePayload.resources[i];
376             var resource = this._createResource(framePayload, subresource.url);
377             resource.type = WebInspector.Resource.Type[subresource.type];
378             resource.mimeType = subresource.mimeType;
379             resource.finished = true;
380             this._addResourceToFrame(resource);
381         }
382     },
383
384     _createResource: function(frame, url)
385     {
386         var resource = new WebInspector.Resource(null, url, frame.loaderId);
387         resource.frameId = frame.id;
388         resource.documentURL = frame.url;
389         resource.mimeType = frame.mimeType;
390         return resource;
391     }
392 }
393
394 WebInspector.ResourceTreeModel.prototype.__proto__ = WebInspector.Object.prototype;
395
396 WebInspector.PageDispatcher = function(resourceTreeModel)
397 {
398     this._resourceTreeModel = resourceTreeModel;
399 }
400
401 WebInspector.PageDispatcher.prototype = {
402     domContentEventFired: function(time)
403     {
404         this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, time);
405       
406         // FIXME: the only client is HAR, fix it there.
407         WebInspector.mainResourceDOMContentTime = time;
408     },
409
410     loadEventFired: function(time)
411     {
412         this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.OnLoad, time);
413
414         // FIXME: the only client is HAR, fix it there.
415         WebInspector.mainResourceLoadTime = time;
416     },
417
418     frameNavigated: function(frame, loaderId)
419     {
420         this._resourceTreeModel._frameNavigated(frame, loaderId);
421     },
422
423     frameDetached: function(frameId)
424     {
425         this._resourceTreeModel._frameDetached(frameId);
426     }
427 }