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 * @constructor 33 * @extends {WebInspector.Object} 34 * @param {WebInspector.NetworkManager} networkManager 35 */ 36 WebInspector.ResourceTreeModel = function(networkManager) 37 { 38 networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestFinished, this); 39 networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdateDropped, this._onRequestUpdateDropped, this); 40 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); 44 45 PageAgent.enable(); 46 47 NetworkAgent.enable(); 48 this._fetchResourceTree(); 49 50 InspectorBackend.registerPageDispatcher(new WebInspector.PageDispatcher(this)); 51 52 this._pendingConsoleMessages = {}; 53 this._securityOriginFrameCount = {}; 54 } 55 56 WebInspector.ResourceTreeModel.EventTypes = { 57 FrameAdded: "FrameAdded", 58 FrameNavigated: "FrameNavigated", 59 FrameDetached: "FrameDetached", 60 MainFrameNavigated: "MainFrameNavigated", 61 MainFrameCreatedOrNavigated: "MainFrameCreatedOrNavigated", 62 ResourceAdded: "ResourceAdded", 63 WillLoadCachedResources: "WillLoadCachedResources", 64 CachedResourcesLoaded: "CachedResourcesLoaded", 65 DOMContentLoaded: "DOMContentLoaded", 66 Load: "Load", 67 InspectedURLChanged: "InspectedURLChanged", 68 SecurityOriginAdded: "SecurityOriginAdded", 69 SecurityOriginRemoved: "SecurityOriginRemoved" 70 } 71 72 WebInspector.ResourceTreeModel.prototype = { 73 _fetchResourceTree: function() 74 { 75 /** @type {!Object.<string, !WebInspector.ResourceTreeFrame>} */ 76 this._frames = {}; 77 delete this._cachedResourcesProcessed; 78 PageAgent.getResourceTree(this._processCachedResources.bind(this)); 79 }, 80 81 _processCachedResources: function(error, mainFramePayload) 82 { 83 if (error) { 84 console.error(JSON.stringify(error)); 85 return; 86 } 87 88 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources); 89 WebInspector.inspectedPageURL = mainFramePayload.frame.url; 90 this._addFramesRecursively(null, mainFramePayload); 91 this._dispatchInspectedURLChanged(); 92 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded); 93 this._cachedResourcesProcessed = true; 94 }, 95 96 cachedResourcesLoaded: function() 97 { 98 return this._cachedResourcesProcessed; 99 }, 100 101 _dispatchInspectedURLChanged: function() 102 { 103 InspectorFrontendHost.inspectedURLChanged(WebInspector.inspectedPageURL); 104 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, WebInspector.inspectedPageURL); 105 }, 106 107 /** 108 * @param {WebInspector.ResourceTreeFrame} frame 109 * @param {boolean=} aboutToNavigate 110 */ 111 _addFrame: function(frame, aboutToNavigate) 112 { 113 this._frames[frame.id] = frame; 114 if (frame.isMainFrame()) 115 this.mainFrame = frame; 116 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, frame); 117 if (!aboutToNavigate) 118 this._addSecurityOrigin(frame.securityOrigin); 119 if (frame.isMainFrame()) 120 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, frame); 121 }, 122 123 /** 124 * @param {string} securityOrigin 125 */ 126 _addSecurityOrigin: function(securityOrigin) 127 { 128 if (!this._securityOriginFrameCount[securityOrigin]) { 129 this._securityOriginFrameCount[securityOrigin] = 1; 130 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, securityOrigin); 131 return; 132 } 133 this._securityOriginFrameCount[securityOrigin] += 1; 134 }, 135 136 /** 137 * @param {string} securityOrigin 138 */ 139 _removeSecurityOrigin: function(securityOrigin) 140 { 141 console.assert(this._securityOriginFrameCount[securityOrigin]); 142 if (this._securityOriginFrameCount[securityOrigin] === 1) { 143 delete this._securityOriginFrameCount[securityOrigin]; 144 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, securityOrigin); 145 return; 146 } 147 this._securityOriginFrameCount[securityOrigin] -= 1; 148 }, 149 150 /** 151 * @return {Array.<string>} 152 */ 153 securityOrigins: function() 154 { 155 return Object.keys(this._securityOriginFrameCount); 156 }, 157 158 /** 159 * @param {WebInspector.ResourceTreeFrame} mainFrame 160 */ 161 _handleMainFrameDetached: function(mainFrame) 162 { 163 /** 164 * @param {WebInspector.ResourceTreeFrame} frame 165 */ 166 function removeOriginForFrame(frame) 167 { 168 for (var i = 0; i < frame.childFrames.length; ++i) 169 removeOriginForFrame.call(this, frame.childFrames[i]); 170 if (!frame.isMainFrame()) 171 this._removeSecurityOrigin(frame.securityOrigin); 172 } 173 removeOriginForFrame.call(this, WebInspector.resourceTreeModel.mainFrame); 174 }, 175 176 /** 177 * @param {PageAgent.Frame} framePayload 178 */ 179 _frameNavigated: function(framePayload) 180 { 181 // Do nothing unless cached resource tree is processed - it will overwrite everything. 182 if (!this._cachedResourcesProcessed) 183 return; 184 var frame = this._frames[framePayload.id]; 185 var addedOrigin; 186 if (frame) { 187 // Navigation within existing frame. 188 this._removeSecurityOrigin(frame.securityOrigin); 189 frame._navigate(framePayload); 190 addedOrigin = frame.securityOrigin; 191 } else { 192 // Either a new frame or a main frame navigation to the new backend process. 193 var parentFrame = framePayload.parentId ? this._frames[framePayload.parentId] : null; 194 frame = new WebInspector.ResourceTreeFrame(this, parentFrame, framePayload); 195 if (frame.isMainFrame() && this.mainFrame) { 196 this._handleMainFrameDetached(this.mainFrame); 197 // Definitely a navigation to the new backend process. 198 this._frameDetached(this.mainFrame.id); 199 } 200 this._addFrame(frame, true); 201 addedOrigin = frame.securityOrigin; 202 } 203 204 if (frame.isMainFrame()) 205 WebInspector.inspectedPageURL = frame.url; 206 207 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, frame); 208 if (frame.isMainFrame()) { 209 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, frame); 210 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameCreatedOrNavigated, frame); 211 } 212 if (addedOrigin) 213 this._addSecurityOrigin(addedOrigin); 214 215 // Fill frame with retained resources (the ones loaded using new loader). 216 var resources = frame.resources(); 217 for (var i = 0; i < resources.length; ++i) 218 this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resources[i]); 219 220 if (frame.isMainFrame()) 221 this._dispatchInspectedURLChanged(); 222 }, 223 224 /** 225 * @param {NetworkAgent.FrameId} frameId 226 */ 227 _frameDetached: function(frameId) 228 { 229 // Do nothing unless cached resource tree is processed - it will overwrite everything. 230 if (!this._cachedResourcesProcessed) 231 return; 232 233 var frame = this._frames[frameId]; 234 if (!frame) 235 return; 236 237 this._removeSecurityOrigin(frame.securityOrigin); 238 if (frame.parentFrame) 239 frame.parentFrame._removeChildFrame(frame); 240 else 241 frame._remove(); 242 }, 243 244 /** 245 * @param {WebInspector.Event} event 246 */ 247 _onRequestFinished: function(event) 248 { 249 if (!this._cachedResourcesProcessed) 250 return; 251 252 var request = /** @type {WebInspector.NetworkRequest} */ (event.data); 253 if (request.failed || request.type === WebInspector.resourceTypes.XHR) 254 return; 255 256 var frame = this._frames[request.frameId]; 257 if (frame) { 258 var resource = frame._addRequest(request); 259 this._addPendingConsoleMessagesToResource(resource); 260 } 261 }, 262 263 /** 264 * @param {WebInspector.Event} event 265 */ 266 _onRequestUpdateDropped: function(event) 267 { 268 if (!this._cachedResourcesProcessed) 269 return; 270 271 var frameId = event.data.frameId; 272 var frame = this._frames[frameId]; 273 if (!frame) 274 return; 275 276 var url = event.data.url; 277 if (frame._resourcesMap[url]) 278 return; 279 280 var resource = new WebInspector.Resource(null, url, frame.url, frameId, event.data.loaderId, WebInspector.resourceTypes[event.data.resourceType], event.data.mimeType); 281 frame.addResource(resource); 282 }, 283 284 /** 285 * @param {NetworkAgent.FrameId} frameId 286 * @return {WebInspector.ResourceTreeFrame} 287 */ 288 frameForId: function(frameId) 289 { 290 return this._frames[frameId]; 291 }, 292 293 /** 294 * @param {function(WebInspector.Resource)} callback 295 * @return {boolean} 296 */ 297 forAllResources: function(callback) 298 { 299 if (this.mainFrame) 300 return this.mainFrame._callForFrameResources(callback); 301 return false; 302 }, 303 304 /** 305 * @return {Array.<WebInspector.ResourceTreeFrame>} 306 */ 307 frames: function() 308 { 309 return Object.values(this._frames); 310 }, 311 312 /** 313 * @param {WebInspector.Event} event 314 */ 315 _consoleMessageAdded: function(event) 316 { 317 var msg = /** @type {WebInspector.ConsoleMessage} */ (event.data); 318 var resource = msg.url ? this.resourceForURL(msg.url) : null; 319 if (resource) 320 this._addConsoleMessageToResource(msg, resource); 321 else 322 this._addPendingConsoleMessage(msg); 323 }, 324 325 /** 326 * @param {WebInspector.ConsoleMessage} msg 327 */ 328 _addPendingConsoleMessage: function(msg) 329 { 330 if (!msg.url) 331 return; 332 if (!this._pendingConsoleMessages[msg.url]) 333 this._pendingConsoleMessages[msg.url] = []; 334 this._pendingConsoleMessages[msg.url].push(msg); 335 }, 336 337 /** 338 * @param {WebInspector.Resource} resource 339 */ 340 _addPendingConsoleMessagesToResource: function(resource) 341 { 342 var messages = this._pendingConsoleMessages[resource.url]; 343 if (messages) { 344 for (var i = 0; i < messages.length; i++) 345 this._addConsoleMessageToResource(messages[i], resource); 346 delete this._pendingConsoleMessages[resource.url]; 347 } 348 }, 349 350 /** 351 * @param {WebInspector.ConsoleMessage} msg 352 * @param {WebInspector.Resource} resource 353 */ 354 _addConsoleMessageToResource: function(msg, resource) 355 { 356 switch (msg.level) { 357 case WebInspector.ConsoleMessage.MessageLevel.Warning: 358 resource.warnings += msg.repeatDelta; 359 break; 360 case WebInspector.ConsoleMessage.MessageLevel.Error: 361 resource.errors += msg.repeatDelta; 362 break; 363 } 364 resource.addMessage(msg); 365 }, 366 367 _consoleCleared: function() 368 { 369 function callback(resource) 370 { 371 resource.clearErrorsAndWarnings(); 372 } 373 374 this._pendingConsoleMessages = {}; 375 this.forAllResources(callback); 376 }, 377 378 /** 379 * @param {string} url 380 * @return {WebInspector.Resource} 381 */ 382 resourceForURL: function(url) 383 { 384 // Workers call into this with no frames available. 385 return this.mainFrame ? this.mainFrame.resourceForURL(url) : null; 386 }, 387 388 /** 389 * @param {WebInspector.ResourceTreeFrame} parentFrame 390 * @param {PageAgent.FrameResourceTree} frameTreePayload 391 */ 392 _addFramesRecursively: function(parentFrame, frameTreePayload) 393 { 394 var framePayload = frameTreePayload.frame; 395 var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, framePayload); 396 this._addFrame(frame); 397 398 var frameResource = this._createResourceFromFramePayload(framePayload, framePayload.url, WebInspector.resourceTypes.Document, framePayload.mimeType); 399 if (frame.isMainFrame()) 400 WebInspector.inspectedPageURL = frameResource.url; 401 frame.addResource(frameResource); 402 403 for (var i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i) 404 this._addFramesRecursively(frame, frameTreePayload.childFrames[i]); 405 406 for (var i = 0; i < frameTreePayload.resources.length; ++i) { 407 var subresource = frameTreePayload.resources[i]; 408 var resource = this._createResourceFromFramePayload(framePayload, subresource.url, WebInspector.resourceTypes[subresource.type], subresource.mimeType); 409 frame.addResource(resource); 410 } 411 }, 412 413 /** 414 * @param {PageAgent.Frame} frame 415 * @param {string} url 416 * @param {WebInspector.ResourceType} type 417 * @param {string} mimeType 418 * @return {!WebInspector.Resource} 419 */ 420 _createResourceFromFramePayload: function(frame, url, type, mimeType) 421 { 422 return new WebInspector.Resource(null, url, frame.url, frame.id, frame.loaderId, type, mimeType); 423 }, 424 425 __proto__: WebInspector.Object.prototype 426 } 427 428 /** 429 * @constructor 430 * @param {WebInspector.ResourceTreeModel} model 431 * @param {?WebInspector.ResourceTreeFrame} parentFrame 432 * @param {PageAgent.Frame} payload 433 */ 434 WebInspector.ResourceTreeFrame = function(model, parentFrame, payload) 435 { 436 this._model = model; 437 this._parentFrame = parentFrame; 438 439 this._id = payload.id; 440 this._loaderId = payload.loaderId; 441 this._name = payload.name; 442 this._url = payload.url; 443 this._securityOrigin = payload.securityOrigin; 444 this._mimeType = payload.mimeType; 445 446 /** 447 * @type {!Array.<!WebInspector.ResourceTreeFrame>} 448 */ 449 this._childFrames = []; 450 451 /** 452 * @type {!Object.<string, !WebInspector.Resource>} 453 */ 454 this._resourcesMap = {}; 455 456 if (this._parentFrame) 457 this._parentFrame._childFrames.push(this); 458 } 459 460 WebInspector.ResourceTreeFrame.prototype = { 461 /** 462 * @return {string} 463 */ 464 get id() 465 { 466 return this._id; 467 }, 468 469 /** 470 * @return {string} 471 */ 472 get name() 473 { 474 return this._name || ""; 475 }, 476 477 /** 478 * @return {string} 479 */ 480 get url() 481 { 482 return this._url; 483 }, 484 485 /** 486 * @return {string} 487 */ 488 get securityOrigin() 489 { 490 return this._securityOrigin; 491 }, 492 493 /** 494 * @return {string} 495 */ 496 get loaderId() 497 { 498 return this._loaderId; 499 }, 500 501 /** 502 * @return {WebInspector.ResourceTreeFrame} 503 */ 504 get parentFrame() 505 { 506 return this._parentFrame; 507 }, 508 509 /** 510 * @return {!Array.<!WebInspector.ResourceTreeFrame>} 511 */ 512 get childFrames() 513 { 514 return this._childFrames; 515 }, 516 517 /** 518 * @return {boolean} 519 */ 520 isMainFrame: function() 521 { 522 return !this._parentFrame; 523 }, 524 525 /** 526 * @param {PageAgent.Frame} framePayload 527 */ 528 _navigate: function(framePayload) 529 { 530 this._loaderId = framePayload.loaderId; 531 this._name = framePayload.name; 532 this._url = framePayload.url; 533 this._securityOrigin = framePayload.securityOrigin; 534 this._mimeType = framePayload.mimeType; 535 536 var mainResource = this._resourcesMap[this._url]; 537 this._resourcesMap = {}; 538 this._removeChildFrames(); 539 if (mainResource && mainResource.loaderId === this._loaderId) 540 this.addResource(mainResource); 541 }, 542 543 /** 544 * @return {WebInspector.Resource} 545 */ 546 get mainResource() 547 { 548 return this._resourcesMap[this._url]; 549 }, 550 551 /** 552 * @param {!WebInspector.ResourceTreeFrame} frame 553 */ 554 _removeChildFrame: function(frame) 555 { 556 this._childFrames.remove(frame); 557 frame._remove(); 558 }, 559 560 _removeChildFrames: function() 561 { 562 var frames = this._childFrames; 563 this._childFrames = []; 564 for (var i = 0; i < frames.length; ++i) 565 frames[i]._remove(); 566 }, 567 568 _remove: function() 569 { 570 this._removeChildFrames(); 571 delete this._model._frames[this.id]; 572 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this); 573 }, 574 575 /** 576 * @param {!WebInspector.Resource} resource 577 */ 578 addResource: function(resource) 579 { 580 if (this._resourcesMap[resource.url] === resource) { 581 // Already in the tree, we just got an extra update. 582 return; 583 } 584 this._resourcesMap[resource.url] = resource; 585 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource); 586 }, 587 588 /** 589 * @param {WebInspector.NetworkRequest} request 590 * @return {WebInspector.Resource} 591 */ 592 _addRequest: function(request) 593 { 594 var resource = this._resourcesMap[request.url]; 595 if (resource && resource.request === request) { 596 // Already in the tree, we just got an extra update. 597 return resource; 598 } 599 resource = new WebInspector.Resource(request, request.url, request.documentURL, request.frameId, request.loaderId, request.type, request.mimeType); 600 this._resourcesMap[resource.url] = resource; 601 this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource); 602 return resource; 603 }, 604 605 /** 606 * @return {!Array.<!WebInspector.Resource>} 607 */ 608 resources: function() 609 { 610 var result = []; 611 for (var url in this._resourcesMap) 612 result.push(this._resourcesMap[url]); 613 return result; 614 }, 615 616 /** 617 * @param {string} url 618 * @return {?WebInspector.Resource} 619 */ 620 resourceForURL: function(url) 621 { 622 var result; 623 function filter(resource) 624 { 625 if (resource.url === url) { 626 result = resource; 627 return true; 628 } 629 } 630 this._callForFrameResources(filter); 631 return result; 632 }, 633 634 /** 635 * @param {function(WebInspector.Resource)} callback 636 * @return {boolean} 637 */ 638 _callForFrameResources: function(callback) 639 { 640 for (var url in this._resourcesMap) { 641 if (callback(this._resourcesMap[url])) 642 return true; 643 } 644 645 for (var i = 0; i < this._childFrames.length; ++i) { 646 if (this._childFrames[i]._callForFrameResources(callback)) 647 return true; 648 } 649 return false; 650 } 651 } 652 653 /** 654 * @constructor 655 * @implements {PageAgent.Dispatcher} 656 */ 657 WebInspector.PageDispatcher = function(resourceTreeModel) 658 { 659 this._resourceTreeModel = resourceTreeModel; 660 } 661 662 WebInspector.PageDispatcher.prototype = { 663 domContentEventFired: function(time) 664 { 665 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, time); 666 }, 667 668 loadEventFired: function(time) 669 { 670 this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.Load, time); 671 }, 672 673 frameNavigated: function(frame) 674 { 675 this._resourceTreeModel._frameNavigated(frame); 676 }, 677 678 frameDetached: function(frameId) 679 { 680 this._resourceTreeModel._frameDetached(frameId); 681 }, 682 683 frameStartedLoading: function(frameId) 684 { 685 }, 686 687 frameStoppedLoading: function(frameId) 688 { 689 }, 690 691 frameScheduledNavigation: function(frameId, delay) 692 { 693 }, 694 695 frameClearedScheduledNavigation: function(frameId) 696 { 697 }, 698 699 javascriptDialogOpening: function(message) 700 { 701 }, 702 703 javascriptDialogClosed: function() 704 { 705 }, 706 707 scriptsEnabled: function(isEnabled) 708 { 709 WebInspector.settings.javaScriptDisabled.set(!isEnabled); 710 } 711 } 712 713 /** 714 * @type {WebInspector.ResourceTreeModel} 715 */ 716 WebInspector.resourceTreeModel = null; 717