1 /* 2 * Copyright (C) 2007, 2008, 2010 Apple Inc. All rights reserved. 3 * Copyright (C) 2009 Joseph Pecoraro 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 WebInspector.ResourcesPanel = function(database) 31 { 32 WebInspector.Panel.call(this, "resources"); 33 34 WebInspector.settings.installApplicationSetting("resourcesLastSelectedItem", {}); 35 36 this.createSidebar(); 37 this.sidebarElement.addStyleClass("outline-disclosure"); 38 this.sidebarElement.addStyleClass("filter-all"); 39 this.sidebarElement.addStyleClass("children"); 40 this.sidebarElement.addStyleClass("small"); 41 this.sidebarTreeElement.removeStyleClass("sidebar-tree"); 42 43 this.resourcesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Frames"), "Frames", ["frame-storage-tree-item"]); 44 this.sidebarTree.appendChild(this.resourcesListTreeElement); 45 46 this.databasesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Databases"), "Databases", ["database-storage-tree-item"]); 47 this.sidebarTree.appendChild(this.databasesListTreeElement); 48 49 this.localStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Local Storage"), "LocalStorage", ["domstorage-storage-tree-item", "local-storage"]); 50 this.sidebarTree.appendChild(this.localStorageListTreeElement); 51 52 this.sessionStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Session Storage"), "SessionStorage", ["domstorage-storage-tree-item", "session-storage"]); 53 this.sidebarTree.appendChild(this.sessionStorageListTreeElement); 54 55 this.cookieListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Cookies"), "Cookies", ["cookie-storage-tree-item"]); 56 this.sidebarTree.appendChild(this.cookieListTreeElement); 57 58 this.applicationCacheListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Application Cache"), "ApplicationCache", ["application-cache-storage-tree-item"]); 59 this.sidebarTree.appendChild(this.applicationCacheListTreeElement); 60 61 this.storageViews = document.createElement("div"); 62 this.storageViews.id = "storage-views"; 63 this.storageViews.className = "diff-container"; 64 this.element.appendChild(this.storageViews); 65 66 this.storageViewStatusBarItemsContainer = document.createElement("div"); 67 this.storageViewStatusBarItemsContainer.className = "status-bar-items"; 68 69 this._databases = []; 70 this._domStorage = []; 71 this._cookieViews = {}; 72 this._origins = {}; 73 this._domains = {}; 74 75 this.sidebarElement.addEventListener("mousemove", this._onmousemove.bind(this), false); 76 this.sidebarElement.addEventListener("mouseout", this._onmouseout.bind(this), false); 77 78 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._refreshResource, this); 79 } 80 81 WebInspector.ResourcesPanel.prototype = { 82 get toolbarItemLabel() 83 { 84 return WebInspector.UIString("Resources"); 85 }, 86 87 get statusBarItems() 88 { 89 return [this.storageViewStatusBarItemsContainer]; 90 }, 91 92 elementsToRestoreScrollPositionsFor: function() 93 { 94 return [this.sidebarElement]; 95 }, 96 97 show: function() 98 { 99 WebInspector.Panel.prototype.show.call(this); 100 101 this._populateResourceTree(); 102 103 if (this.visibleView && this.visibleView.resource) 104 this._showResourceView(this.visibleView.resource); 105 }, 106 107 loadEventFired: function() 108 { 109 this._initDefaultSelection(); 110 }, 111 112 _initDefaultSelection: function() 113 { 114 var itemURL = WebInspector.settings.resourcesLastSelectedItem; 115 if (itemURL) { 116 for (var treeElement = this.sidebarTree.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.sidebarTree, true)) { 117 if (treeElement.itemURL === itemURL) { 118 treeElement.select(); 119 treeElement.reveal(); 120 return; 121 } 122 } 123 } 124 125 if (WebInspector.mainResource && this.resourcesListTreeElement && this.resourcesListTreeElement.expanded) 126 this.showResource(WebInspector.mainResource); 127 }, 128 129 reset: function() 130 { 131 delete this._initializedDefaultSelection; 132 this._origins = {}; 133 this._domains = {}; 134 for (var i = 0; i < this._databases.length; ++i) { 135 var database = this._databases[i]; 136 delete database._tableViews; 137 delete database._queryView; 138 } 139 this._databases = []; 140 141 var domStorageLength = this._domStorage.length; 142 for (var i = 0; i < this._domStorage.length; ++i) { 143 var domStorage = this._domStorage[i]; 144 delete domStorage._domStorageView; 145 } 146 this._domStorage = []; 147 148 this._cookieViews = {}; 149 150 this._applicationCacheView = null; 151 delete this._cachedApplicationCacheViewStatus; 152 153 this.databasesListTreeElement.removeChildren(); 154 this.localStorageListTreeElement.removeChildren(); 155 this.sessionStorageListTreeElement.removeChildren(); 156 this.cookieListTreeElement.removeChildren(); 157 this.applicationCacheListTreeElement.removeChildren(); 158 this.storageViews.removeChildren(); 159 160 this.storageViewStatusBarItemsContainer.removeChildren(); 161 162 if (this.sidebarTree.selectedTreeElement) 163 this.sidebarTree.selectedTreeElement.deselect(); 164 }, 165 166 _populateResourceTree: function() 167 { 168 if (this._treeElementForFrameId) 169 return; 170 171 this._treeElementForFrameId = {}; 172 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this); 173 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this); 174 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this); 175 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, this._resourceAdded, this); 176 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._cachedResourcesLoaded, this); 177 178 function populateFrame(frameId) 179 { 180 var subframes = WebInspector.resourceTreeModel.subframes(frameId); 181 for (var i = 0; i < subframes.length; ++i) { 182 this._frameAdded({data:subframes[i]}); 183 populateFrame.call(this, subframes[i].id); 184 } 185 186 var resources = WebInspector.resourceTreeModel.resources(frameId); 187 for (var i = 0; i < resources.length; ++i) 188 this._resourceAdded({data:resources[i]}); 189 } 190 populateFrame.call(this, 0); 191 }, 192 193 _frameAdded: function(event) 194 { 195 var frame = event.data; 196 var frameId = frame.id; 197 var parentFrameId = frame.parentId; 198 var title = frame.name; 199 var subtitle = new WebInspector.Resource(null, frame.url).displayName; 200 this.addDocumentURL(frame.url); 201 202 var frameTreeElement = this._treeElementForFrameId[frameId]; 203 if (frameTreeElement) { 204 // Maintain sorted order. 205 var parent = frameTreeElement.parent; 206 parent.removeChild(frameTreeElement); 207 frameTreeElement.setTitles(title, subtitle); 208 parent.appendChild(frameTreeElement); 209 return; 210 } 211 212 var parentTreeElement = parentFrameId ? this._treeElementForFrameId[parentFrameId] : this.resourcesListTreeElement; 213 if (!parentTreeElement) { 214 console.warn("No frame with id:" + parentFrameId + " to route " + title + "/" + subtitle + " to.") 215 return; 216 } 217 218 var frameTreeElement = new WebInspector.FrameTreeElement(this, frameId, title, subtitle); 219 this._treeElementForFrameId[frameId] = frameTreeElement; 220 parentTreeElement.appendChild(frameTreeElement); 221 }, 222 223 _frameDetached: function(event) 224 { 225 var frameId = event.data; 226 var frameTreeElement = this._treeElementForFrameId[frameId]; 227 if (!frameTreeElement) 228 return; 229 delete this._treeElementForFrameId[frameId]; 230 if (frameTreeElement.parent) 231 frameTreeElement.parent.removeChild(frameTreeElement); 232 }, 233 234 _resourceAdded: function(event) 235 { 236 var resource = event.data; 237 var frameId = resource.frameId; 238 239 if (resource.statusCode >= 301 && resource.statusCode <= 303) 240 return; 241 242 var frameTreeElement = this._treeElementForFrameId[frameId]; 243 if (!frameTreeElement) { 244 // This is a frame's main resource, it will be retained 245 // and re-added by the resource manager; 246 return; 247 } 248 249 frameTreeElement.appendResource(resource); 250 }, 251 252 _frameNavigated: function(event) 253 { 254 var frameId = event.data; 255 if (!frameId) { 256 // Total update. 257 this.resourcesListTreeElement.removeChildren(); 258 this._treeElementForFrameId = {}; 259 this.reset(); 260 return; 261 } 262 263 var frameTreeElement = this._treeElementForFrameId[frameId]; 264 if (frameTreeElement) 265 frameTreeElement.removeChildren(); 266 }, 267 268 _cachedResourcesLoaded: function() 269 { 270 this._initDefaultSelection(); 271 }, 272 273 _refreshResource: function(event) 274 { 275 var resource = event.data; 276 // FIXME: do not add XHR in the first place based on the native instrumentation. 277 if (resource.type === WebInspector.Resource.Type.XHR) { 278 var resourceTreeElement = this._findTreeElementForResource(resource); 279 if (resourceTreeElement) 280 resourceTreeElement.parent.removeChild(resourceTreeElement); 281 } 282 }, 283 284 addDatabase: function(database) 285 { 286 this._databases.push(database); 287 288 var databaseTreeElement = new WebInspector.DatabaseTreeElement(this, database); 289 database._databasesTreeElement = databaseTreeElement; 290 this.databasesListTreeElement.appendChild(databaseTreeElement); 291 }, 292 293 addDocumentURL: function(url) 294 { 295 var parsedURL = url.asParsedURL(); 296 if (!parsedURL) 297 return; 298 299 var domain = parsedURL.host; 300 if (!this._domains[domain]) { 301 this._domains[domain] = true; 302 303 var cookieDomainTreeElement = new WebInspector.CookieTreeElement(this, domain); 304 this.cookieListTreeElement.appendChild(cookieDomainTreeElement); 305 306 var applicationCacheTreeElement = new WebInspector.ApplicationCacheTreeElement(this, domain); 307 this.applicationCacheListTreeElement.appendChild(applicationCacheTreeElement); 308 } 309 }, 310 311 addDOMStorage: function(domStorage) 312 { 313 this._domStorage.push(domStorage); 314 var domStorageTreeElement = new WebInspector.DOMStorageTreeElement(this, domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage")); 315 domStorage._domStorageTreeElement = domStorageTreeElement; 316 if (domStorage.isLocalStorage) 317 this.localStorageListTreeElement.appendChild(domStorageTreeElement); 318 else 319 this.sessionStorageListTreeElement.appendChild(domStorageTreeElement); 320 }, 321 322 selectDatabase: function(databaseId) 323 { 324 var database; 325 for (var i = 0, len = this._databases.length; i < len; ++i) { 326 database = this._databases[i]; 327 if (database.id === databaseId) { 328 this.showDatabase(database); 329 database._databasesTreeElement.select(); 330 return; 331 } 332 } 333 }, 334 335 selectDOMStorage: function(storageId) 336 { 337 var domStorage = this._domStorageForId(storageId); 338 if (domStorage) { 339 this.showDOMStorage(domStorage); 340 domStorage._domStorageTreeElement.select(); 341 } 342 }, 343 344 canShowAnchorLocation: function(anchor) 345 { 346 return !!WebInspector.resourceForURL(anchor.href); 347 }, 348 349 showAnchorLocation: function(anchor) 350 { 351 var resource = WebInspector.resourceForURL(anchor.href); 352 if (resource.type === WebInspector.Resource.Type.XHR) { 353 // Show XHRs in the network panel only. 354 if (WebInspector.panels.network && WebInspector.panels.network.canShowAnchorLocation(anchor)) { 355 WebInspector.currentPanel = WebInspector.panels.network; 356 WebInspector.panels.network.showAnchorLocation(anchor); 357 } 358 return; 359 } 360 this.showResource(resource, anchor.getAttribute("line_number") - 1); 361 }, 362 363 showResource: function(resource, line) 364 { 365 var resourceTreeElement = this._findTreeElementForResource(resource); 366 if (resourceTreeElement) { 367 resourceTreeElement.reveal(); 368 resourceTreeElement.select(); 369 } 370 371 if (line !== undefined) { 372 var view = WebInspector.ResourceView.resourceViewForResource(resource); 373 if (view.highlightLine) 374 view.highlightLine(line); 375 } 376 return true; 377 }, 378 379 _showResourceView: function(resource) 380 { 381 var view = WebInspector.ResourceView.resourceViewForResource(resource); 382 this._fetchAndApplyDiffMarkup(view, resource); 383 this._innerShowView(view); 384 }, 385 386 _showRevisionView: function(revision) 387 { 388 if (!revision._view) 389 revision._view = new WebInspector.RevisionSourceFrame(revision); 390 var view = revision._view; 391 this._fetchAndApplyDiffMarkup(view, revision.resource, revision); 392 this._innerShowView(view); 393 }, 394 395 _fetchAndApplyDiffMarkup: function(view, resource, revision) 396 { 397 var baseRevision = resource.history[0]; 398 if (!baseRevision) 399 return; 400 if (!(view instanceof WebInspector.SourceFrame)) 401 return; 402 403 baseRevision.requestContent(step1.bind(this)); 404 405 function step1(baseContent) 406 { 407 (revision ? revision : resource).requestContent(step2.bind(this, baseContent)); 408 } 409 410 function step2(baseContent, revisionContent) 411 { 412 this._applyDiffMarkup(view, baseContent, revisionContent); 413 } 414 }, 415 416 _applyDiffMarkup: function(view, baseContent, newContent) { 417 var oldLines = baseContent.split(/\r?\n/); 418 var newLines = newContent.split(/\r?\n/); 419 420 var diff = Array.diff(oldLines, newLines); 421 422 var diffData = {}; 423 diffData.added = []; 424 diffData.removed = []; 425 diffData.changed = []; 426 427 var offset = 0; 428 var right = diff.right; 429 for (var i = 0; i < right.length; ++i) { 430 if (typeof right[i] === "string") { 431 if (right.length > i + 1 && right[i + 1].row === i + 1 - offset) 432 diffData.changed.push(i); 433 else { 434 diffData.added.push(i); 435 offset++; 436 } 437 } else 438 offset = i - right[i].row; 439 } 440 view.markDiff(diffData); 441 }, 442 443 showDatabase: function(database, tableName) 444 { 445 if (!database) 446 return; 447 448 var view; 449 if (tableName) { 450 if (!("_tableViews" in database)) 451 database._tableViews = {}; 452 view = database._tableViews[tableName]; 453 if (!view) { 454 view = new WebInspector.DatabaseTableView(database, tableName); 455 database._tableViews[tableName] = view; 456 } 457 } else { 458 view = database._queryView; 459 if (!view) { 460 view = new WebInspector.DatabaseQueryView(database); 461 database._queryView = view; 462 } 463 } 464 465 this._innerShowView(view); 466 }, 467 468 showDOMStorage: function(domStorage) 469 { 470 if (!domStorage) 471 return; 472 473 var view; 474 view = domStorage._domStorageView; 475 if (!view) { 476 view = new WebInspector.DOMStorageItemsView(domStorage); 477 domStorage._domStorageView = view; 478 } 479 480 this._innerShowView(view); 481 }, 482 483 showCookies: function(treeElement, cookieDomain) 484 { 485 var view = this._cookieViews[cookieDomain]; 486 if (!view) { 487 view = new WebInspector.CookieItemsView(treeElement, cookieDomain); 488 this._cookieViews[cookieDomain] = view; 489 } 490 491 this._innerShowView(view); 492 }, 493 494 showApplicationCache: function(treeElement, appcacheDomain) 495 { 496 var view = this._applicationCacheView; 497 if (!view) { 498 view = new WebInspector.ApplicationCacheItemsView(treeElement, appcacheDomain); 499 this._applicationCacheView = view; 500 } 501 502 this._innerShowView(view); 503 504 if ("_cachedApplicationCacheViewStatus" in this) 505 this._applicationCacheView.updateStatus(this._cachedApplicationCacheViewStatus); 506 }, 507 508 showCategoryView: function(categoryName) 509 { 510 if (!this._categoryView) 511 this._categoryView = new WebInspector.StorageCategoryView(); 512 this._categoryView.setText(categoryName); 513 this._innerShowView(this._categoryView); 514 }, 515 516 _innerShowView: function(view) 517 { 518 if (this.visibleView) 519 this.visibleView.hide(); 520 521 view.show(this.storageViews); 522 this.visibleView = view; 523 524 this.storageViewStatusBarItemsContainer.removeChildren(); 525 var statusBarItems = view.statusBarItems || []; 526 for (var i = 0; i < statusBarItems.length; ++i) 527 this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]); 528 }, 529 530 closeVisibleView: function() 531 { 532 if (this.visibleView) 533 this.visibleView.hide(); 534 delete this.visibleView; 535 }, 536 537 updateDatabaseTables: function(database) 538 { 539 if (!database || !database._databasesTreeElement) 540 return; 541 542 database._databasesTreeElement.shouldRefreshChildren = true; 543 544 if (!("_tableViews" in database)) 545 return; 546 547 var tableNamesHash = {}; 548 var self = this; 549 function tableNamesCallback(tableNames) 550 { 551 var tableNamesLength = tableNames.length; 552 for (var i = 0; i < tableNamesLength; ++i) 553 tableNamesHash[tableNames[i]] = true; 554 555 for (var tableName in database._tableViews) { 556 if (!(tableName in tableNamesHash)) { 557 if (self.visibleView === database._tableViews[tableName]) 558 self.closeVisibleView(); 559 delete database._tableViews[tableName]; 560 } 561 } 562 } 563 database.getTableNames(tableNamesCallback); 564 }, 565 566 dataGridForResult: function(columnNames, values) 567 { 568 var numColumns = columnNames.length; 569 if (!numColumns) 570 return null; 571 572 var columns = {}; 573 574 for (var i = 0; i < columnNames.length; ++i) { 575 var column = {}; 576 column.width = columnNames[i].length; 577 column.title = columnNames[i]; 578 column.sortable = true; 579 580 columns[columnNames[i]] = column; 581 } 582 583 var nodes = []; 584 for (var i = 0; i < values.length / numColumns; ++i) { 585 var data = {}; 586 for (var j = 0; j < columnNames.length; ++j) 587 data[columnNames[j]] = values[numColumns * i + j]; 588 589 var node = new WebInspector.DataGridNode(data, false); 590 node.selectable = false; 591 nodes.push(node); 592 } 593 594 var dataGrid = new WebInspector.DataGrid(columns); 595 var length = nodes.length; 596 for (var i = 0; i < length; ++i) 597 dataGrid.appendChild(nodes[i]); 598 599 dataGrid.addEventListener("sorting changed", this._sortDataGrid.bind(this, dataGrid), this); 600 return dataGrid; 601 }, 602 603 _sortDataGrid: function(dataGrid) 604 { 605 var nodes = dataGrid.children.slice(); 606 var sortColumnIdentifier = dataGrid.sortColumnIdentifier; 607 var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1; 608 var columnIsNumeric = true; 609 610 for (var i = 0; i < nodes.length; i++) { 611 if (isNaN(Number(nodes[i].data[sortColumnIdentifier]))) 612 columnIsNumeric = false; 613 } 614 615 function comparator(dataGridNode1, dataGridNode2) 616 { 617 var item1 = dataGridNode1.data[sortColumnIdentifier]; 618 var item2 = dataGridNode2.data[sortColumnIdentifier]; 619 620 var comparison; 621 if (columnIsNumeric) { 622 // Sort numbers based on comparing their values rather than a lexicographical comparison. 623 var number1 = parseFloat(item1); 624 var number2 = parseFloat(item2); 625 comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0); 626 } else 627 comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0); 628 629 return sortDirection * comparison; 630 } 631 632 nodes.sort(comparator); 633 dataGrid.removeChildren(); 634 for (var i = 0; i < nodes.length; i++) 635 dataGrid.appendChild(nodes[i]); 636 }, 637 638 updateDOMStorage: function(storageId) 639 { 640 var domStorage = this._domStorageForId(storageId); 641 if (!domStorage) 642 return; 643 644 var view = domStorage._domStorageView; 645 if (this.visibleView && view === this.visibleView) 646 domStorage._domStorageView.update(); 647 }, 648 649 updateApplicationCacheStatus: function(status) 650 { 651 this._cachedApplicationCacheViewStatus = status; 652 if (this._applicationCacheView && this._applicationCacheView === this.visibleView) 653 this._applicationCacheView.updateStatus(status); 654 }, 655 656 updateNetworkState: function(isNowOnline) 657 { 658 if (this._applicationCacheView && this._applicationCacheView === this.visibleView) 659 this._applicationCacheView.updateNetworkState(isNowOnline); 660 }, 661 662 updateManifest: function(manifest) 663 { 664 if (this._applicationCacheView && this._applicationCacheView === this.visibleView) 665 this._applicationCacheView.updateManifest(manifest); 666 }, 667 668 _domStorageForId: function(storageId) 669 { 670 if (!this._domStorage) 671 return null; 672 var domStorageLength = this._domStorage.length; 673 for (var i = 0; i < domStorageLength; ++i) { 674 var domStorage = this._domStorage[i]; 675 if (domStorage.id == storageId) 676 return domStorage; 677 } 678 return null; 679 }, 680 681 updateMainViewWidth: function(width) 682 { 683 this.storageViews.style.left = width + "px"; 684 this.storageViewStatusBarItemsContainer.style.left = width + "px"; 685 this.resize(); 686 }, 687 688 get searchableViews() 689 { 690 var views = []; 691 692 const visibleView = this.visibleView; 693 if (visibleView && visibleView.performSearch) 694 views.push(visibleView); 695 696 function callback(resourceTreeElement) 697 { 698 var resource = resourceTreeElement._resource; 699 var resourceView = WebInspector.ResourceView.resourceViewForResource(resource); 700 if (resourceView.performSearch && resourceView !== visibleView) 701 views.push(resourceView); 702 } 703 this._forAllResourceTreeElements(callback); 704 return views; 705 }, 706 707 _forAllResourceTreeElements: function(callback) 708 { 709 var stop = false; 710 for (var treeElement = this.resourcesListTreeElement; !stop && treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.resourcesListTreeElement, true)) { 711 if (treeElement instanceof WebInspector.FrameResourceTreeElement) 712 stop = callback(treeElement); 713 } 714 }, 715 716 searchMatchFound: function(view, matches) 717 { 718 if (!view.resource) 719 return; 720 var treeElement = this._findTreeElementForResource(view.resource); 721 if (treeElement) 722 treeElement.searchMatchFound(matches); 723 }, 724 725 _findTreeElementForResource: function(resource) 726 { 727 function isAncestor(ancestor, object) 728 { 729 // Redirects, XHRs do not belong to the tree, it is fine to silently return false here. 730 return false; 731 } 732 733 function getParent(object) 734 { 735 // Redirects, XHRs do not belong to the tree, it is fine to silently return false here. 736 return null; 737 } 738 739 return this.sidebarTree.findTreeElement(resource, isAncestor, getParent); 740 }, 741 742 searchCanceled: function(startingNewSearch) 743 { 744 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); 745 746 if (startingNewSearch) 747 return; 748 749 function callback(resourceTreeElement) 750 { 751 resourceTreeElement._errorsWarningsUpdated(); 752 } 753 this._forAllResourceTreeElements(callback); 754 }, 755 756 performSearch: function(query) 757 { 758 function callback(resourceTreeElement) 759 { 760 resourceTreeElement._resetBubble(); 761 } 762 this._forAllResourceTreeElements(callback); 763 WebInspector.Panel.prototype.performSearch.call(this, query); 764 }, 765 766 showView: function(view) 767 { 768 if (view) 769 this.showResource(view.resource); 770 }, 771 772 _onmousemove: function(event) 773 { 774 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); 775 if (!nodeUnderMouse) 776 return; 777 778 var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("li"); 779 if (!listNode) 780 return; 781 782 var element = listNode.treeElement; 783 if (this._previousHoveredElement === element) 784 return; 785 786 if (this._previousHoveredElement) { 787 this._previousHoveredElement.hovered = false; 788 delete this._previousHoveredElement; 789 } 790 791 if (element instanceof WebInspector.FrameTreeElement) { 792 this._previousHoveredElement = element; 793 element.hovered = true; 794 } 795 }, 796 797 _onmouseout: function(event) 798 { 799 if (this._previousHoveredElement) { 800 this._previousHoveredElement.hovered = false; 801 delete this._previousHoveredElement; 802 } 803 } 804 } 805 806 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype; 807 808 WebInspector.BaseStorageTreeElement = function(storagePanel, representedObject, title, iconClasses, hasChildren, noIcon) 809 { 810 TreeElement.call(this, "", representedObject, hasChildren); 811 this._storagePanel = storagePanel; 812 this._titleText = title; 813 this._iconClasses = iconClasses; 814 this._noIcon = noIcon; 815 } 816 817 WebInspector.BaseStorageTreeElement.prototype = { 818 onattach: function() 819 { 820 this.listItemElement.removeChildren(); 821 if (this._iconClasses) { 822 for (var i = 0; i < this._iconClasses.length; ++i) 823 this.listItemElement.addStyleClass(this._iconClasses[i]); 824 } 825 826 var selectionElement = document.createElement("div"); 827 selectionElement.className = "selection"; 828 this.listItemElement.appendChild(selectionElement); 829 830 if (!this._noIcon) { 831 this.imageElement = document.createElement("img"); 832 this.imageElement.className = "icon"; 833 this.listItemElement.appendChild(this.imageElement); 834 } 835 836 this.titleElement = document.createElement("div"); 837 this.titleElement.className = "base-storage-tree-element-title"; 838 this.titleElement.textContent = this._titleText; 839 this.listItemElement.appendChild(this.titleElement); 840 }, 841 842 onselect: function() 843 { 844 var itemURL = this.itemURL; 845 if (itemURL) 846 WebInspector.settings.resourcesLastSelectedItem = itemURL; 847 }, 848 849 onreveal: function() 850 { 851 if (this.listItemElement) 852 this.listItemElement.scrollIntoViewIfNeeded(false); 853 }, 854 855 get titleText() 856 { 857 return this._titleText; 858 }, 859 860 set titleText(titleText) 861 { 862 this._titleText = titleText; 863 if (this.titleElement) 864 this.titleElement.textContent = this._titleText; 865 }, 866 867 isEventWithinDisclosureTriangle: function() 868 { 869 // Override it since we use margin-left in place of treeoutline's text-indent. 870 // Hence we need to take padding into consideration. This all is needed for leading 871 // icons in the tree. 872 const paddingLeft = 14; 873 var left = this.listItemElement.totalOffsetLeft + paddingLeft; 874 return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; 875 } 876 } 877 878 WebInspector.BaseStorageTreeElement.prototype.__proto__ = TreeElement.prototype; 879 880 WebInspector.StorageCategoryTreeElement = function(storagePanel, categoryName, settingsKey, iconClasses, noIcon) 881 { 882 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, categoryName, iconClasses, true, noIcon); 883 this._expandedSettingKey = "resources" + settingsKey + "Expanded"; 884 WebInspector.settings.installApplicationSetting(this._expandedSettingKey, settingsKey === "Frames"); 885 this._categoryName = categoryName; 886 } 887 888 WebInspector.StorageCategoryTreeElement.prototype = { 889 get itemURL() 890 { 891 return "category://" + this._categoryName; 892 }, 893 894 onselect: function() 895 { 896 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 897 this._storagePanel.showCategoryView(this._categoryName); 898 }, 899 900 onattach: function() 901 { 902 WebInspector.BaseStorageTreeElement.prototype.onattach.call(this); 903 if (WebInspector.settings[this._expandedSettingKey]) 904 this.expand(); 905 }, 906 907 onexpand: function() 908 { 909 WebInspector.settings[this._expandedSettingKey] = true; 910 }, 911 912 oncollapse: function() 913 { 914 WebInspector.settings[this._expandedSettingKey] = false; 915 } 916 } 917 WebInspector.StorageCategoryTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 918 919 WebInspector.FrameTreeElement = function(storagePanel, frameId, title, subtitle) 920 { 921 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]); 922 this._frameId = frameId; 923 this.setTitles(title, subtitle); 924 this._categoryElements = {}; 925 } 926 927 WebInspector.FrameTreeElement.prototype = { 928 get itemURL() 929 { 930 return "frame://" + encodeURI(this._displayName); 931 }, 932 933 onattach: function() 934 { 935 WebInspector.BaseStorageTreeElement.prototype.onattach.call(this); 936 if (this._titleToSetOnAttach || this._subtitleToSetOnAttach) { 937 this.setTitles(this._titleToSetOnAttach, this._subtitleToSetOnAttach); 938 delete this._titleToSetOnAttach; 939 delete this._subtitleToSetOnAttach; 940 } 941 }, 942 943 onselect: function() 944 { 945 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 946 this._storagePanel.showCategoryView(this._displayName); 947 948 this.listItemElement.removeStyleClass("hovered"); 949 DOMAgent.hideFrameHighlight(); 950 }, 951 952 get displayName() 953 { 954 return this._displayName; 955 }, 956 957 setTitles: function(title, subtitle) 958 { 959 this._displayName = title || ""; 960 if (subtitle) 961 this._displayName += " (" + subtitle + ")"; 962 963 if (this.parent) { 964 this.titleElement.textContent = title || ""; 965 if (subtitle) { 966 var subtitleElement = document.createElement("span"); 967 subtitleElement.className = "base-storage-tree-element-subtitle"; 968 subtitleElement.textContent = "(" + subtitle + ")"; 969 this.titleElement.appendChild(subtitleElement); 970 } 971 } else { 972 this._titleToSetOnAttach = title; 973 this._subtitleToSetOnAttach = subtitle; 974 } 975 }, 976 977 set hovered(hovered) 978 { 979 if (hovered) { 980 this.listItemElement.addStyleClass("hovered"); 981 DOMAgent.highlightFrame(this._frameId); 982 } else { 983 this.listItemElement.removeStyleClass("hovered"); 984 DOMAgent.hideFrameHighlight(); 985 } 986 }, 987 988 appendResource: function(resource) 989 { 990 var categoryName = resource.category.name; 991 var categoryElement = resource.category === WebInspector.resourceCategories.documents ? this : this._categoryElements[categoryName]; 992 if (!categoryElement) { 993 categoryElement = new WebInspector.StorageCategoryTreeElement(this._storagePanel, resource.category.title, categoryName, null, true); 994 this._categoryElements[resource.category.name] = categoryElement; 995 this._insertInPresentationOrder(this, categoryElement); 996 } 997 var resourceTreeElement = new WebInspector.FrameResourceTreeElement(this._storagePanel, resource); 998 this._insertInPresentationOrder(categoryElement, resourceTreeElement); 999 resourceTreeElement._populateRevisions(); 1000 }, 1001 1002 appendChild: function(treeElement) 1003 { 1004 this._insertInPresentationOrder(this, treeElement); 1005 }, 1006 1007 _insertInPresentationOrder: function(parentTreeElement, childTreeElement) 1008 { 1009 // Insert in the alphabetical order, first frames, then resources. Document resource goes last. 1010 function typeWeight(treeElement) 1011 { 1012 if (treeElement instanceof WebInspector.StorageCategoryTreeElement) 1013 return 2; 1014 if (treeElement instanceof WebInspector.FrameTreeElement) 1015 return 1; 1016 return 3; 1017 } 1018 1019 function compare(treeElement1, treeElement2) 1020 { 1021 var typeWeight1 = typeWeight(treeElement1); 1022 var typeWeight2 = typeWeight(treeElement2); 1023 1024 var result; 1025 if (typeWeight1 > typeWeight2) 1026 result = 1; 1027 else if (typeWeight1 < typeWeight2) 1028 result = -1; 1029 else { 1030 var title1 = treeElement1.displayName || treeElement1.titleText; 1031 var title2 = treeElement2.displayName || treeElement2.titleText; 1032 result = title1.localeCompare(title2); 1033 } 1034 return result; 1035 } 1036 1037 var children = parentTreeElement.children; 1038 var i; 1039 for (i = 0; i < children.length; ++i) { 1040 if (compare(childTreeElement, children[i]) < 0) 1041 break; 1042 } 1043 parentTreeElement.insertChild(childTreeElement, i); 1044 } 1045 } 1046 1047 WebInspector.FrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1048 1049 WebInspector.FrameResourceTreeElement = function(storagePanel, resource) 1050 { 1051 WebInspector.BaseStorageTreeElement.call(this, storagePanel, resource, resource.displayName, ["resource-sidebar-tree-item", "resources-category-" + resource.category.name]); 1052 this._resource = resource; 1053 this._resource.addEventListener("errors-warnings-updated", this._errorsWarningsUpdated, this); 1054 this._resource.addEventListener(WebInspector.Resource.Events.RevisionAdded, this._revisionAdded, this); 1055 this.tooltip = resource.url; 1056 } 1057 1058 WebInspector.FrameResourceTreeElement.prototype = { 1059 get itemURL() 1060 { 1061 return this._resource.url; 1062 }, 1063 1064 onselect: function() 1065 { 1066 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 1067 this._storagePanel._showResourceView(this._resource); 1068 }, 1069 1070 ondblclick: function(event) 1071 { 1072 PageAgent.openInInspectedWindow(this._resource.url); 1073 }, 1074 1075 onattach: function() 1076 { 1077 WebInspector.BaseStorageTreeElement.prototype.onattach.call(this); 1078 1079 if (this._resource.category === WebInspector.resourceCategories.images) { 1080 var previewImage = document.createElement("img"); 1081 previewImage.className = "image-resource-icon-preview"; 1082 this._resource.populateImageSource(previewImage); 1083 1084 var iconElement = document.createElement("div"); 1085 iconElement.className = "icon"; 1086 iconElement.appendChild(previewImage); 1087 this.listItemElement.replaceChild(iconElement, this.imageElement); 1088 } 1089 1090 this._statusElement = document.createElement("div"); 1091 this._statusElement.className = "status"; 1092 this.listItemElement.insertBefore(this._statusElement, this.titleElement); 1093 1094 this.listItemElement.draggable = true; 1095 this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); 1096 }, 1097 1098 _ondragstart: function(event) 1099 { 1100 event.dataTransfer.setData("text/plain", this._resource.content); 1101 event.dataTransfer.effectAllowed = "copy"; 1102 return true; 1103 }, 1104 1105 _setBubbleText: function(x) 1106 { 1107 if (!this._bubbleElement) { 1108 this._bubbleElement = document.createElement("div"); 1109 this._bubbleElement.className = "bubble"; 1110 this._statusElement.appendChild(this._bubbleElement); 1111 } 1112 1113 this._bubbleElement.textContent = x; 1114 }, 1115 1116 _resetBubble: function() 1117 { 1118 if (this._bubbleElement) { 1119 this._bubbleElement.textContent = ""; 1120 this._bubbleElement.removeStyleClass("search-matches"); 1121 this._bubbleElement.removeStyleClass("warning"); 1122 this._bubbleElement.removeStyleClass("error"); 1123 } 1124 }, 1125 1126 searchMatchFound: function(matches) 1127 { 1128 this._resetBubble(); 1129 1130 this._setBubbleText(matches); 1131 this._bubbleElement.addStyleClass("search-matches"); 1132 1133 // Expand, do not scroll into view. 1134 var currentAncestor = this.parent; 1135 while (currentAncestor && !currentAncestor.root) { 1136 if (!currentAncestor.expanded) 1137 currentAncestor.expand(); 1138 currentAncestor = currentAncestor.parent; 1139 } 1140 }, 1141 1142 _errorsWarningsUpdated: function() 1143 { 1144 // FIXME: move to the SourceFrame. 1145 if (!this._resource.warnings && !this._resource.errors) { 1146 var view = WebInspector.ResourceView.existingResourceViewForResource(this._resource); 1147 if (view && view.clearMessages) 1148 view.clearMessages(); 1149 } 1150 1151 if (this._storagePanel.currentQuery) 1152 return; 1153 1154 this._resetBubble(); 1155 1156 if (this._resource.warnings || this._resource.errors) 1157 this._setBubbleText(this._resource.warnings + this._resource.errors); 1158 1159 if (this._resource.warnings) 1160 this._bubbleElement.addStyleClass("warning"); 1161 1162 if (this._resource.errors) 1163 this._bubbleElement.addStyleClass("error"); 1164 }, 1165 1166 _populateRevisions: function() 1167 { 1168 for (var i = 0; i < this._resource.history.length; ++i) 1169 this._appendRevision(this._resource.history[i]); 1170 }, 1171 1172 _revisionAdded: function(event) 1173 { 1174 this._appendRevision(event.data); 1175 }, 1176 1177 _appendRevision: function(revision) 1178 { 1179 this.insertChild(new WebInspector.ResourceRevisionTreeElement(this._storagePanel, revision), 0); 1180 var oldView = WebInspector.ResourceView.existingResourceViewForResource(this._resource); 1181 if (oldView) { 1182 var newView = WebInspector.ResourceView.recreateResourceView(this._resource); 1183 if (oldView === this._storagePanel.visibleView) 1184 this._storagePanel._showResourceView(this._resource); 1185 } 1186 } 1187 } 1188 1189 WebInspector.FrameResourceTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1190 1191 WebInspector.DatabaseTreeElement = function(storagePanel, database) 1192 { 1193 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, database.name, ["database-storage-tree-item"], true); 1194 this._database = database; 1195 } 1196 1197 WebInspector.DatabaseTreeElement.prototype = { 1198 get itemURL() 1199 { 1200 return "database://" + encodeURI(this._database.name); 1201 }, 1202 1203 onselect: function() 1204 { 1205 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 1206 this._storagePanel.showDatabase(this._database); 1207 }, 1208 1209 oncollapse: function() 1210 { 1211 // Request a refresh after every collapse so the next 1212 // expand will have an updated table list. 1213 this.shouldRefreshChildren = true; 1214 }, 1215 1216 onpopulate: function() 1217 { 1218 this.removeChildren(); 1219 1220 function tableNamesCallback(tableNames) 1221 { 1222 var tableNamesLength = tableNames.length; 1223 for (var i = 0; i < tableNamesLength; ++i) 1224 this.appendChild(new WebInspector.DatabaseTableTreeElement(this._storagePanel, this._database, tableNames[i])); 1225 } 1226 this._database.getTableNames(tableNamesCallback.bind(this)); 1227 } 1228 1229 } 1230 WebInspector.DatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1231 1232 WebInspector.DatabaseTableTreeElement = function(storagePanel, database, tableName) 1233 { 1234 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, tableName, ["database-storage-tree-item"]); 1235 this._database = database; 1236 this._tableName = tableName; 1237 } 1238 1239 WebInspector.DatabaseTableTreeElement.prototype = { 1240 get itemURL() 1241 { 1242 return "database://" + encodeURI(this._database.name) + "/" + encodeURI(this._tableName); 1243 }, 1244 1245 onselect: function() 1246 { 1247 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 1248 this._storagePanel.showDatabase(this._database, this._tableName); 1249 } 1250 } 1251 WebInspector.DatabaseTableTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1252 1253 WebInspector.DOMStorageTreeElement = function(storagePanel, domStorage, className) 1254 { 1255 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, domStorage.domain ? domStorage.domain : WebInspector.UIString("Local Files"), ["domstorage-storage-tree-item", className]); 1256 this._domStorage = domStorage; 1257 } 1258 1259 WebInspector.DOMStorageTreeElement.prototype = { 1260 get itemURL() 1261 { 1262 return "storage://" + this._domStorage.domain + "/" + (this._domStorage.isLocalStorage ? "local" : "session"); 1263 }, 1264 1265 onselect: function() 1266 { 1267 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 1268 this._storagePanel.showDOMStorage(this._domStorage); 1269 } 1270 } 1271 WebInspector.DOMStorageTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1272 1273 WebInspector.CookieTreeElement = function(storagePanel, cookieDomain) 1274 { 1275 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, cookieDomain ? cookieDomain : WebInspector.UIString("Local Files"), ["cookie-storage-tree-item"]); 1276 this._cookieDomain = cookieDomain; 1277 } 1278 1279 WebInspector.CookieTreeElement.prototype = { 1280 get itemURL() 1281 { 1282 return "cookies://" + this._cookieDomain; 1283 }, 1284 1285 onselect: function() 1286 { 1287 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 1288 this._storagePanel.showCookies(this, this._cookieDomain); 1289 } 1290 } 1291 WebInspector.CookieTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1292 1293 WebInspector.ApplicationCacheTreeElement = function(storagePanel, appcacheDomain) 1294 { 1295 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, appcacheDomain ? appcacheDomain : WebInspector.UIString("Local Files"), ["application-cache-storage-tree-item"]); 1296 this._appcacheDomain = appcacheDomain; 1297 } 1298 1299 WebInspector.ApplicationCacheTreeElement.prototype = { 1300 get itemURL() 1301 { 1302 return "appcache://" + this._appcacheDomain; 1303 }, 1304 1305 onselect: function() 1306 { 1307 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 1308 this._storagePanel.showApplicationCache(this, this._appcacheDomain); 1309 } 1310 } 1311 WebInspector.ApplicationCacheTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1312 1313 WebInspector.ResourceRevisionTreeElement = function(storagePanel, revision) 1314 { 1315 var title = revision.timestamp ? revision.timestamp.toLocaleTimeString() : WebInspector.UIString("(original)"); 1316 WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, title, ["resource-sidebar-tree-item", "resources-category-" + revision.resource.category.name]); 1317 if (revision.timestamp) 1318 this.tooltip = revision.timestamp.toLocaleString(); 1319 this._revision = revision; 1320 } 1321 1322 WebInspector.ResourceRevisionTreeElement.prototype = { 1323 get itemURL() 1324 { 1325 return this._revision.resource.url; 1326 }, 1327 1328 onattach: function() 1329 { 1330 WebInspector.BaseStorageTreeElement.prototype.onattach.call(this); 1331 this.listItemElement.draggable = true; 1332 this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); 1333 this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 1334 }, 1335 1336 onselect: function() 1337 { 1338 WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); 1339 this._storagePanel._showRevisionView(this._revision); 1340 }, 1341 1342 _ondragstart: function(event) 1343 { 1344 if (this._revision.content) { 1345 event.dataTransfer.setData("text/plain", this._revision.content); 1346 event.dataTransfer.effectAllowed = "copy"; 1347 return true; 1348 } 1349 }, 1350 1351 _handleContextMenuEvent: function(event) 1352 { 1353 var contextMenu = new WebInspector.ContextMenu(); 1354 contextMenu.appendItem(WebInspector.UIString("Revert to this revision"), this._revision.revertToThis.bind(this._revision)); 1355 contextMenu.show(event); 1356 } 1357 } 1358 1359 WebInspector.ResourceRevisionTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; 1360 1361 WebInspector.StorageCategoryView = function() 1362 { 1363 WebInspector.View.call(this); 1364 1365 this.element.addStyleClass("storage-view"); 1366 1367 this._emptyMsgElement = document.createElement("div"); 1368 this._emptyMsgElement.className = "storage-empty-view"; 1369 this.element.appendChild(this._emptyMsgElement); 1370 } 1371 1372 WebInspector.StorageCategoryView.prototype = { 1373 setText: function(text) 1374 { 1375 this._emptyMsgElement.textContent = text; 1376 } 1377 } 1378 1379 WebInspector.StorageCategoryView.prototype.__proto__ = WebInspector.View.prototype; 1380