Web Inspector: Network - Toggle Between Live Activity and Imported HAR resource colle...
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Mar 2019 18:44:38 +0000 (18:44 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Mar 2019 18:44:38 +0000 (18:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195734

Reviewed by Devin Rousso.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Images/NetworkHAR.svg: Added.
* UserInterface/Images/NetworkOverview.svg: Added.
* UserInterface/Views/NetworkTableContentView.css:
(.content-view.tab.network > .content-browser > .navigation-bar .hierarchical-path .icon):
(.network-overview-icon > .icon):
(.network-har-icon > .icon):
(@media (prefers-color-scheme: dark)):

* UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView):
(WI.NetworkTableContentView.prototype.get navigationItems):
(WI.NetworkTableContentView.prototype.reset):
(WI.NetworkTableContentView.prototype.tableIndexForRepresentedObject):
(WI.NetworkTableContentView.prototype.tableRepresentedObjectForIndex):
(WI.NetworkTableContentView.prototype.tableNumberOfRows):
(WI.NetworkTableContentView.prototype.tableCellContextMenuClicked):
(WI.NetworkTableContentView.prototype.tableSelectionDidChange):
(WI.NetworkTableContentView.prototype.tablePopulateCell):
(WI.NetworkTableContentView.prototype._addCollection):
(WI.NetworkTableContentView.prototype._setActiveCollection):
(WI.NetworkTableContentView.prototype._addCollectionPathComponent):
(WI.NetworkTableContentView.prototype._collectionsHierarchicalPathComponentWasSelected):
(WI.NetworkTableContentView.prototype._changeCollection):
(WI.NetworkTableContentView.prototype.handleClearShortcut):
(WI.NetworkTableContentView.prototype._updateWaterfallTimeRange):
(WI.NetworkTableContentView.prototype._updateWaterfallTimelineRuler):
(WI.NetworkTableContentView.prototype._canExportHAR):
(WI.NetworkTableContentView.prototype._processPendingEntries):
(WI.NetworkTableContentView.prototype._populateWithInitialResourcesIfNeeded):
(WI.NetworkTableContentView.prototype._rowIndexForRepresentedObject):
(WI.NetworkTableContentView.prototype._updateEntryForResource):
(WI.NetworkTableContentView.prototype._updateEmptyFilterResultsMessage):
(WI.NetworkTableContentView.prototype._mainResourceDidChange):
(WI.NetworkTableContentView.prototype._mainFrameDidChange):
(WI.NetworkTableContentView.prototype._resourceLoadingDidFinish):
(WI.NetworkTableContentView.prototype._resourceLoadingDidFail):
(WI.NetworkTableContentView.prototype._resourceTransferSizeDidChange):
(WI.NetworkTableContentView.prototype._handleResourceAdded):
(WI.NetworkTableContentView.prototype._runForMainCollection):
(WI.NetworkTableContentView.prototype._isShowingMainCollection):
(WI.NetworkTableContentView.prototype._insertResourceAndReloadTable):
(WI.NetworkTableContentView.prototype._handleNodeDidFireEvent):
(WI.NetworkTableContentView.prototype._handleNodeLowPowerChanged):
(WI.NetworkTableContentView.prototype._updateSort):
(WI.NetworkTableContentView.prototype._updateFilteredEntries):
(WI.NetworkTableContentView.prototype._urlFilterDidChange):
(WI.NetworkTableContentView.prototype._HARResources):
(WI.NetworkTableContentView.prototype._importHAR):
Introduce the concept of collections that can be swapped in/out
to re-render the Network Table with different contents.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@243004 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Images/NetworkHAR.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Images/NetworkOverview.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js

index 13ae870..c4b7b07 100644 (file)
@@ -1,3 +1,62 @@
+2019-03-15  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Network - Toggle Between Live Activity and Imported HAR resource collections
+        https://bugs.webkit.org/show_bug.cgi?id=195734
+
+        Reviewed by Devin Rousso.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Images/NetworkHAR.svg: Added.
+        * UserInterface/Images/NetworkOverview.svg: Added.
+        * UserInterface/Views/NetworkTableContentView.css:
+        (.content-view.tab.network > .content-browser > .navigation-bar .hierarchical-path .icon):
+        (.network-overview-icon > .icon):
+        (.network-har-icon > .icon):
+        (@media (prefers-color-scheme: dark)):
+
+        * UserInterface/Views/NetworkTableContentView.js:
+        (WI.NetworkTableContentView):
+        (WI.NetworkTableContentView.prototype.get navigationItems):
+        (WI.NetworkTableContentView.prototype.reset):
+        (WI.NetworkTableContentView.prototype.tableIndexForRepresentedObject):
+        (WI.NetworkTableContentView.prototype.tableRepresentedObjectForIndex):
+        (WI.NetworkTableContentView.prototype.tableNumberOfRows):
+        (WI.NetworkTableContentView.prototype.tableCellContextMenuClicked):
+        (WI.NetworkTableContentView.prototype.tableSelectionDidChange):
+        (WI.NetworkTableContentView.prototype.tablePopulateCell):
+        (WI.NetworkTableContentView.prototype._addCollection):
+        (WI.NetworkTableContentView.prototype._setActiveCollection):
+        (WI.NetworkTableContentView.prototype._addCollectionPathComponent):
+        (WI.NetworkTableContentView.prototype._collectionsHierarchicalPathComponentWasSelected):
+        (WI.NetworkTableContentView.prototype._changeCollection):
+        (WI.NetworkTableContentView.prototype.handleClearShortcut):
+        (WI.NetworkTableContentView.prototype._updateWaterfallTimeRange):
+        (WI.NetworkTableContentView.prototype._updateWaterfallTimelineRuler):
+        (WI.NetworkTableContentView.prototype._canExportHAR):
+        (WI.NetworkTableContentView.prototype._processPendingEntries):
+        (WI.NetworkTableContentView.prototype._populateWithInitialResourcesIfNeeded):
+        (WI.NetworkTableContentView.prototype._rowIndexForRepresentedObject):
+        (WI.NetworkTableContentView.prototype._updateEntryForResource):
+        (WI.NetworkTableContentView.prototype._updateEmptyFilterResultsMessage):
+        (WI.NetworkTableContentView.prototype._mainResourceDidChange):
+        (WI.NetworkTableContentView.prototype._mainFrameDidChange):
+        (WI.NetworkTableContentView.prototype._resourceLoadingDidFinish):
+        (WI.NetworkTableContentView.prototype._resourceLoadingDidFail):
+        (WI.NetworkTableContentView.prototype._resourceTransferSizeDidChange):
+        (WI.NetworkTableContentView.prototype._handleResourceAdded):
+        (WI.NetworkTableContentView.prototype._runForMainCollection):
+        (WI.NetworkTableContentView.prototype._isShowingMainCollection):
+        (WI.NetworkTableContentView.prototype._insertResourceAndReloadTable):
+        (WI.NetworkTableContentView.prototype._handleNodeDidFireEvent):
+        (WI.NetworkTableContentView.prototype._handleNodeLowPowerChanged):
+        (WI.NetworkTableContentView.prototype._updateSort):
+        (WI.NetworkTableContentView.prototype._updateFilteredEntries):
+        (WI.NetworkTableContentView.prototype._urlFilterDidChange):
+        (WI.NetworkTableContentView.prototype._HARResources):
+        (WI.NetworkTableContentView.prototype._importHAR):
+        Introduce the concept of collections that can be swapped in/out
+        to re-render the Network Table with different contents.
+
 2019-03-15  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: provide a way to capture a screenshot of a node from within the page
index 44eb84a..290a535 100644 (file)
@@ -552,6 +552,7 @@ localizedStrings["Images"] = "Images";
 localizedStrings["Immediate Pause Requested"] = "Immediate Pause Requested";
 localizedStrings["Import"] = "Import";
 localizedStrings["Imported"] = "Imported";
+localizedStrings["Imported - %s"] = "Imported - %s";
 localizedStrings["Imported Recordings"] = "Imported Recordings";
 localizedStrings["Imported \u2014 %s"] = "Imported \u2014 %s";
 localizedStrings["Incomplete"] = "Incomplete";
@@ -598,6 +599,7 @@ localizedStrings["Line Number"] = "Line Number";
 localizedStrings["Line wrapping:"] = "Line wrapping:";
 localizedStrings["Linear Gradient"] = "Linear Gradient";
 localizedStrings["Live"] = "Live";
+localizedStrings["Live Activity"] = "Live Activity";
 localizedStrings["Live Size"] = "Live Size";
 localizedStrings["Load \u2014 %s"] = "Load \u2014 %s";
 localizedStrings["Load cancelled"] = "Load cancelled";
diff --git a/Source/WebInspectorUI/UserInterface/Images/NetworkHAR.svg b/Source/WebInspectorUI/UserInterface/Images/NetworkHAR.svg
new file mode 100644 (file)
index 0000000..c3217b4
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <rect x="0.5" y="2.5" width="13" height="10" fill="none" stroke="currentColor"/>
+    <polyline points="2 14.5 15.5 14.5 15.5 4" fill="none" stroke="currentColor"/>
+    <line x1="2.5" y1="5.5" x2="11.5" y2="5.5" fill="none" stroke="currentColor"/>
+    <line x1="2.5" y1="9.5" x2="11.5" y2="9.5" fill="none" stroke="currentColor"/>
+</svg>
diff --git a/Source/WebInspectorUI/UserInterface/Images/NetworkOverview.svg b/Source/WebInspectorUI/UserInterface/Images/NetworkOverview.svg
new file mode 100644 (file)
index 0000000..3d6110d
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <rect x="0.5" y="2.5" width="13" height="10" fill="none" stroke="currentColor"/>
+    <polyline points="2 14.5 15.5 14.5 15.5 4" fill="none" stroke="currentColor"/>
+    <path fill="none" stroke="currentColor" d="M 4.5 2.5 L 4.5 10.5"/>
+    <path fill="none" stroke="currentColor" d="M 2.0 8.0 L 4.5 10.5 L 7.0 8.0"/>
+    <path fill="none" stroke="currentColor" d="M 9.5 12.5 L 9.5 4.5"/>
+    <path fill="none" stroke="currentColor" d="M 12.0 6.5 L 9.5 4.5 L 7.0 6.5"/>
+</svg>
index 281de6c..6e3dd1e 100644 (file)
     background: none;
 }
 
