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.SDKModel}
34 * @param {!WebInspector.Target} target
36 WebInspector.ResourceTreeModel = function(target)
38 WebInspector.SDKModel.call(this, WebInspector.ResourceTreeModel, target);
40 target.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this);
41 target.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdateDropped, this._onRequestUpdateDropped, this);
43 target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
44 target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
46 this._agent = target.pageAgent();
49 this._fetchResourceTree();
51 target.registerPageDispatcher(new WebInspector.PageDispatcher(this));
53 this._pendingConsoleMessages = {};
54 this._securityOriginFrameCount = {};
55 this._inspectedPageURL = "";
58 WebInspector.ResourceTreeModel.EventTypes = {
59 FrameAdded: "FrameAdded",
60 FrameNavigated: "FrameNavigated",
61 FrameDetached: "FrameDetached",
62 FrameResized: "FrameResized",
63 MainFrameNavigated: "MainFrameNavigated",
64 ResourceAdded: "ResourceAdded",
65 WillLoadCachedResources: "WillLoadCachedResources",
66 CachedResourcesLoaded: "CachedResourcesLoaded",
67 DOMContentLoaded: "DOMContentLoaded",
69 WillReloadPage: "WillReloadPage",
70 InspectedURLChanged: "InspectedURLChanged",
71 SecurityOriginAdded: "SecurityOriginAdded",
72 SecurityOriginRemoved: "SecurityOriginRemoved",
73 ScreencastFrame: "ScreencastFrame",
74 ScreencastVisibilityChanged: "ScreencastVisibilityChanged",
75 ViewportChanged: "ViewportChanged",
76 ColorPicked: "ColorPicked"
79 WebInspector.ResourceTreeModel.prototype = {
80 _fetchResourceTree: function()
82 /** @type {!Object.<string, !WebInspector.ResourceTreeFrame>} */
84 delete this._cachedResourcesProcessed;
85 this._agent.getResourceTree(this._processCachedResources.bind(this));
88 _processCachedResources: function(error, mainFramePayload)
91 //FIXME: remove resourceTreeModel from worker
92 if (!this.target().isWorkerTarget())
93 console.error(JSON.stringify(error));
97 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources);
98 this._inspectedPageURL = mainFramePayload.frame.url;
99 this._addFramesRecursively(null, mainFramePayload);
100 this._dispatchInspectedURLChanged();
101 this._cachedResourcesProcessed = true;
102 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded);
108 inspectedPageURL: function()
110 return this._inspectedPageURL;
116 inspectedPageDomain: function()
118 var parsedURL = this._inspectedPageURL ? this._inspectedPageURL.asParsedURL() : null;
119 return parsedURL ? parsedURL.host : "";
125 cachedResourcesLoaded: function()
127 return this._cachedResourcesProcessed;
130 _dispatchInspectedURLChanged: function()
132 InspectorFrontendHost.inspectedURLChanged(this._inspectedPageURL);
133 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedPageURL);
137 * @param {!WebInspector.ResourceTreeFrame} frame
138 * @param {boolean=} aboutToNavigate
140 _addFrame: function(frame, aboutToNavigate)
142 this._frames[frame.id] = frame;
143 if (frame.isMainFrame())
144 this.mainFrame = frame;
145 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, frame);
146 if (!aboutToNavigate)
147 this._addSecurityOrigin(frame.securityOrigin);
151 * @param {string} securityOrigin
153 _addSecurityOrigin: function(securityOrigin)
155 if (!this._securityOriginFrameCount[securityOrigin]) {
156 this._securityOriginFrameCount[securityOrigin] = 1;
157 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, securityOrigin);
160 this._securityOriginFrameCount[securityOrigin] += 1;
164 * @param {string|undefined} securityOrigin
166 _removeSecurityOrigin: function(securityOrigin)
168 if (typeof securityOrigin === "undefined")
170 if (this._securityOriginFrameCount[securityOrigin] === 1) {
171 delete this._securityOriginFrameCount[securityOrigin];
172 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, securityOrigin);
175 this._securityOriginFrameCount[securityOrigin] -= 1;
179 * @return {!Array.<string>}
181 securityOrigins: function()
183 return Object.keys(this._securityOriginFrameCount);
187 * @param {!WebInspector.ResourceTreeFrame} mainFrame
189 _handleMainFrameDetached: function(mainFrame)
192 * @param {!WebInspector.ResourceTreeFrame} frame
193 * @this {WebInspector.ResourceTreeModel}
195 function removeOriginForFrame(frame)
197 for (var i = 0; i < frame.childFrames.length; ++i)
198 removeOriginForFrame.call(this, frame.childFrames[i]);
199 if (!frame.isMainFrame())
200 this._removeSecurityOrigin(frame.securityOrigin);
202 removeOriginForFrame.call(this, mainFrame);
206 * @param {!PageAgent.FrameId} frameId
207 * @param {?PageAgent.FrameId} parentFrameId
208 * @return {?WebInspector.ResourceTreeFrame}
210 _frameAttached: function(frameId, parentFrameId)
212 // Do nothing unless cached resource tree is processed - it will overwrite everything.
213 if (!this._cachedResourcesProcessed)
215 if (this._frames[frameId])
218 var parentFrame = parentFrameId ? this._frames[parentFrameId] : null;
219 var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, frameId);
220 if (frame.isMainFrame() && this.mainFrame) {
221 this._handleMainFrameDetached(this.mainFrame);
222 // Navigation to the new backend process.
223 this._frameDetached(this.mainFrame.id);
225 this._addFrame(frame, true);
230 * @param {!PageAgent.Frame} framePayload
232 _frameNavigated: function(framePayload)
234 // Do nothing unless cached resource tree is processed - it will overwrite everything.
235 if (!this._cachedResourcesProcessed)
237 var frame = this._frames[framePayload.id];
239 // Simulate missed "frameAttached" for a main frame navigation to the new backend process.
240 console.assert(!framePayload.parentId, "Main frame shouldn't have parent frame id.");
241 frame = this._frameAttached(framePayload.id, framePayload.parentId || "");
242 console.assert(frame);
244 this._removeSecurityOrigin(frame.securityOrigin);
245 frame._navigate(framePayload);
246 var addedOrigin = frame.securityOrigin;
248 if (frame.isMainFrame())
249 this._inspectedPageURL = frame.url;
251 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, frame);
252 if (frame.isMainFrame())
253 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, frame);
255 this._addSecurityOrigin(addedOrigin);
257 // Fill frame with retained resources (the ones loaded using new loader).
258 var resources = frame.resources();
259 for (var i = 0; i < resources.length; ++i)
260 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resources[i]);
262 if (frame.isMainFrame())
263 this._dispatchInspectedURLChanged();
267 * @param {!PageAgent.FrameId} frameId
269 _frameDetached: function(frameId)
271 // Do nothing unless cached resource tree is processed - it will overwrite everything.
272 if (!this._cachedResourcesProcessed)
275 var frame = this._frames[frameId];
279 this._removeSecurityOrigin(frame.securityOrigin);
280 if (frame.parentFrame)
281 frame.parentFrame._removeChildFrame(frame);
287 * @param {!WebInspector.Event} event
289 _onRequestFinished: function(event)
291 if (!this._cachedResourcesProcessed)
294 var request = /** @type {!WebInspector.NetworkRequest} */ (event.data);
295 if (request.failed || request.resourceType() === WebInspector.resourceTypes.XHR)
298 var frame = this._frames[request.frameId];
300 var resource = frame._addRequest(request);
301 this._addPendingConsoleMessagesToResource(resource);
306 * @param {!WebInspector.Event} event
308 _onRequestUpdateDropped: function(event)
310 if (!this._cachedResourcesProcessed)
313 var frameId = event.data.frameId;
314 var frame = this._frames[frameId];
318 var url = event.data.url;
319 if (frame._resourcesMap[url])
322 var resource = new WebInspector.Resource(this.target(), null, url, frame.url, frameId, event.data.loaderId, WebInspector.resourceTypes[event.data.resourceType], event.data.mimeType);
323 frame.addResource(resource);
327 * @param {!PageAgent.FrameId} frameId
328 * @return {!WebInspector.ResourceTreeFrame}
330 frameForId: function(frameId)
332 return this._frames[frameId];
336 * @param {function(!WebInspector.Resource)} callback
339 forAllResources: function(callback)
342 return this.mainFrame._callForFrameResources(callback);
347 * @return {!Array.<!WebInspector.ResourceTreeFrame>}
351 return Object.values(this._frames);
355 * @param {!WebInspector.Event} event
357 _consoleMessageAdded: function(event)
359 var msg = /** @type {!WebInspector.ConsoleMessage} */ (event.data);
360 var resource = msg.url ? this.resourceForURL(msg.url) : null;
362 this._addConsoleMessageToResource(msg, resource);
364 this._addPendingConsoleMessage(msg);
368 * @param {!WebInspector.ConsoleMessage} msg
370 _addPendingConsoleMessage: function(msg)
374 if (!this._pendingConsoleMessages[msg.url])
375 this._pendingConsoleMessages[msg.url] = [];
376 this._pendingConsoleMessages[msg.url].push(msg);
380 * @param {!WebInspector.Resource} resource
382 _addPendingConsoleMessagesToResource: function(resource)
384 var messages = this._pendingConsoleMessages[resource.url];
386 for (var i = 0; i < messages.length; i++)
387 this._addConsoleMessageToResource(messages[i], resource);
388 delete this._pendingConsoleMessages[resource.url];
393 * @param {!WebInspector.ConsoleMessage} msg
394 * @param {!WebInspector.Resource} resource
396 _addConsoleMessageToResource: function(msg, resource)
399 case WebInspector.ConsoleMessage.MessageLevel.Warning:
402 case WebInspector.ConsoleMessage.MessageLevel.Error:
406 resource.addMessage(msg);
409 _consoleCleared: function()
411 function callback(resource)
413 resource.clearErrorsAndWarnings();
416 this._pendingConsoleMessages = {};
417 this.forAllResources(callback);
421 * @param {string} url
422 * @return {?WebInspector.Resource}
424 resourceForURL: function(url)
426 // Workers call into this with no frames available.
427 return this.mainFrame ? this.mainFrame.resourceForURL(url) : null;
431 * @param {?WebInspector.ResourceTreeFrame} parentFrame
432 * @param {!PageAgent.FrameResourceTree} frameTreePayload
434 _addFramesRecursively: function(parentFrame, frameTreePayload)
436 var framePayload = frameTreePayload.frame;
437 var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, framePayload.id, framePayload);
438 this._addFrame(frame);
440 var frameResource = this._createResourceFromFramePayload(framePayload, framePayload.url, WebInspector.resourceTypes.Document, framePayload.mimeType);
441 if (frame.isMainFrame())
442 this._inspectedPageURL = frameResource.url;
443 // FIXME(413891): This check could be removed once we stop to send frame tree for service/shared workers.
444 // This makes sure that the shadow page document resource does not hide the worker script resource (they have the same url).
445 if (!WebInspector.isWorkerFrontend())
446 frame.addResource(frameResource);
448 for (var i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i)
449 this._addFramesRecursively(frame, frameTreePayload.childFrames[i]);
451 for (var i = 0; i < frameTreePayload.resources.length; ++i) {
452 var subresource = frameTreePayload.resources[i];
453 var resource = this._createResourceFromFramePayload(framePayload, subresource.url, WebInspector.resourceTypes[subresource.type], subresource.mimeType);
454 frame.addResource(resource);
459 * @param {!PageAgent.Frame} frame
460 * @param {string} url
461 * @param {!WebInspector.ResourceType} type
462 * @param {string} mimeType
463 * @return {!WebInspector.Resource}
465 _createResourceFromFramePayload: function(frame, url, type, mimeType)
467 return new WebInspector.Resource(this.target(), null, url, frame.url, frame.id, frame.loaderId, type, mimeType);
471 * @param {boolean=} ignoreCache
472 * @param {string=} scriptToEvaluateOnLoad
473 * @param {string=} scriptPreprocessor
475 reloadPage: function(ignoreCache, scriptToEvaluateOnLoad, scriptPreprocessor)
477 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillReloadPage);
478 this._agent.reload(ignoreCache, scriptToEvaluateOnLoad, scriptPreprocessor);
481 __proto__: WebInspector.SDKModel.prototype
486 * @param {!WebInspector.ResourceTreeModel} model
487 * @param {?WebInspector.ResourceTreeFrame} parentFrame
488 * @param {!PageAgent.FrameId} frameId
489 * @param {!PageAgent.Frame=} payload
491 WebInspector.ResourceTreeFrame = function(model, parentFrame, frameId, payload)
494 this._parentFrame = parentFrame;
499 this._loaderId = payload.loaderId;
500 this._name = payload.name;
501 this._url = payload.url;
502 this._securityOrigin = payload.securityOrigin;
503 this._mimeType = payload.mimeType;
507 * @type {!Array.<!WebInspector.ResourceTreeFrame>}
509 this._childFrames = [];
512 * @type {!Object.<string, !WebInspector.Resource>}
514 this._resourcesMap = {};
516 if (this._parentFrame)
517 this._parentFrame._childFrames.push(this);
520 WebInspector.ResourceTreeFrame.prototype = {
522 * @return {!WebInspector.Target}
526 return this._model.target();
542 return this._name || "";
558 return this._securityOrigin;
566 return this._loaderId;
570 * @return {?WebInspector.ResourceTreeFrame}
574 return this._parentFrame;
578 * @return {!Array.<!WebInspector.ResourceTreeFrame>}
582 return this._childFrames;
588 isMainFrame: function()
590 return !this._parentFrame;
594 * @param {!PageAgent.Frame} framePayload
596 _navigate: function(framePayload)
598 this._loaderId = framePayload.loaderId;
599 this._name = framePayload.name;
600 this._url = framePayload.url;
601 this._securityOrigin = framePayload.securityOrigin;
602 this._mimeType = framePayload.mimeType;
604 var mainResource = this._resourcesMap[this._url];
605 this._resourcesMap = {};
606 this._removeChildFrames();
607 if (mainResource && mainResource.loaderId === this._loaderId)
608 this.addResource(mainResource);
612 * @return {!WebInspector.Resource}
616 return this._resourcesMap[this._url];
620 * @param {!WebInspector.ResourceTreeFrame} frame
622 _removeChildFrame: function(frame)
624 this._childFrames.remove(frame);
628 _removeChildFrames: function()
630 var frames = this._childFrames;
631 this._childFrames = [];
632 for (var i = 0; i < frames.length; ++i)
638 this._removeChildFrames();
639 delete this._model._frames[this.id];
640 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this);
644 * @param {!WebInspector.Resource} resource
646 addResource: function(resource)
648 if (this._resourcesMap[resource.url] === resource) {
649 // Already in the tree, we just got an extra update.
652 this._resourcesMap[resource.url] = resource;
653 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource);
657 * @param {!WebInspector.NetworkRequest} request
658 * @return {!WebInspector.Resource}
660 _addRequest: function(request)
662 var resource = this._resourcesMap[request.url];
663 if (resource && resource.request === request) {
664 // Already in the tree, we just got an extra update.
667 resource = new WebInspector.Resource(this.target(), request, request.url, request.documentURL, request.frameId, request.loaderId, request.resourceType(), request.mimeType);
668 this._resourcesMap[resource.url] = resource;
669 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource);
674 * @return {!Array.<!WebInspector.Resource>}
676 resources: function()
679 for (var url in this._resourcesMap)
680 result.push(this._resourcesMap[url]);
685 * @param {string} url
686 * @return {?WebInspector.Resource}
688 resourceForURL: function(url)
691 function filter(resource)
693 if (resource.url === url) {
698 this._callForFrameResources(filter);
699 return result || null;
703 * @param {function(!WebInspector.Resource)} callback
706 _callForFrameResources: function(callback)
708 for (var url in this._resourcesMap) {
709 if (callback(this._resourcesMap[url]))
713 for (var i = 0; i < this._childFrames.length; ++i) {
714 if (this._childFrames[i]._callForFrameResources(callback))
723 displayName: function()
725 if (!this._parentFrame)
726 return WebInspector.UIString("<top frame>");
727 var subtitle = new WebInspector.ParsedURL(this._url).displayName;
731 return this._name + "( " + subtitle + " )";
733 return WebInspector.UIString("<iframe>");
739 * @implements {PageAgent.Dispatcher}
741 WebInspector.PageDispatcher = function(resourceTreeModel)
743 this._resourceTreeModel = resourceTreeModel;
746 WebInspector.PageDispatcher.prototype = {
749 * @param {number} time
751 domContentEventFired: function(time)
753 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, time);
758 * @param {number} time
760 loadEventFired: function(time)
762 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.Load, time);
765 frameAttached: function(frameId, parentFrameId)
767 this._resourceTreeModel._frameAttached(frameId, parentFrameId);
770 frameNavigated: function(frame)
772 this._resourceTreeModel._frameNavigated(frame);
775 frameDetached: function(frameId)
777 this._resourceTreeModel._frameDetached(frameId);
780 frameStartedLoading: function(frameId)
784 frameStoppedLoading: function(frameId)
788 frameScheduledNavigation: function(frameId, delay)
792 frameClearedScheduledNavigation: function(frameId)
796 frameResized: function()
798 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameResized, null);
801 javascriptDialogOpening: function(message)
805 javascriptDialogClosed: function()
809 scriptsEnabled: function(isEnabled)
811 WebInspector.settings.javaScriptDisabled.set(!isEnabled);
815 * @param {string} data
816 * @param {!PageAgent.ScreencastFrameMetadata=} metadata
818 screencastFrame: function(data, metadata)
820 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ScreencastFrame, {data:data, metadata:metadata});
824 * @param {boolean} visible
826 screencastVisibilityChanged: function(visible)
828 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ScreencastVisibilityChanged, {visible:visible});
832 * @param {!PageAgent.Viewport=} viewport
834 viewportChanged: function(viewport)
836 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ViewportChanged, viewport);
840 * @param {!DOMAgent.RGBA} color
842 colorPicked: function(color)
844 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ColorPicked, color);
847 interstitialShown: function()
849 // Frontend is not interested in interstitials.
852 interstitialHidden: function()
854 // Frontend is not interested in interstitials.
859 * @type {!WebInspector.ResourceTreeModel}
861 WebInspector.resourceTreeModel;