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.Target} target
36 WebInspector.ResourceTreeModel = function(target)
38 target.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this);
39 target.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdateDropped, this._onRequestUpdateDropped, this);
41 target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
42 target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
44 this._agent = target.pageAgent();
47 this._fetchResourceTree();
49 target.registerPageDispatcher(new WebInspector.PageDispatcher(this));
51 this._pendingConsoleMessages = {};
52 this._securityOriginFrameCount = {};
53 this._inspectedPageURL = "";
56 WebInspector.ResourceTreeModel.EventTypes = {
57 FrameAdded: "FrameAdded",
58 FrameNavigated: "FrameNavigated",
59 FrameDetached: "FrameDetached",
60 FrameResized: "FrameResized",
61 MainFrameNavigated: "MainFrameNavigated",
62 MainFrameCreatedOrNavigated: "MainFrameCreatedOrNavigated",
63 ResourceAdded: "ResourceAdded",
64 WillLoadCachedResources: "WillLoadCachedResources",
65 CachedResourcesLoaded: "CachedResourcesLoaded",
66 DOMContentLoaded: "DOMContentLoaded",
68 WillReloadPage: "WillReloadPage",
69 InspectedURLChanged: "InspectedURLChanged",
70 SecurityOriginAdded: "SecurityOriginAdded",
71 SecurityOriginRemoved: "SecurityOriginRemoved",
72 ScreencastFrame: "ScreencastFrame",
73 ScreencastVisibilityChanged: "ScreencastVisibilityChanged"
76 WebInspector.ResourceTreeModel.prototype = {
77 _fetchResourceTree: function()
79 /** @type {!Object.<string, !WebInspector.ResourceTreeFrame>} */
81 delete this._cachedResourcesProcessed;
82 this._agent.getResourceTree(this._processCachedResources.bind(this));
85 _processCachedResources: function(error, mainFramePayload)
88 console.error(JSON.stringify(error));
92 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources);
93 this._inspectedPageURL = mainFramePayload.frame.url;
94 this._addFramesRecursively(null, mainFramePayload);
95 this._dispatchInspectedURLChanged();
96 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded);
97 this._cachedResourcesProcessed = true;
103 inspectedPageURL: function()
105 return this._inspectedPageURL;
111 inspectedPageDomain: function()
113 var parsedURL = this._inspectedPageURL ? this._inspectedPageURL.asParsedURL() : null;
114 return parsedURL ? parsedURL.host : "";
120 cachedResourcesLoaded: function()
122 return this._cachedResourcesProcessed;
125 _dispatchInspectedURLChanged: function()
127 InspectorFrontendHost.inspectedURLChanged(this._inspectedPageURL);
128 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedPageURL);
132 * @param {!WebInspector.ResourceTreeFrame} frame
133 * @param {boolean=} aboutToNavigate
135 _addFrame: function(frame, aboutToNavigate)
137 this._frames[frame.id] = frame;
138 if (frame.isMainFrame())
139 this.mainFrame = frame;
140 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, frame);
141 if (!aboutToNavigate)
142 this._addSecurityOrigin(frame.securityOrigin);
143 if (frame.isMainFrame())
144 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, frame);
148 * @param {string} securityOrigin
150 _addSecurityOrigin: function(securityOrigin)
152 if (!this._securityOriginFrameCount[securityOrigin]) {
153 this._securityOriginFrameCount[securityOrigin] = 1;
154 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, securityOrigin);
157 this._securityOriginFrameCount[securityOrigin] += 1;
161 * @param {string|undefined} securityOrigin
163 _removeSecurityOrigin: function(securityOrigin)
165 if (typeof securityOrigin === "undefined")
167 if (this._securityOriginFrameCount[securityOrigin] === 1) {
168 delete this._securityOriginFrameCount[securityOrigin];
169 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, securityOrigin);
172 this._securityOriginFrameCount[securityOrigin] -= 1;
176 * @return {!Array.<string>}
178 securityOrigins: function()
180 return Object.keys(this._securityOriginFrameCount);
184 * @param {!WebInspector.ResourceTreeFrame} mainFrame
186 _handleMainFrameDetached: function(mainFrame)
189 * @param {!WebInspector.ResourceTreeFrame} frame
190 * @this {WebInspector.ResourceTreeModel}
192 function removeOriginForFrame(frame)
194 for (var i = 0; i < frame.childFrames.length; ++i)
195 removeOriginForFrame.call(this, frame.childFrames[i]);
196 if (!frame.isMainFrame())
197 this._removeSecurityOrigin(frame.securityOrigin);
199 removeOriginForFrame.call(this, WebInspector.resourceTreeModel.mainFrame);
203 * @param {!PageAgent.FrameId} frameId
204 * @param {?PageAgent.FrameId} parentFrameId
205 * @return {?WebInspector.ResourceTreeFrame}
207 _frameAttached: function(frameId, parentFrameId)
209 // Do nothing unless cached resource tree is processed - it will overwrite everything.
210 if (!this._cachedResourcesProcessed)
212 if (this._frames[frameId])
215 var parentFrame = parentFrameId ? this._frames[parentFrameId] : null;
216 var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, frameId);
217 if (frame.isMainFrame() && this.mainFrame) {
218 this._handleMainFrameDetached(this.mainFrame);
219 // Navigation to the new backend process.
220 this._frameDetached(this.mainFrame.id);
222 this._addFrame(frame, true);
227 * @param {!PageAgent.Frame} framePayload
229 _frameNavigated: function(framePayload)
231 // Do nothing unless cached resource tree is processed - it will overwrite everything.
232 if (!this._cachedResourcesProcessed)
234 var frame = this._frames[framePayload.id];
236 // Simulate missed "frameAttached" for a main frame navigation to the new backend process.
237 console.assert(!framePayload.parentId, "Main frame shouldn't have parent frame id.");
238 frame = this._frameAttached(framePayload.id, framePayload.parentId || "");
239 console.assert(frame);
241 this._removeSecurityOrigin(frame.securityOrigin);
242 frame._navigate(framePayload);
243 var addedOrigin = frame.securityOrigin;
245 if (frame.isMainFrame())
246 this._inspectedPageURL = frame.url;
248 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, frame);
249 if (frame.isMainFrame()) {
250 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, frame);
251 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, frame);
254 this._addSecurityOrigin(addedOrigin);
256 // Fill frame with retained resources (the ones loaded using new loader).
257 var resources = frame.resources();
258 for (var i = 0; i < resources.length; ++i)
259 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resources[i]);
261 if (frame.isMainFrame())
262 this._dispatchInspectedURLChanged();
266 * @param {!PageAgent.FrameId} frameId
268 _frameDetached: function(frameId)
270 // Do nothing unless cached resource tree is processed - it will overwrite everything.
271 if (!this._cachedResourcesProcessed)
274 var frame = this._frames[frameId];
278 this._removeSecurityOrigin(frame.securityOrigin);
279 if (frame.parentFrame)
280 frame.parentFrame._removeChildFrame(frame);
286 * @param {!WebInspector.Event} event
288 _onRequestFinished: function(event)
290 if (!this._cachedResourcesProcessed)
293 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
294 if (request.failed || request.type === WebInspector.resourceTypes.XHR)
297 var frame = this._frames[request.frameId];
299 var resource = frame._addRequest(request);
300 this._addPendingConsoleMessagesToResource(resource);
305 * @param {!WebInspector.Event} event
307 _onRequestUpdateDropped: function(event)
309 if (!this._cachedResourcesProcessed)
312 var frameId = event.data.frameId;
313 var frame = this._frames[frameId];
317 var url = event.data.url;
318 if (frame._resourcesMap[url])
321 var resource = new WebInspector.Resource(null, url, frame.url, frameId, event.data.loaderId, WebInspector.resourceTypes[event.data.resourceType], event.data.mimeType);
322 frame.addResource(resource);
326 * @param {!PageAgent.FrameId} frameId
327 * @return {!WebInspector.ResourceTreeFrame}
329 frameForId: function(frameId)
331 return this._frames[frameId];
335 * @param {function(!WebInspector.Resource)} callback
338 forAllResources: function(callback)
341 return this.mainFrame._callForFrameResources(callback);
346 * @return {!Array.<!WebInspector.ResourceTreeFrame>}
350 return Object.values(this._frames);
354 * @param {!WebInspector.Event} event
356 _consoleMessageAdded: function(event)
358 var msg = /** @type {!WebInspector.ConsoleMessage} */ (event.data);
359 var resource = msg.url ? this.resourceForURL(msg.url) : null;
361 this._addConsoleMessageToResource(msg, resource);
363 this._addPendingConsoleMessage(msg);
367 * @param {!WebInspector.ConsoleMessage} msg
369 _addPendingConsoleMessage: function(msg)
373 if (!this._pendingConsoleMessages[msg.url])
374 this._pendingConsoleMessages[msg.url] = [];
375 this._pendingConsoleMessages[msg.url].push(msg);
379 * @param {!WebInspector.Resource} resource
381 _addPendingConsoleMessagesToResource: function(resource)
383 var messages = this._pendingConsoleMessages[resource.url];
385 for (var i = 0; i < messages.length; i++)
386 this._addConsoleMessageToResource(messages[i], resource);
387 delete this._pendingConsoleMessages[resource.url];
392 * @param {!WebInspector.ConsoleMessage} msg
393 * @param {!WebInspector.Resource} resource
395 _addConsoleMessageToResource: function(msg, resource)
398 case WebInspector.ConsoleMessage.MessageLevel.Warning:
401 case WebInspector.ConsoleMessage.MessageLevel.Error:
405 resource.addMessage(msg);
408 _consoleCleared: function()
410 function callback(resource)
412 resource.clearErrorsAndWarnings();
415 this._pendingConsoleMessages = {};
416 this.forAllResources(callback);
420 * @param {string} url
421 * @return {?WebInspector.Resource}
423 resourceForURL: function(url)
425 // Workers call into this with no frames available.
426 return this.mainFrame ? this.mainFrame.resourceForURL(url) : null;
430 * @param {?WebInspector.ResourceTreeFrame} parentFrame
431 * @param {!PageAgent.FrameResourceTree} frameTreePayload
433 _addFramesRecursively: function(parentFrame, frameTreePayload)
435 var framePayload = frameTreePayload.frame;
436 var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, framePayload.id, framePayload);
437 this._addFrame(frame);
439 var frameResource = this._createResourceFromFramePayload(framePayload, framePayload.url, WebInspector.resourceTypes.Document, framePayload.mimeType);
440 if (frame.isMainFrame())
441 this._inspectedPageURL = frameResource.url;
442 frame.addResource(frameResource);
444 for (var i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i)
445 this._addFramesRecursively(frame, frameTreePayload.childFrames[i]);
447 for (var i = 0; i < frameTreePayload.resources.length; ++i) {
448 var subresource = frameTreePayload.resources[i];
449 var resource = this._createResourceFromFramePayload(framePayload, subresource.url, WebInspector.resourceTypes[subresource.type], subresource.mimeType);
450 frame.addResource(resource);
455 * @param {!PageAgent.Frame} frame
456 * @param {string} url
457 * @param {!WebInspector.ResourceType} type
458 * @param {string} mimeType
459 * @return {!WebInspector.Resource}
461 _createResourceFromFramePayload: function(frame, url, type, mimeType)
463 return new WebInspector.Resource(null, url, frame.url, frame.id, frame.loaderId, type, mimeType);
467 * @param {boolean=} ignoreCache
468 * @param {string=} scriptToEvaluateOnLoad
469 * @param {string=} scriptPreprocessor
471 reloadPage: function(ignoreCache, scriptToEvaluateOnLoad, scriptPreprocessor)
473 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage);
474 this._agent.reload(ignoreCache, scriptToEvaluateOnLoad, scriptPreprocessor);
477 __proto__: WebInspector.Object.prototype
482 * @param {!WebInspector.ResourceTreeModel} model
483 * @param {?WebInspector.ResourceTreeFrame} parentFrame
484 * @param {!PageAgent.FrameId} frameId
485 * @param {!PageAgent.Frame=} payload
487 WebInspector.ResourceTreeFrame = function(model, parentFrame, frameId, payload)
490 this._parentFrame = parentFrame;
495 this._loaderId = payload.loaderId;
496 this._name = payload.name;
497 this._url = payload.url;
498 this._securityOrigin = payload.securityOrigin;
499 this._mimeType = payload.mimeType;
503 * @type {!Array.<!WebInspector.ResourceTreeFrame>}
505 this._childFrames = [];
508 * @type {!Object.<string, !WebInspector.Resource>}
510 this._resourcesMap = {};
512 if (this._parentFrame)
513 this._parentFrame._childFrames.push(this);
516 WebInspector.ResourceTreeFrame.prototype = {
530 return this._name || "";
546 return this._securityOrigin;
554 return this._loaderId;
558 * @return {?WebInspector.ResourceTreeFrame}
562 return this._parentFrame;
566 * @return {!Array.<!WebInspector.ResourceTreeFrame>}
570 return this._childFrames;
576 isMainFrame: function()
578 return !this._parentFrame;
582 * @param {!PageAgent.Frame} framePayload
584 _navigate: function(framePayload)
586 this._loaderId = framePayload.loaderId;
587 this._name = framePayload.name;
588 this._url = framePayload.url;
589 this._securityOrigin = framePayload.securityOrigin;
590 this._mimeType = framePayload.mimeType;
592 var mainResource = this._resourcesMap[this._url];
593 this._resourcesMap = {};
594 this._removeChildFrames();
595 if (mainResource && mainResource.loaderId === this._loaderId)
596 this.addResource(mainResource);
600 * @return {!WebInspector.Resource}
604 return this._resourcesMap[this._url];
608 * @param {!WebInspector.ResourceTreeFrame} frame
610 _removeChildFrame: function(frame)
612 this._childFrames.remove(frame);
616 _removeChildFrames: function()
618 var frames = this._childFrames;
619 this._childFrames = [];
620 for (var i = 0; i < frames.length; ++i)
626 this._removeChildFrames();
627 delete this._model._frames[this.id];
628 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this);
632 * @param {!WebInspector.Resource} resource
634 addResource: function(resource)
636 if (this._resourcesMap[resource.url] === resource) {
637 // Already in the tree, we just got an extra update.
640 this._resourcesMap[resource.url] = resource;
641 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource);
645 * @param {!WebInspector.NetworkRequest} request
646 * @return {!WebInspector.Resource}
648 _addRequest: function(request)
650 var resource = this._resourcesMap[request.url];
651 if (resource && resource.request === request) {
652 // Already in the tree, we just got an extra update.
655 resource = new WebInspector.Resource(request, request.url, request.documentURL, request.frameId, request.loaderId, request.type, request.mimeType);
656 this._resourcesMap[resource.url] = resource;
657 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource);
662 * @return {!Array.<!WebInspector.Resource>}
664 resources: function()
667 for (var url in this._resourcesMap)
668 result.push(this._resourcesMap[url]);
673 * @param {string} url
674 * @return {?WebInspector.Resource}
676 resourceForURL: function(url)
679 function filter(resource)
681 if (resource.url === url) {
686 this._callForFrameResources(filter);
687 return result || null;
691 * @param {function(!WebInspector.Resource)} callback
694 _callForFrameResources: function(callback)
696 for (var url in this._resourcesMap) {
697 if (callback(this._resourcesMap[url]))
701 for (var i = 0; i < this._childFrames.length; ++i) {
702 if (this._childFrames[i]._callForFrameResources(callback))
711 displayName: function()
713 if (!this._parentFrame)
714 return WebInspector.UIString("<top frame>");
715 var subtitle = new WebInspector.ParsedURL(this._url).displayName;
719 return this._name + "( " + subtitle + " )";
721 return WebInspector.UIString("<iframe>");
727 * @implements {PageAgent.Dispatcher}
729 WebInspector.PageDispatcher = function(resourceTreeModel)
731 this._resourceTreeModel = resourceTreeModel;
734 WebInspector.PageDispatcher.prototype = {
735 domContentEventFired: function(time)
737 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, time);
740 loadEventFired: function(time)
742 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.Load, time);
745 frameAttached: function(frameId, parentFrameId)
747 this._resourceTreeModel._frameAttached(frameId, parentFrameId);
750 frameNavigated: function(frame)
752 this._resourceTreeModel._frameNavigated(frame);
755 frameDetached: function(frameId)
757 this._resourceTreeModel._frameDetached(frameId);
760 frameStartedLoading: function(frameId)
764 frameStoppedLoading: function(frameId)
768 frameScheduledNavigation: function(frameId, delay)
772 frameClearedScheduledNavigation: function(frameId)
776 frameResized: function()
778 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameResized, null);
781 javascriptDialogOpening: function(message)
785 javascriptDialogClosed: function()
789 scriptsEnabled: function(isEnabled)
791 WebInspector.settings.javaScriptDisabled.set(!isEnabled);
795 * @param {string} data
796 * @param {!PageAgent.ScreencastFrameMetadata=} metadata
798 screencastFrame: function(data, metadata)
800 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ScreencastFrame, {data:data, metadata:metadata});
804 * @param {boolean} visible
806 screencastVisibilityChanged: function(visible)
808 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ScreencastVisibilityChanged, {visible:visible});
813 * @type {!WebInspector.ResourceTreeModel}
815 WebInspector.resourceTreeModel;