+.content-view.tab.network > .content-browser > .navigation-bar .hierarchical-path .icon {
+    -webkit-margin-start: 8px;
+    -webkit-margin-end: 4px;
+}
+
+.network-overview-icon > .icon {
+    content: url(../Images/NetworkOverview.svg);
+}
+
+.network-har-icon > .icon {
+    content: url(../Images/NetworkHAR.svg);
+}
+
 .content-view.network .network-table .icon {
     position: relative;
     width: 16px;
@@ -303,6 +316,10 @@ body[dir=rtl] .waterfall .block {
 }
 
 @media (prefers-color-scheme: dark) {
+    .content-view.tab.network > .content-browser > .navigation-bar .hierarchical-path .icon {
+        filter: invert(88%);
+    }
+
     .network-table .cell.domain > .lock {
         filter: var(--filter-invert);
     }
index 2e309f7..1389197 100644 (file)
@@ -29,14 +29,18 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
     {
         super(representedObject);
 
-        this._entries = [];
+        // Collections contain the set of values needed to render the table.
+        // The main collection reflects the main target's live activity.
+        // We create other collections for HAR imports.
+        this._collections = [];
+        this._activeCollection = null;
+        this._mainCollection = this._addCollection();
+        this._setActiveCollection(this._mainCollection);
+
         this._entriesSortComparator = null;
-        this._filteredEntries = [];
-        this._pendingInsertions = [];
-        this._pendingUpdates = [];
+
         this._pendingFilter = false;
         this._showingRepresentedObjectCookie = null;
-        this._showingImportedResources = false;
 
         this._table = null;
         this._nameColumnWidthSetting = new WI.Setting("network-table-content-view-name-column-width", 250);
@@ -47,8 +51,6 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         this._domNodeEntries = new Map;
 
-        this._waterfallStartTime = NaN;
-        this._waterfallEndTime = NaN;
         this._waterfallTimelineRuler = null;
         this._waterfallPopover = null;
 
@@ -123,9 +125,21 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             this._exportHAR();
         });
 
+        this._collectionsPathNavigationItem = new WI.HierarchicalPathNavigationItem;
+        this._collectionsPathNavigationItem.addEventListener(WI.HierarchicalPathNavigationItem.Event.PathComponentWasSelected, this._collectionsHierarchicalPathComponentWasSelected, this);
+
+        this._pathComponentsMap = new Map;
+        this._lastPathComponent = null;
+        let pathComponent = this._addCollectionPathComponent(this._mainCollection, WI.UIString("Live Activity"), "network-overview-icon");
+        this._collectionsPathNavigationItem.components = [pathComponent];
+
         this._checkboxesNavigationItemGroup = new WI.GroupNavigationItem([this._clearOnLoadNavigationItem, new WI.DividerNavigationItem]);
         this._checkboxesNavigationItemGroup.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
 
+        this._pathComponentsNavigationItemGroup = new WI.GroupNavigationItem([this._collectionsPathNavigationItem, new WI.DividerNavigationItem]);
+        this._pathComponentsNavigationItemGroup.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+        this._pathComponentsNavigationItemGroup.hidden = true;
+
         this._buttonsNavigationItemGroup = new WI.GroupNavigationItem([this._harImportNavigationItem, this._harExportNavigationItem, new WI.DividerNavigationItem]);
         this._buttonsNavigationItemGroup.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
 
@@ -141,7 +155,9 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         }
 
         this._clearNetworkItemsNavigationItem = new WI.ButtonNavigationItem("clear-network-items", WI.UIString("Clear Network Items (%s)").format(WI.clearKeyboardShortcut.displayName), "Images/NavigationItemTrash.svg", 15, 15);
