2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @extends {WebInspector.Object}
34 * @param {!WebInspector.NetworkManager} networkManager
36 WebInspector.ResourceTreeModel = function(networkManager)
38 networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this);
39 networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdateDropped, this._onRequestUpdateDropped, this);
41 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
42 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.RepeatCountUpdated, this._consoleMessageAdded, this);
43 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
47 this._fetchResourceTree();
49 InspectorBackend.registerPageDispatcher(new WebInspector.PageDispatcher(this));
51 this._pendingConsoleMessages = {};
52 this._securityOriginFrameCount = {};
55 WebInspector.ResourceTreeModel.EventTypes = {
56 FrameAdded: "FrameAdded",
57 FrameNavigated: "FrameNavigated",
58 FrameDetached: "FrameDetached",
59 FrameResized: "FrameResized",
60 MainFrameNavigated: "MainFrameNavigated",
61 MainFrameCreatedOrNavigated: "MainFrameCreatedOrNavigated",
62 ResourceAdded: "ResourceAdded",
63 WillLoadCachedResources: "WillLoadCachedResources",
64 CachedResourcesLoaded: "CachedResourcesLoaded",
65 DOMContentLoaded: "DOMContentLoaded",
67 WillReloadPage: "WillReloadPage",
68 InspectedURLChanged: "InspectedURLChanged",
69 SecurityOriginAdded: "SecurityOriginAdded",
70 SecurityOriginRemoved: "SecurityOriginRemoved",
71 ScreencastFrame: "ScreencastFrame",
72 ScreencastVisibilityChanged: "ScreencastVisibilityChanged"
75 WebInspector.ResourceTreeModel.prototype = {
76 _fetchResourceTree: function()
78 /** @type {!Object.<string, !WebInspector.ResourceTreeFrame>} */
80 delete this._cachedResourcesProcessed;
81 PageAgent.getResourceTree(this._processCachedResources.bind(this));
84 _processCachedResources: function(error, mainFramePayload)
87 console.error(JSON.stringify(error));
91 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources);
92 WebInspector.inspectedPageURL = mainFramePayload.frame.url;
93 this._addFramesRecursively(null, mainFramePayload);
94 this._dispatchInspectedURLChanged();
95 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded);
96 this._cachedResourcesProcessed = true;
102 cachedResourcesLoaded: function()
104 return this._cachedResourcesProcessed;
107 _dispatchInspectedURLChanged: function()
109 InspectorFrontendHost.inspectedURLChanged(WebInspector.inspectedPageURL);
110 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, WebInspector.inspectedPageURL);
114 * @param {!WebInspector.ResourceTreeFrame} frame
115 * @param {boolean=} aboutToNavigate
117 _addFrame: function(frame, aboutToNavigate)
119 this._frames[frame.id] = frame;
120 if (frame.isMainFrame())
121 this.mainFrame = frame;
122 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, frame);
123 if (!aboutToNavigate)
124 this._addSecurityOrigin(frame.securityOrigin);
125 if (frame.isMainFrame())
126 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, frame);
130 * @param {string} securityOrigin
132 _addSecurityOrigin: function(securityOrigin)
134 if (!this._securityOriginFrameCount[securityOrigin]) {
135 this._securityOriginFrameCount[securityOrigin] = 1;
136 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, securityOrigin);
139 this._securityOriginFrameCount[securityOrigin] += 1;
143 * @param {string|undefined} securityOrigin
145 _removeSecurityOrigin: function(securityOrigin)
147 if (typeof securityOrigin === "undefined")
149 if (this._securityOriginFrameCount[securityOrigin] === 1) {
150 delete this._securityOriginFrameCount[securityOrigin];
151 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, securityOrigin);
154 this._securityOriginFrameCount[securityOrigin] -= 1;
158 * @return {!Array.<string>}
160 securityOrigins: function()
162 return Object.keys(this._securityOriginFrameCount);
166 * @param {!WebInspector.ResourceTreeFrame} mainFrame
168 _handleMainFrameDetached: function(mainFrame)
171 * @param {!WebInspector.ResourceTreeFrame} frame
172 * @this {WebInspector.ResourceTreeModel}
174 function removeOriginForFrame(frame)
176 for (var i = 0; i < frame.childFrames.length; ++i)
177 removeOriginForFrame.call(this, frame.childFrames[i]);
178 if (!frame.isMainFrame())
179 this._removeSecurityOrigin(frame.securityOrigin);
181 removeOriginForFrame.call(this, WebInspector.resourceTreeModel.mainFrame);
185 * @param {!PageAgent.FrameId} frameId
186 * @param {?PageAgent.FrameId} parentFrameId
187 * @return {?WebInspector.ResourceTreeFrame}
189 _frameAttached: function(frameId, parentFrameId)
191 // Do nothing unless cached resource tree is processed - it will overwrite everything.
192 if (!this._cachedResourcesProcessed)
194 if (this._frames[frameId])
197 var parentFrame = parentFrameId ? this._frames[parentFrameId] : null;
198 var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, frameId);
199 if (frame.isMainFrame() && this.mainFrame) {
200 this._handleMainFrameDetached(this.mainFrame);
201 // Navigation to the new backend process.
202 this._frameDetached(this.mainFrame.id);
204 this._addFrame(frame, true);
209 * @param {!PageAgent.Frame} framePayload
211 _frameNavigated: function(framePayload)
213 // Do nothing unless cached resource tree is processed - it will overwrite everything.
214 if (!this._cachedResourcesProcessed)
216 var frame = this._frames[framePayload.id];
218 // Simulate missed "frameAttached" for a main frame navigation to the new backend process.
219 console.assert(!framePayload.parentId, "Main frame shouldn't have parent frame id.");
220 frame = this._frameAttached(framePayload.id, framePayload.parentId || "");
221 console.assert(frame);
223 this._removeSecurityOrigin(frame.securityOrigin);
224 frame._navigate(framePayload);
225 var addedOrigin = frame.securityOrigin;
227 if (frame.isMainFrame())
228 WebInspector.inspectedPageURL = frame.url;
230 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, frame);
231 if (frame.isMainFrame()) {
232 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, frame);
233 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, frame);
236 this._addSecurityOrigin(addedOrigin);
238 // Fill frame with retained resources (the ones loaded using new loader).
239 var resources = frame.resources();
240 for (var i = 0; i < resources.length; ++i)
241 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resources[i]);
243 if (frame.isMainFrame())
244 this._dispatchInspectedURLChanged();
248 * @param {!PageAgent.FrameId} frameId
250 _frameDetached: function(frameId)
252 // Do nothing unless cached resource tree is processed - it will overwrite everything.
253 if (!this._cachedResourcesProcessed)
256 var frame = this._frames[frameId];
260 this._removeSecurityOrigin(frame.securityOrigin);
261 if (frame.parentFrame)
262 frame.parentFrame._removeChildFrame(frame);
268 * @param {!WebInspector.Event} event
270 _onRequestFinished: function(event)
272 if (!this._cachedResourcesProcessed)
275 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
276 if (request.failed || request.type === WebInspector.resourceTypes.XHR)
279 var frame = this._frames[request.frameId];
281 var resource = frame._addRequest(request);
282 this._addPendingConsoleMessagesToResource(resource);
287 * @param {!WebInspector.Event} event
289 _onRequestUpdateDropped: function(event)
291 if (!this._cachedResourcesProcessed)
294 var frameId = event.data.frameId;
295 var frame = this._frames[frameId];
299 var url = event.data.url;
300 if (frame._resourcesMap[url])
303 var resource = new WebInspector.Resource(null, url, frame.url, frameId, event.data.loaderId, WebInspector.resourceTypes[event.data.resourceType], event.data.mimeType);
304 frame.addResource(resource);
308 * @param {!PageAgent.FrameId} frameId
309 * @return {!WebInspector.ResourceTreeFrame}
311 frameForId: function(frameId)
313 return this._frames[frameId];
317 * @param {function(!WebInspector.Resource)} callback
320 forAllResources: function(callback)
323 return this.mainFrame._callForFrameResources(callback);
328 * @return {!Array.<!WebInspector.ResourceTreeFrame>}
332 return Object.values(this._frames);
336 * @param {!WebInspector.Event} event
338 _consoleMessageAdded: function(event)
340 var msg = /** @type {!WebInspector.ConsoleMessage} */ (event.data);
341 var resource = msg.url ? this.resourceForURL(msg.url) : null;
343 this._addConsoleMessageToResource(msg, resource);
345 this._addPendingConsoleMessage(msg);
349 * @param {!WebInspector.ConsoleMessage} msg
351 _addPendingConsoleMessage: function(msg)
355 if (!this._pendingConsoleMessages[msg.url])
356 this._pendingConsoleMessages[msg.url] = [];
357 this._pendingConsoleMessages[msg.url].push(msg);
361 * @param {!WebInspector.Resource} resource
363 _addPendingConsoleMessagesToResource: function(resource)
365 var messages = this._pendingConsoleMessages[resource.url];
367 for (var i = 0; i < messages.length; i++)
368 this._addConsoleMessageToResource(messages[i], resource);
369 delete this._pendingConsoleMessages[resource.url];
374 * @param {!WebInspector.ConsoleMessage} msg
375 * @param {!WebInspector.Resource} resource
377 _addConsoleMessageToResource: function(msg, resource)
380 case WebInspector.ConsoleMessage.MessageLevel.Warning:
381 resource.warnings += msg.repeatDelta;
383 case WebInspector.ConsoleMessage.MessageLevel.Error:
384 resource.errors += msg.repeatDelta;
387 resource.addMessage(msg);
390 _consoleCleared: function()
392 function callback(resource)
394 resource.clearErrorsAndWarnings();
397 this._pendingConsoleMessages = {};
398 this.forAllResources(callback);
402 * @param {string} url
403 * @return {?WebInspector.Resource}
405 resourceForURL: function(url)
407 // Workers call into this with no frames available.
408 return this.mainFrame ? this.mainFrame.resourceForURL(url) : null;
412 * @param {?WebInspector.ResourceTreeFrame} parentFrame
413 * @param {!PageAgent.FrameResourceTree} frameTreePayload
415 _addFramesRecursively: function(parentFrame, frameTreePayload)
417 var framePayload = frameTreePayload.frame;
418 var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, framePayload.id, framePayload);
419 this._addFrame(frame);
421 var frameResource = this._createResourceFromFramePayload(framePayload, framePayload.url, WebInspector.resourceTypes.Document, framePayload.mimeType);
422 if (frame.isMainFrame())
423 WebInspector.inspectedPageURL = frameResource.url;
424 frame.addResource(frameResource);
426 for (var i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i)
427 this._addFramesRecursively(frame, frameTreePayload.childFrames[i]);
429 for (var i = 0; i < frameTreePayload.resources.length; ++i) {
430 var subresource = frameTreePayload.resources[i];
431 var resource = this._createResourceFromFramePayload(framePayload, subresource.url, WebInspector.resourceTypes[subresource.type], subresource.mimeType);
432 frame.addResource(resource);
437 * @param {!PageAgent.Frame} frame
438 * @param {string} url
439 * @param {!WebInspector.ResourceType} type
440 * @param {string} mimeType
441 * @return {!WebInspector.Resource}
443 _createResourceFromFramePayload: function(frame, url, type, mimeType)
445 return new WebInspector.Resource(null, url, frame.url, frame.id, frame.loaderId, type, mimeType);
449 * @param {boolean=} ignoreCache
450 * @param {string=} scriptToEvaluateOnLoad
451 * @param {string=} scriptPreprocessor
453 reloadPage: function(ignoreCache, scriptToEvaluateOnLoad, scriptPreprocessor)
455 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage);
456 PageAgent.reload(ignoreCache, scriptToEvaluateOnLoad, scriptPreprocessor);
459 __proto__: WebInspector.Object.prototype
464 * @param {!WebInspector.ResourceTreeModel} model
465 * @param {?WebInspector.ResourceTreeFrame} parentFrame
466 * @param {!PageAgent.FrameId} frameId
467 * @param {!PageAgent.Frame=} payload
469 WebInspector.ResourceTreeFrame = function(model, parentFrame, frameId, payload)
472 this._parentFrame = parentFrame;
477 this._loaderId = payload.loaderId;
478 this._name = payload.name;
479 this._url = payload.url;
480 this._securityOrigin = payload.securityOrigin;
481 this._mimeType = payload.mimeType;
485 * @type {!Array.<!WebInspector.ResourceTreeFrame>}
487 this._childFrames = [];
490 * @type {!Object.<string, !WebInspector.Resource>}
492 this._resourcesMap = {};
494 if (this._parentFrame)
495 this._parentFrame._childFrames.push(this);
498 WebInspector.ResourceTreeFrame.prototype = {
512 return this._name || "";
528 return this._securityOrigin;
536 return this._loaderId;
540 * @return {?WebInspector.ResourceTreeFrame}
544 return this._parentFrame;
548 * @return {!Array.<!WebInspector.ResourceTreeFrame>}
552 return this._childFrames;
558 isMainFrame: function()
560 return !this._parentFrame;
564 * @param {!PageAgent.Frame} framePayload
566 _navigate: function(framePayload)
568 this._loaderId = framePayload.loaderId;
569 this._name = framePayload.name;
570 this._url = framePayload.url;
571 this._securityOrigin = framePayload.securityOrigin;
572 this._mimeType = framePayload.mimeType;
574 var mainResource = this._resourcesMap[this._url];
575 this._resourcesMap = {};
576 this._removeChildFrames();
577 if (mainResource && mainResource.loaderId === this._loaderId)
578 this.addResource(mainResource);
582 * @return {!WebInspector.Resource}
586 return this._resourcesMap[this._url];
590 * @param {!WebInspector.ResourceTreeFrame} frame
592 _removeChildFrame: function(frame)
594 this._childFrames.remove(frame);
598 _removeChildFrames: function()
600 var frames = this._childFrames;
601 this._childFrames = [];
602 for (var i = 0; i < frames.length; ++i)
608 this._removeChildFrames();
609 delete this._model._frames[this.id];
610 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this);
614 * @param {!WebInspector.Resource} resource
616 addResource: function(resource)
618 if (this._resourcesMap[resource.url] === resource) {
619 // Already in the tree, we just got an extra update.
622 this._resourcesMap[resource.url] = resource;
623 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource);
627 * @param {!WebInspector.NetworkRequest} request
628 * @return {!WebInspector.Resource}
630 _addRequest: function(request)
632 var resource = this._resourcesMap[request.url];
633 if (resource && resource.request === request) {
634 // Already in the tree, we just got an extra update.
637 resource = new WebInspector.Resource(request, request.url, request.documentURL, request.frameId, request.loaderId, request.type, request.mimeType);
638 this._resourcesMap[resource.url] = resource;
639 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource);
644 * @return {!Array.<!WebInspector.Resource>}
646 resources: function()
649 for (var url in this._resourcesMap)
650 result.push(this._resourcesMap[url]);
655 * @param {string} url
656 * @return {?WebInspector.Resource}
658 resourceForURL: function(url)
661 function filter(resource)
663 if (resource.url === url) {
668 this._callForFrameResources(filter);
669 return result || null;
673 * @param {function(!WebInspector.Resource)} callback
676 _callForFrameResources: function(callback)
678 for (var url in this._resourcesMap) {
679 if (callback(this._resourcesMap[url]))
683 for (var i = 0; i < this._childFrames.length; ++i) {
684 if (this._childFrames[i]._callForFrameResources(callback))
693 displayName: function()
695 if (!this._parentFrame)
696 return WebInspector.UIString("<top frame>");
697 var subtitle = new WebInspector.ParsedURL(this._url).displayName;
701 return this._name + "( " + subtitle + " )";
703 return WebInspector.UIString("<iframe>");
709 * @implements {PageAgent.Dispatcher}
711 WebInspector.PageDispatcher = function(resourceTreeModel)
713 this._resourceTreeModel = resourceTreeModel;
716 WebInspector.PageDispatcher.prototype = {
717 domContentEventFired: function(time)
719 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, time);
722 loadEventFired: function(time)
724 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.Load, time);
727 frameAttached: function(frameId, parentFrameId)
729 this._resourceTreeModel._frameAttached(frameId, parentFrameId);
732 frameNavigated: function(frame)
734 this._resourceTreeModel._frameNavigated(frame);
737 frameDetached: function(frameId)
739 this._resourceTreeModel._frameDetached(frameId);
742 frameStartedLoading: function(frameId)
746 frameStoppedLoading: function(frameId)
750 frameScheduledNavigation: function(frameId, delay)
754 frameClearedScheduledNavigation: function(frameId)
758 frameResized: function()
760 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameResized, null);
763 javascriptDialogOpening: function(message)
767 javascriptDialogClosed: function()
771 scriptsEnabled: function(isEnabled)
773 WebInspector.settings.javaScriptDisabled.set(!isEnabled);
777 * @param {string} data
778 * @param {!PageAgent.ScreencastFrameMetadata=} metadata
780 screencastFrame: function(data, metadata)
782 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ScreencastFrame, {data:data, metadata:metadata});
786 * @param {boolean} visible
788 screencastVisibilityChanged: function(visible)
790 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ScreencastVisibilityChanged, {visible:visible});
795 * @type {!WebInspector.ResourceTreeModel}
797 WebInspector.resourceTreeModel;