-        this._clearNetworkItemsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => { this.reset(); });
+        this._clearNetworkItemsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => {
+            this.reset();
+        });
 
         WI.Target.addEventListener(WI.Target.Event.ResourceAdded, this._handleResourceAdded, this);
         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
@@ -219,7 +235,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     get navigationItems()
     {
-        let items = [this._checkboxesNavigationItemGroup, this._buttonsNavigationItemGroup];
+        let items = [this._checkboxesNavigationItemGroup, this._pathComponentsNavigationItemGroup, this._buttonsNavigationItemGroup];
         if (this._disableResourceCacheNavigationItem)
             items.push(this._disableResourceCacheNavigationItem);
         items.push(this._clearNetworkItemsNavigationItem);
@@ -288,9 +304,14 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     reset()
     {
-        this._entries = [];
-        this._filteredEntries = [];
-        this._pendingInsertions = [];
+        this._runForMainCollection((collection) => {
+            collection.entries = [];
+            collection.filteredEntries = [];
+            collection.pendingInsertions = [];
+            collection.pendingUpdates = [];
+            collection.waterfallStartTime = NaN;
+            collection.waterfallEndTime = NaN;
+        });
 
         for (let detailView of this._detailViewMap.values())
             detailView.dispose();
@@ -298,8 +319,6 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         this._domNodeEntries.clear();
 
-        this._waterfallStartTime = NaN;
-        this._waterfallEndTime = NaN;
         this._updateWaterfallTimelineRuler();
         this._updateExportButton();
 
@@ -309,8 +328,6 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             this._hidePopover();
             this._hideDetailView();
         }
-
-        this._showingImportedResources = false;
     }
 
     showRepresentedObject(representedObject, cookie)
@@ -343,18 +360,18 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     tableIndexForRepresentedObject(table, object)
     {
-        return this._filteredEntries.indexOf(object);
+        return this._activeCollection.filteredEntries.indexOf(object);
     }
 
     tableRepresentedObjectForIndex(table, index)
     {
-        console.assert(index >=0 && index < this._filteredEntries.length);
-        return this._filteredEntries[index];
+        console.assert(index >=0 && index < this._activeCollection.filteredEntries.length);
+        return this._activeCollection.filteredEntries[index];
     }
 
     tableNumberOfRows(table)
     {
-        return this._filteredEntries.length;
+        return this._activeCollection.filteredEntries.length;
     }
 
     tableSortChanged(table)
@@ -383,7 +400,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         this._table.selectRow(rowIndex);
 
-        let entry = this._filteredEntries[rowIndex];
+        let entry = this._activeCollection.filteredEntries[rowIndex];
         let contextMenu = WI.ContextMenu.createFromEvent(event);
         WI.appendContextMenuItemsForSourceCode(contextMenu, entry.resource);
 
@@ -405,7 +422,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             return;
         }
 
-        let entry = this._filteredEntries[rowIndex];
+        let entry = this._activeCollection.filteredEntries[rowIndex];
         if (entry.resource === this._selectedObject || entry.domNode === this._selectedObject)
             return;
 
@@ -418,7 +435,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     tablePopulateCell(table, cell, column, rowIndex)
     {
-        let entry = this._filteredEntries[rowIndex];
+        let entry = this._activeCollection.filteredEntries[rowIndex];
 
         if (entry.resource)
             cell.classList.toggle("error", entry.resource.hadLoadingError());
@@ -506,6 +523,82 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     // Private
 
+    _addCollection()
+    {
+        let collection = {
+            entries: [],
+            filteredEntries: [],
+            pendingInsertions: [],
+            pendingUpdates: [],
+            waterfallStartTime: NaN,
+            waterfallEndTime: NaN,
+        };
+
+        this._collections.push(collection);
+
+        return collection;
+    }
+
+    _setActiveCollection(collection)
+    {
+        console.assert(this._collections.includes(collection));
+
+        if (this._activeCollection === collection)
+            return;
+
+        this._activeCollection = collection;
+    }
+
+    _addCollectionPathComponent(collection, displayName, iconClassName)
+    {
+        let pathComponent = new WI.HierarchicalPathComponent(displayName, iconClassName, collection);
+        this._pathComponentsMap.set(collection, pathComponent);
+
+        if (this._lastPathComponent) {
+            this._lastPathComponent.nextSibling = pathComponent;
+            pathComponent.previousSibling = this._lastPathComponent;
+        }
+
+        this._lastPathComponent = pathComponent;
+
+        if (this._pathComponentsNavigationItemGroup && this._pathComponentsMap.size > 1)
+            this._pathComponentsNavigationItemGroup.hidden = false;
+
+        return pathComponent;
+    }
+
+    _collectionsHierarchicalPathComponentWasSelected(event)
+    {
+        console.assert(event.data.pathComponent instanceof WI.HierarchicalPathComponent);
+
+        let collection = event.data.pathComponent.representedObject;
+
+        this._changeCollection(collection);
+    }
+
+    _changeCollection(collection)
+    {
+        if (collection === this._activeCollection)
+            return;
+
+        this._setActiveCollection(collection);
+
+        let isMain = collection === this._mainCollection;
+        this._checkboxesNavigationItemGroup.hidden = !isMain;
+        this._groupByDOMNodeNavigationItem.hidden = !isMain;
+        this._clearNetworkItemsNavigationItem.enabled = isMain;
+        this._collectionsPathNavigationItem.components = [this._pathComponentsMap.get(collection)];
+
+        this._updateSort();
+        this._updateActiveFilterResources();
+        this._updateFilteredEntries();
+        this._updateWaterfallTimelineRuler();
+        this._reloadTable();
+        this._hideDetailView();
+
+        this.needsLayout();
+    }
+
     _populateNameCell(cell, entry)
     {
         console.assert(!cell.firstChild, "We expect the cell to be empty.", cell, cell.firstChild);
@@ -677,6 +770,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         let container = cell.appendChild(document.createElement("div"));
         container.className = "waterfall-container";
 
+        let collection = this._activeCollection;
         let graphStartTime = this._waterfallTimelineRuler.startTime;
         let secondsPerPixel = this._waterfallTimelineRuler.secondsPerPixel;
 
@@ -712,7 +806,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                     fullscreenDOMEvents.unshift({timestamp: graphStartTime});
 
                 if (fullscreenDOMEvents.lastValue.data.enabled)
-                    fullscreenDOMEvents.push({timestamp: this._waterfallEndTime});
+                    fullscreenDOMEvents.push({timestamp: collection.waterfallEndTime});
 
                 console.assert((fullscreenDOMEvents.length % 2) === 0, "Every enter/exit of fullscreen should have a corresponding exit/enter.");
 
@@ -732,7 +826,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
             for (let lowPowerRange of domNode.lowPowerRanges) {
                 let startTimestamp = lowPowerRange.startTimestamp || graphStartTime;
-                let endTimestamp = lowPowerRange.endTimestamp || this._waterfallEndTime;
+                let endTimestamp = lowPowerRange.endTimestamp || collection.waterfallEndTime;
 
                 let lowPowerElement = container.appendChild(document.createElement("div"));
                 lowPowerElement.classList.add("area", "low-power");
@@ -771,7 +865,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                 createDOMEventLine(a.domEvents, a.endTimestamp, b.startTimestamp);
 
             if (groupedDOMEvents.length)
-                createDOMEventLine(groupedDOMEvents.lastValue.domEvents, groupedDOMEvents.lastValue.endTimestamp, this._waterfallEndTime);
+                createDOMEventLine(groupedDOMEvents.lastValue.domEvents, groupedDOMEvents.lastValue.endTimestamp, collection.waterfallEndTime);
 
             for (let {startTimestamp, endTimestamp, domEvents} of groupedDOMEvents) {
                 let paddingForCentering = NetworkTableContentView.nodeWaterfallDOMEventSize * secondsPerPixel / 2;
@@ -1149,6 +1243,9 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     handleClearShortcut(event)
     {
+        if (!this._isShowingMainCollection())
+            return;
+
         this.reset();
     }
 
@@ -1156,11 +1253,13 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _updateWaterfallTimeRange(startTimestamp, endTimestamp)
     {
-        if (isNaN(this._waterfallStartTime) || startTimestamp < this._waterfallStartTime)
-            this._waterfallStartTime = startTimestamp;
+        let collection = this._activeCollection;
 
-        if (isNaN(this._waterfallEndTime) || endTimestamp > this._waterfallEndTime)
-            this._waterfallEndTime = endTimestamp;
+        if (isNaN(collection.waterfallStartTime) || startTimestamp < collection.waterfallStartTime)
+            collection.waterfallStartTime = startTimestamp;
+
+        if (isNaN(collection.waterfallEndTime) || endTimestamp > collection.waterfallEndTime)
+            collection.waterfallEndTime = endTimestamp;
     }
 
     _updateWaterfallTimelineRuler()
@@ -1168,26 +1267,31 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         if (!this._waterfallTimelineRuler)
             return;
 
-        if (isNaN(this._waterfallStartTime)) {
+        let collection = this._activeCollection;
+
+        if (isNaN(collection.waterfallStartTime)) {
             this._waterfallTimelineRuler.zeroTime = 0;
             this._waterfallTimelineRuler.startTime = 0;
             this._waterfallTimelineRuler.endTime = 0.250;
         } else {
-            this._waterfallTimelineRuler.zeroTime = this._waterfallStartTime;
-            this._waterfallTimelineRuler.startTime = this._waterfallStartTime;
-            this._waterfallTimelineRuler.endTime = this._waterfallEndTime;
+            this._waterfallTimelineRuler.zeroTime = collection.waterfallStartTime;
+            this._waterfallTimelineRuler.startTime = collection.waterfallStartTime;
+            this._waterfallTimelineRuler.endTime = collection.waterfallEndTime;
 
             // Add a little bit of padding on the each side.
             const paddingPixels = 5;
             let padSeconds = paddingPixels * this._waterfallTimelineRuler.secondsPerPixel;
-            this._waterfallTimelineRuler.zeroTime = this._waterfallStartTime - padSeconds;
-            this._waterfallTimelineRuler.startTime = this._waterfallStartTime - padSeconds;
-            this._waterfallTimelineRuler.endTime = this._waterfallEndTime + padSeconds;
+            this._waterfallTimelineRuler.zeroTime = collection.waterfallStartTime - padSeconds;
+            this._waterfallTimelineRuler.startTime = collection.waterfallStartTime - padSeconds;
+            this._waterfallTimelineRuler.endTime = collection.waterfallEndTime + padSeconds;
         }
     }
 
     _canExportHAR()
     {
+        if (!this._isShowingMainCollection())
+            return false;
+
         let mainFrame = WI.networkManager.mainFrame;
         if (!mainFrame)
             return false;
@@ -1212,31 +1316,32 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _processPendingEntries()
     {
-        let needsSort = this._pendingUpdates.length > 0;
+        let collection = this._activeCollection;
+        let needsSort = collection.pendingUpdates.length > 0;
         let needsFilter = this._pendingFilter;
 
         // No global sort or filter is needed, so just insert new records into their sorted position.
         if (!needsSort && !needsFilter) {
-            let originalLength = this._pendingInsertions.length;
-            for (let resource of this._pendingInsertions)
+            let originalLength = collection.pendingInsertions.length;
+            for (let resource of collection.pendingInsertions)
                 this._insertResourceAndReloadTable(resource);
-            console.assert(this._pendingInsertions.length === originalLength);
-            this._pendingInsertions = [];
+            console.assert(collection.pendingInsertions.length === originalLength);
+            collection.pendingInsertions = [];
             return;
         }
 
-        for (let resource of this._pendingInsertions) {
+        for (let resource of collection.pendingInsertions) {
             let resourceEntry = this._entryForResource(resource);
             this._tryLinkResourceToDOMNode(resourceEntry);
-            this._entries.push(resourceEntry);
+            collection.entries.push(resourceEntry);
         }
-        this._pendingInsertions = [];
+        collection.pendingInsertions = [];
 
-        for (let updateObject of this._pendingUpdates) {
+        for (let updateObject of collection.pendingUpdates) {
             if (updateObject instanceof WI.Resource)
                 this._updateEntryForResource(updateObject);
         }
-        this._pendingUpdates = [];
+        collection.pendingUpdates = [];
 
         this._pendingFilter = false;
 
@@ -1245,7 +1350,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._reloadTable();
     }
 
-    _populateWithInitialResourcesIfNeeded()
+    _populateWithInitialResourcesIfNeeded(collection)
     {
         if (!this._needsInitialPopulate)
             return;
@@ -1254,12 +1359,12 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         let populateResourcesForFrame = (frame) => {
             if (frame.provisionalMainResource)
-                this._pendingInsertions.push(frame.provisionalMainResource);
+                collection.pendingInsertions.push(frame.provisionalMainResource);
             else if (frame.mainResource)
-                this._pendingInsertions.push(frame.mainResource);
+                collection.pendingInsertions.push(frame.mainResource);
 
             for (let resource of frame.resourceCollection)
-                this._pendingInsertions.push(resource);
+                collection.pendingInsertions.push(resource);
 
             for (let childFrame of frame.childFrameCollection)
                 populateResourcesForFrame(childFrame);
@@ -1267,9 +1372,9 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         let populateResourcesForTarget = (target) => {
             if (target.mainResource instanceof WI.Resource)
-                this._pendingInsertions.push(target.mainResource);
+                collection.pendingInsertions.push(target.mainResource);
             for (let resource of target.resourceCollection)
-                this._pendingInsertions.push(resource);
+                collection.pendingInsertions.push(resource);
         };
 
         for (let target of WI.targets) {
@@ -1299,7 +1404,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _rowIndexForRepresentedObject(object)
     {
-        return this._filteredEntries.findIndex((x) => {
+        return this._activeCollection.filteredEntries.findIndex((x) => {
             if (x.resource === object)
                 return true;
             if (x.domNode === object)
@@ -1310,7 +1415,9 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _updateEntryForResource(resource)
     {
-        let index = this._entries.findIndex((x) => x.resource === resource);
+        let collection = this._activeCollection;
+
+        let index = collection.entries.findIndex((x) => x.resource === resource);
         if (index === -1)
             return;
 
@@ -1321,13 +1428,13 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         }
 
         let entry = this._entryForResource(resource);
-        updateExistingEntry(this._entries[index], entry);
+        updateExistingEntry(collection.entries[index], entry);
 
         let rowIndex = this._rowIndexForRepresentedObject(resource);
         if (rowIndex === -1)
             return;
 
-        updateExistingEntry(this._filteredEntries[rowIndex], entry);
+        updateExistingEntry(collection.filteredEntries[rowIndex], entry);
     }
 
     _hidePopover()
@@ -1408,7 +1515,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _updateEmptyFilterResultsMessage()
     {
-        if (this._hasActiveFilter() && !this._filteredEntries.length)
+        if (this._hasActiveFilter() && !this._activeCollection.filteredEntries.length)
             this._showEmptyFilterResultsMessage();
         else
             this._hideEmptyFilterResultsMessage();
@@ -1469,45 +1576,55 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         this.reset();
 
-        if (this._transitioningPageTarget) {
-            this._transitioningPageTarget = false;
-            this._needsInitialPopulate = true;
-            this._populateWithInitialResourcesIfNeeded();
-            return;
-        }
+        this._runForMainCollection((collection) => {
+            if (this._transitioningPageTarget) {
+                this._transitioningPageTarget = false;
+                this._needsInitialPopulate = true;
+                this._populateWithInitialResourcesIfNeeded(collection);
+                return;
+            }
 
-        this._insertResourceAndReloadTable(frame.mainResource);
+            this._insertResourceAndReloadTable(frame.mainResource);
+        });
     }
 
     _mainFrameDidChange()
     {
-        this._populateWithInitialResourcesIfNeeded();
+        this._runForMainCollection((collection) => {
+            this._populateWithInitialResourcesIfNeeded(collection);
+        });
     }
 
     _resourceLoadingDidFinish(event)
     {
-        let resource = event.target;
-        this._pendingUpdates.push(resource);
+        this._runForMainCollection((collection, wasMain) => {
+            let resource = event.target;
+            collection.pendingUpdates.push(resource);
 
-        this._updateWaterfallTimeRange(resource.firstTimestamp, resource.timingData.responseEnd);
+            this._updateWaterfallTimeRange(resource.firstTimestamp, resource.timingData.responseEnd);
 
-        if (this._hasURLFilter())
-            this._checkURLFilterAgainstResource(resource);
+            if (this._hasURLFilter())
+                this._checkURLFilterAgainstResource(resource);
 
-        this.needsLayout();
+            if (wasMain)
+                this.needsLayout();
+        });
     }
 
     _resourceLoadingDidFail(event)
     {
-        let resource = event.target;
-        this._pendingUpdates.push(resource);
+        this._runForMainCollection((collection, wasMain) => {
+            let resource = event.target;
+            collection.pendingUpdates.push(resource);
 
-        this._updateWaterfallTimeRange(resource.firstTimestamp, resource.timingData.responseEnd);
+            this._updateWaterfallTimeRange(resource.firstTimestamp, resource.timingData.responseEnd);
 
-        if (this._hasURLFilter())
-            this._checkURLFilterAgainstResource(resource);
+            if (this._hasURLFilter())
+                this._checkURLFilterAgainstResource(resource);
 
-        this.needsLayout();
+            if (wasMain)
+                this.needsLayout();
+        });
     }
 
     _resourceTransferSizeDidChange(event)
@@ -1515,35 +1632,58 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         if (!this._table)
             return;
 
-        let resource = event.target;
+        this._runForMainCollection((collection, wasMain) => {
+            let resource = event.target;
 
-        // In the unlikely event that this is the sort column, we may need to resort.
-        if (this._table.sortColumnIdentifier === "transferSize") {
-            this._pendingUpdates.push(resource);
-            this.needsLayout();
-            return;
-        }
+            // In the unlikely event that this is the sort column, we may need to resort.
+            if (this._table.sortColumnIdentifier === "transferSize") {
+                collection.pendingUpdates.push(resource);
+                this.needsLayout();
+                return;
+            }
 
-        let index = this._entries.findIndex((x) => x.resource === resource);
-        if (index === -1)
-            return;
+            let index = collection.entries.findIndex((x) => x.resource === resource);
+            if (index === -1)
+                return;
 
-        let entry = this._entries[index];
-        entry.transferSize = !isNaN(resource.networkTotalTransferSize) ? resource.networkTotalTransferSize : resource.estimatedTotalTransferSize;
+            let entry = collection.entries[index];
+            entry.transferSize = !isNaN(resource.networkTotalTransferSize) ? resource.networkTotalTransferSize : resource.estimatedTotalTransferSize;
 
-        let rowIndex = this._rowIndexForRepresentedObject(resource);
-        if (rowIndex === -1)
-            return;
+            if (!wasMain)
+                return;
+
+            let rowIndex = this._rowIndexForRepresentedObject(resource);
+            if (rowIndex === -1)
+                return;
 
-        this._table.reloadCell(rowIndex, "transferSize");
+            this._table.reloadCell(rowIndex, "transferSize");
+        });
     }
 
     _handleResourceAdded(event)
     {
-        if (this._showingImportedResources)
-            return;
+        this._runForMainCollection((collection) => {
+            this._insertResourceAndReloadTable(event.data.resource);
+        });
+    }
 
-        this._insertResourceAndReloadTable(event.data.resource);
+    _runForMainCollection(callback)
+    {
+        let currentCollection = this._activeCollection;
+        let wasMain = currentCollection === this._mainCollection;
+
+        if (!wasMain)
+            this._setActiveCollection(this._mainCollection);
+
+        callback(this._activeCollection, wasMain);
+
+        if (!wasMain)
+            this._setActiveCollection(currentCollection);
+    }
+
+    _isShowingMainCollection()
+    {
+        return this._activeCollection === this._mainCollection;
     }
 
     _isDefaultSort()
@@ -1553,10 +1693,15 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _insertResourceAndReloadTable(resource)
     {
+        if (this._needsInitialPopulate)
+            return;
+
+        let collection = this._activeCollection;
+
         this._updateWaterfallTimeRange(resource.firstTimestamp, resource.timingData.responseEnd);
 
         if (!this._table || !(WI.tabBrowser.selectedTabContentView instanceof WI.NetworkTabContentView)) {
-            this._pendingInsertions.push(resource);
+            collection.pendingInsertions.push(resource);
             this.needsLayout();
             return;
         }
@@ -1570,25 +1715,25 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                 this._generateSortComparator();
         } else if (this._isDefaultSort() || !this._entriesSortComparator) {
             // Default sort has fast path.
-            this._entries.push(resourceEntry);
+            collection.entries.push(resourceEntry);
             if (this._passFilter(resourceEntry)) {
-                this._filteredEntries.push(resourceEntry);
+                collection.filteredEntries.push(resourceEntry);
                 this._table.reloadDataAddedToEndOnly();
             }
             return;
         }
 
-        insertObjectIntoSortedArray(resourceEntry, this._entries, this._entriesSortComparator);
+        insertObjectIntoSortedArray(resourceEntry, collection.entries, this._entriesSortComparator);
 
         if (this._passFilter(resourceEntry)) {
             if (WI.settings.groupByDOMNode.value)
                 this._updateFilteredEntries();
             else
-                insertObjectIntoSortedArray(resourceEntry, this._filteredEntries, this._entriesSortComparator);
+                insertObjectIntoSortedArray(resourceEntry, collection.filteredEntries, this._entriesSortComparator);
 
             // Probably a useless optimization here, but if we only added this row to the end
             // we may avoid recreating all visible rows by saying as such.
-            if (this._filteredEntries.lastValue === resourceEntry)
+            if (collection.filteredEntries.lastValue === resourceEntry)
                 this._table.reloadDataAddedToEndOnly();
             else
                 this._reloadTable();
@@ -1671,26 +1816,32 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _handleNodeDidFireEvent(event)
     {
-        let domNode = event.target;
-        let {domEvent} = event.data;
+        this._runForMainCollection((collection, wasMain) => {
+            let domNode = event.target;
+            let {domEvent} = event.data;
 
-        this._pendingUpdates.push(domNode);
+            collection.pendingUpdates.push(domNode);
 
-        this._updateWaterfallTimeRange(NaN, domEvent.timestamp + (this._waterfallTimelineRuler.secondsPerPixel * 10));
+            this._updateWaterfallTimeRange(NaN, domEvent.timestamp + (this._waterfallTimelineRuler.secondsPerPixel * 10));
 
-        this.needsLayout();
+            if (wasMain)
+                this.needsLayout();
+        });
     }
 
     _handleNodeLowPowerChanged(event)
     {
-        let domNode = event.target;
-        let {timestamp} = event.data;
+        this._runForMainCollection((collection, wasMain) => {
+            let domNode = event.target;
+            let {timestamp} = event.data;
 
-        this._pendingUpdates.push(domNode);
+            collection.pendingUpdates.push(domNode);
 
-        this._updateWaterfallTimeRange(NaN, timestamp + (this._waterfallTimelineRuler.secondsPerPixel * 10));
+            this._updateWaterfallTimeRange(NaN, timestamp + (this._waterfallTimelineRuler.secondsPerPixel * 10));
 
-        this.needsLayout();
+            if (wasMain)
+                this.needsLayout();
+        });
     }
 
     _hasTypeFilter()
@@ -1731,16 +1882,20 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _updateSort()
     {
-        if (this._entriesSortComparator)
-            this._entries = this._entries.sort(this._entriesSortComparator);
+        if (this._entriesSortComparator) {
+            let collection = this._activeCollection;
+            collection.entries = collection.entries.sort(this._entriesSortComparator);
+        }
     }
 
     _updateFilteredEntries()
     {
+        let collection = this._activeCollection;
+
         if (this._hasActiveFilter())
-            this._filteredEntries = this._entries.filter(this._passFilter, this);
+            collection.filteredEntries = collection.entries.filter(this._passFilter, this);
         else
-            this._filteredEntries = this._entries.slice();
+            collection.filteredEntries = collection.entries.slice();
 
         if (WI.settings.groupByDOMNode.value) {
             for (let nodeEntry of this._domNodeEntries.values()) {
@@ -1752,7 +1907,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                     if (this._hasActiveFilter() && !this._passFilter(resourceEntry))
                         continue;
 
-                    let index = this._filteredEntries.indexOf(resourceEntry);
+                    let index = collection.filteredEntries.indexOf(resourceEntry);
                     if (index >= 0 && index < firstIndex)
                         firstIndex = index;
                 }
@@ -1760,10 +1915,10 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
                 if (!isFinite(firstIndex))
                     continue;
 
-                this._filteredEntries.insertAtIndex(nodeEntry, firstIndex);
+                collection.filteredEntries.insertAtIndex(nodeEntry, firstIndex);
             }
 
-            this._filteredEntries = this._filteredEntries.filter((entry) => {
+            collection.filteredEntries = collection.filteredEntries.filter((entry) => {
                 if (entry.resource && entry.resource.initiatorNode) {
                     let nodeEntry = this._domNodeEntries.get(entry.resource.initiatorNode);
                     if (!nodeEntry.expanded)
@@ -1891,15 +2046,21 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._urlFilterSearchText = searchQuery;
         this._urlFilterSearchRegex = WI.SearchUtilities.regExpForString(searchQuery, WI.SearchUtilities.defaultSettings);
 
-        this._activeURLFilterResources.clear();
-
-        for (let entry of this._entries)
-            this._checkURLFilterAgainstResource(entry.resource);
-
+        this._updateActiveFilterResources();
         this._updateFilteredEntries();
         this._reloadTable();
     }
 
+    _updateActiveFilterResources()
+    {
+        this._activeURLFilterResources.clear();
+
+        if (this._hasURLFilter()) {
+            for (let entry of this._activeCollection.entries)
+                this._checkURLFilterAgainstResource(entry.resource);            
+        }
+    }
+
     _restoreSelectedRow()
     {
         if (!this._selectedObject)
@@ -1918,11 +2079,11 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _HARResources()
     {
-        let resources = this._filteredEntries.map((x) => x.resource);
+        let resources = this._activeCollection.filteredEntries.map((x) => x.resource);
         const supportedHARSchemes = new Set(["http", "https", "ws", "wss"]);
         return resources.filter((resource) => {
             if (!resource) {
-                // DOM node entries are also added to `_filteredEntries`.
+                // DOM node entries are also added to `filteredEntries`.
                 return false;
             }
 
@@ -1963,13 +2124,15 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             if (!resources)
                 return;
 
-            this.reset();
+            let importedCollection = this._addCollection();
+
+            let displayName = WI.UIString("Imported - %s").format(result.filename);
+            this._addCollectionPathComponent(importedCollection, displayName, "network-har-icon");
 
-            this._showingImportedResources = true;
+            this._changeCollection(importedCollection);
 
             for (let resource of resources)
                 this._insertResourceAndReloadTable(resource);
-            this.needsLayout();
         });
     }