Web Inspector: Detail Views for resources in Network Tab
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Oct 2017 19:17:30 +0000 (19:17 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Oct 2017 19:17:30 +0000 (19:17 +0000)
https://bugs.webkit.org/show_bug.cgi?id=177553

Reviewed by Devin Rousso.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Main.html:
New strings and resources.

* UserInterface/Base/Main.js:
(WI._focusedContentBrowser):
Detect nested content browsers instead of only top level tab content browsers.

* UserInterface/Base/Setting.js:
Add a new global setting for which Network Detail view is preferred.

* UserInterface/Views/ContentBrowser.css:
(.content-browser > .navigation-bar .item):
(.content-browser > .navigation-bar > .item): Deleted.
Generalize a navigation item style so it works on items nested inside a group.

* UserInterface/Views/ContentBrowser.js:
(WI.ContentBrowser.prototype._updateContentViewSelectionPathNavigationItem):
(WI.ContentBrowser.prototype._updateContentViewNavigationItems):
(WI.ContentBrowser.prototype._removeAllNavigationItems):
Give ContentBrowser a way to group all ContentView specific navigation items
inside a GroupNavigationItem. This lets the client decide what to do with
those navigation items, instead of default behavior in the navigation bar.

* UserInterface/Views/ContentViewContainer.js:
(WI.ContentViewContainer.prototype.showContentView):
Avoid a flash when showContentView is called with the current content view.

* UserInterface/Views/FlexibleSpaceNavigationItem.css:
(:matches(.navigation-bar, .toolbar) .item.flexible-space.align-start > .item):
(:matches(.navigation-bar, .toolbar) .item.flexible-space.align-end > .item):
When containing a navigation item decide where you want the items to align to.

* UserInterface/Views/FlexibleSpaceNavigationItem.js:
(WI.FlexibleSpaceNavigationItem):
(WI.FlexibleSpaceNavigationItem.prototype.updateLayout):
Provide an option to embed a NavigationItem within a FlexibleSpace. Its behavior right
now is rather simple. If the embedded Item fits in the current available space it is
shown. If it doesn't fit, it is hidden and we have just a flexible space. This is used
in the network detail view's navigation bar to keep the main navigation items centered
while allowing for buttons to show up on the side without affecting the centering.

* UserInterface/Views/NavigationBar.js:
(WI.NavigationBar):
(WI.NavigationBar.prototype._mouseDown):
(WI.NavigationBar.prototype._mouseUp):
Simplify event registration. This would also help avoid cases where we
might have registered multiple mousedown handlers.

* UserInterface/Views/GroupNavigationItem.js:
(WI.GroupNavigationItem):
(WI.GroupNavigationItem.prototype.get navigationItems):
(WI.GroupNavigationItem.prototype.set navigationItems):
(WI.GroupNavigationItem.prototype.get width):
(WI.GroupNavigationItem.prototype.get minimumWidth):
(WI.GroupNavigationItem.prototype._updateItems):
* UserInterface/Views/HierarchicalPathNavigationItem.js:
(WI.HierarchicalPathNavigationItem):
(WI.HierarchicalPathNavigationItem.prototype.set components):
(WI.HierarchicalPathNavigationItem.prototype.updateLayout):
(WI.HierarchicalPathNavigationItem.prototype._updateComponentsIfNeeded):
Defer DOM modifications until layout for NavigationItems container classes
that change items/components dynamically. This reduces UI flashing in the
bar when items/components change by coalescing all DOM updates at the same
time; when the NavigationBar does its next layout.

* UserInterface/Views/NetworkResourceDetailView.css:
(.network-resource-detail):
(.network-resource-detail .navigation-bar):
(.network-resource-detail .item.close > .glyph):
(.network-resource-detail .item.close > .glyph:hover):
(.network-resource-detail .item.close > .glyph:active):
(.network .network-resource-detail .navigation-bar .item.radio.button.text-only):
(.network .network-resource-detail .navigation-bar .item.radio.button.text-only.selected):
(.network-resource-detail > .content-browser):
Styles for the detail view's navigation bar.

* UserInterface/Views/NetworkResourceDetailView.js: Added.
(WI.NetworkResourceDetailView):
(WI.NetworkResourceDetailView.prototype.get resource):
(WI.NetworkResourceDetailView.prototype.shown):
(WI.NetworkResourceDetailView.prototype.hidden):
(WI.NetworkResourceDetailView.prototype.dispose):
(WI.NetworkResourceDetailView.prototype.initialLayout):
(WI.NetworkResourceDetailView.prototype._showPreferredContentView):
(WI.NetworkResourceDetailView.prototype._showContentViewForNavigationItem):
(WI.NetworkResourceDetailView.prototype._navigationItemSelected):
(WI.NetworkResourceDetailView.prototype._handleCloseButton):
ContentBrowser with customized navigation bar. This container has a fixed
list of ContentViews all relating to the Resource. The detail view has
a single delegate method for its close button. Since it maintains a
ContentBrowser it needs to expose shown/hidden/dispose logic to ensure
proper ContentView lifecycle events.

* UserInterface/Views/NetworkTableContentView.css:
(.showing-detail .table .cell:not(.name)):
(.showing-detail .table .resizer:not(:first-of-type)):
* UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView):
(WI.NetworkTableContentView.prototype.shown):
(WI.NetworkTableContentView.prototype.hidden):
(WI.NetworkTableContentView.prototype.closed):
(WI.NetworkTableContentView.prototype.reset):
(WI.NetworkTableContentView.prototype.networkResourceDetailViewClose):
(WI.NetworkTableContentView.prototype.tableSortChanged):
(WI.NetworkTableContentView.prototype.tableCellClicked):
(WI.NetworkTableContentView.prototype.tableSelectedRowChanged):
(WI.NetworkTableContentView.prototype.layout):
(WI.NetworkTableContentView.prototype._hideResourceDetailView):
(WI.NetworkTableContentView.prototype._showResourceDetailView):
(WI.NetworkTableContentView.prototype._positionDetailView):
(WI.NetworkTableContentView.prototype._updateFilteredEntries):
(WI.NetworkTableContentView.prototype._typeFilterScopeBarSelectionChanged):
(WI.NetworkTableContentView.prototype._restoreSelectedRow):
(WI.NetworkTableContentView.prototype._tableNameColumnDidChangeWidth):
Behavior for selecting a row and showing / hiding the detail view.
The detail view is positioned beside the Network Table's "name" column
and resizes when that column resizes.

* UserInterface/Views/Resizer.css:
(.resizer):
* UserInterface/Views/Table.css:
(.table > .resizers):
* UserInterface/Views/Table.js:
(WI.Table):
(WI.Table.prototype.get scrollContainer):
(WI.Table.prototype._positionResizerElements):
To let clients customize the table a bit, put resizers into their own
container and expose the scroll container.

* UserInterface/Views/Variables.css:
(:root):
Add a new button hover color, slightly lighter than the existing button active color.

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

21 files changed:
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Base/Setting.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Views/ContentBrowser.css
Source/WebInspectorUI/UserInterface/Views/ContentBrowser.js
Source/WebInspectorUI/UserInterface/Views/ContentViewContainer.js
Source/WebInspectorUI/UserInterface/Views/FlexibleSpaceNavigationItem.css
Source/WebInspectorUI/UserInterface/Views/FlexibleSpaceNavigationItem.js
Source/WebInspectorUI/UserInterface/Views/GroupNavigationItem.js
Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js
Source/WebInspectorUI/UserInterface/Views/NavigationBar.js
Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js
Source/WebInspectorUI/UserInterface/Views/Resizer.css
Source/WebInspectorUI/UserInterface/Views/Table.css
Source/WebInspectorUI/UserInterface/Views/Table.js
Source/WebInspectorUI/UserInterface/Views/Variables.css

index ab34da3..dcdd219 100644 (file)
@@ -1,5 +1,146 @@
 2017-10-04  Joseph Pecoraro  <pecoraro@apple.com>
 
+        Web Inspector: Detail Views for resources in Network Tab
+        https://bugs.webkit.org/show_bug.cgi?id=177553
+
+        Reviewed by Devin Rousso.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Main.html:
+        New strings and resources.
+
+        * UserInterface/Base/Main.js:
+        (WI._focusedContentBrowser):
+        Detect nested content browsers instead of only top level tab content browsers.
+
+        * UserInterface/Base/Setting.js:
+        Add a new global setting for which Network Detail view is preferred.
+
+        * UserInterface/Views/ContentBrowser.css:
+        (.content-browser > .navigation-bar .item):
+        (.content-browser > .navigation-bar > .item): Deleted.
+        Generalize a navigation item style so it works on items nested inside a group.
+
+        * UserInterface/Views/ContentBrowser.js:
+        (WI.ContentBrowser.prototype._updateContentViewSelectionPathNavigationItem):
+        (WI.ContentBrowser.prototype._updateContentViewNavigationItems):
+        (WI.ContentBrowser.prototype._removeAllNavigationItems):
+        Give ContentBrowser a way to group all ContentView specific navigation items
+        inside a GroupNavigationItem. This lets the client decide what to do with
+        those navigation items, instead of default behavior in the navigation bar.
+
+        * UserInterface/Views/ContentViewContainer.js:
+        (WI.ContentViewContainer.prototype.showContentView):
+        Avoid a flash when showContentView is called with the current content view.
+
+        * UserInterface/Views/FlexibleSpaceNavigationItem.css:
+        (:matches(.navigation-bar, .toolbar) .item.flexible-space.align-start > .item):
+        (:matches(.navigation-bar, .toolbar) .item.flexible-space.align-end > .item):
+        When containing a navigation item decide where you want the items to align to.
+
+        * UserInterface/Views/FlexibleSpaceNavigationItem.js:
+        (WI.FlexibleSpaceNavigationItem):
+        (WI.FlexibleSpaceNavigationItem.prototype.updateLayout):
+        Provide an option to embed a NavigationItem within a FlexibleSpace. Its behavior right
+        now is rather simple. If the embedded Item fits in the current available space it is
+        shown. If it doesn't fit, it is hidden and we have just a flexible space. This is used
+        in the network detail view's navigation bar to keep the main navigation items centered
+        while allowing for buttons to show up on the side without affecting the centering.
+
+        * UserInterface/Views/NavigationBar.js:
+        (WI.NavigationBar):
+        (WI.NavigationBar.prototype._mouseDown):
+        (WI.NavigationBar.prototype._mouseUp):
+        Simplify event registration. This would also help avoid cases where we
+        might have registered multiple mousedown handlers.
+
+        * UserInterface/Views/GroupNavigationItem.js:
+        (WI.GroupNavigationItem):
+        (WI.GroupNavigationItem.prototype.get navigationItems):
+        (WI.GroupNavigationItem.prototype.set navigationItems):
+        (WI.GroupNavigationItem.prototype.get width):
+        (WI.GroupNavigationItem.prototype.get minimumWidth):
+        (WI.GroupNavigationItem.prototype._updateItems):
+        * UserInterface/Views/HierarchicalPathNavigationItem.js:
+        (WI.HierarchicalPathNavigationItem):
+        (WI.HierarchicalPathNavigationItem.prototype.set components):
+        (WI.HierarchicalPathNavigationItem.prototype.updateLayout):
+        (WI.HierarchicalPathNavigationItem.prototype._updateComponentsIfNeeded):
+        Defer DOM modifications until layout for NavigationItems container classes
+        that change items/components dynamically. This reduces UI flashing in the
+        bar when items/components change by coalescing all DOM updates at the same
+        time; when the NavigationBar does its next layout.
+
+        * UserInterface/Views/NetworkResourceDetailView.css:
+        (.network-resource-detail):
+        (.network-resource-detail .navigation-bar):
+        (.network-resource-detail .item.close > .glyph):
+        (.network-resource-detail .item.close > .glyph:hover):
+        (.network-resource-detail .item.close > .glyph:active):
+        (.network .network-resource-detail .navigation-bar .item.radio.button.text-only):
+        (.network .network-resource-detail .navigation-bar .item.radio.button.text-only.selected):
+        (.network-resource-detail > .content-browser):
+        Styles for the detail view's navigation bar.
+
+        * UserInterface/Views/NetworkResourceDetailView.js: Added.
+        (WI.NetworkResourceDetailView):
+        (WI.NetworkResourceDetailView.prototype.get resource):
+        (WI.NetworkResourceDetailView.prototype.shown):
+        (WI.NetworkResourceDetailView.prototype.hidden):
+        (WI.NetworkResourceDetailView.prototype.dispose):
+        (WI.NetworkResourceDetailView.prototype.initialLayout):
+        (WI.NetworkResourceDetailView.prototype._showPreferredContentView):
+        (WI.NetworkResourceDetailView.prototype._showContentViewForNavigationItem):
+        (WI.NetworkResourceDetailView.prototype._navigationItemSelected):
+        (WI.NetworkResourceDetailView.prototype._handleCloseButton):
+        ContentBrowser with customized navigation bar. This container has a fixed
+        list of ContentViews all relating to the Resource. The detail view has
+        a single delegate method for its close button. Since it maintains a
+        ContentBrowser it needs to expose shown/hidden/dispose logic to ensure
+        proper ContentView lifecycle events.
+
+        * UserInterface/Views/NetworkTableContentView.css:
+        (.showing-detail .table .cell:not(.name)):
+        (.showing-detail .table .resizer:not(:first-of-type)):
+        * UserInterface/Views/NetworkTableContentView.js:
+        (WI.NetworkTableContentView):
+        (WI.NetworkTableContentView.prototype.shown):
+        (WI.NetworkTableContentView.prototype.hidden):
+        (WI.NetworkTableContentView.prototype.closed):
+        (WI.NetworkTableContentView.prototype.reset):
+        (WI.NetworkTableContentView.prototype.networkResourceDetailViewClose):
+        (WI.NetworkTableContentView.prototype.tableSortChanged):
+        (WI.NetworkTableContentView.prototype.tableCellClicked):
+        (WI.NetworkTableContentView.prototype.tableSelectedRowChanged):
+        (WI.NetworkTableContentView.prototype.layout):
+        (WI.NetworkTableContentView.prototype._hideResourceDetailView):
+        (WI.NetworkTableContentView.prototype._showResourceDetailView):
+        (WI.NetworkTableContentView.prototype._positionDetailView):
+        (WI.NetworkTableContentView.prototype._updateFilteredEntries):
+        (WI.NetworkTableContentView.prototype._typeFilterScopeBarSelectionChanged):
+        (WI.NetworkTableContentView.prototype._restoreSelectedRow):
+        (WI.NetworkTableContentView.prototype._tableNameColumnDidChangeWidth):
+        Behavior for selecting a row and showing / hiding the detail view.
+        The detail view is positioned beside the Network Table's "name" column
+        and resizes when that column resizes.
+
+        * UserInterface/Views/Resizer.css:
+        (.resizer):
+        * UserInterface/Views/Table.css:
+        (.table > .resizers):
+        * UserInterface/Views/Table.js:
+        (WI.Table):
+        (WI.Table.prototype.get scrollContainer):
+        (WI.Table.prototype._positionResizerElements):
+        To let clients customize the table a bit, put resizers into their own
+        container and expose the scroll container.
+
+        * UserInterface/Views/Variables.css:
+        (:root):
+        Add a new button hover color, slightly lighter than the existing button active color.
+
+2017-10-04  Joseph Pecoraro  <pecoraro@apple.com>
+
         Web Inspector: Fix Beacon and Ping folderization issues
         https://bugs.webkit.org/show_bug.cgi?id=177885
 
index bfdb284..65697d1 100644 (file)
@@ -198,6 +198,7 @@ localizedStrings["Close"] = "Close";
 localizedStrings["Close %s timeline view"] = "Close %s timeline view";
 localizedStrings["Close Other Tabs"] = "Close Other Tabs";
 localizedStrings["Close Tab"] = "Close Tab";
+localizedStrings["Close detail view"] = "Close detail view";
 localizedStrings["Close resource view"] = "Close resource view";
 localizedStrings["Closed"] = "Closed";
 localizedStrings["Closure Variables"] = "Closure Variables";
@@ -446,6 +447,7 @@ localizedStrings["Grouping Method"] = "Grouping Method";
 localizedStrings["Grow"] = "Grow";
 localizedStrings["HTML Attributes"] = "HTML Attributes";
 localizedStrings["HTTP"] = "HTTP";
+localizedStrings["Headers"] = "Headers";
 localizedStrings["Heading Level"] = "Heading Level";
 localizedStrings["Heap Snapshot Object (%s)"] = "Heap Snapshot Object (%s)";
 localizedStrings["Height"] = "Height";
@@ -666,6 +668,7 @@ localizedStrings["Position Y"] = "Position Y";
 localizedStrings["Prefer indent using:"] = "Prefer indent using:";
 localizedStrings["Pressed"] = "Pressed";
 localizedStrings["Pretty print"] = "Pretty print";
+localizedStrings["Preview"] = "Preview";
 localizedStrings["Primary Key"] = "Primary Key";
 localizedStrings["Primary Key \u2014 %s"] = "Primary Key \u2014 %s";
 localizedStrings["Priority"] = "Priority";
index 3e3473f..88e9de7 100644 (file)
@@ -1880,8 +1880,14 @@ WI._focusConsolePrompt = function(event)
 
 WI._focusedContentBrowser = function()
 {
+    if (this.currentFocusElement) {
+        let contentBrowserElement = this.currentFocusElement.enclosingNodeOrSelfWithClass("content-browser");
+        if (contentBrowserElement && contentBrowserElement.__view && contentBrowserElement.__view instanceof WI.ContentBrowser)
+            return contentBrowserElement.__view;
+    }
+
     if (this.tabBrowser.element.isSelfOrAncestor(this.currentFocusElement) || document.activeElement === document.body) {
-        var tabContentView = this.tabBrowser.selectedTabContentView;
+        let tabContentView = this.tabBrowser.selectedTabContentView;
         if (tabContentView.contentBrowser)
             return tabContentView.contentBrowser;
         return null;
index cabd596..571aa69 100644 (file)
@@ -123,6 +123,7 @@ WI.settings = {
     showScopeChainOnPause: new WI.Setting("show-scope-chain-sidebar", true),
     showImageGrid: new WI.Setting("show-image-grid", false),
     showCanvasPath: new WI.Setting("show-canvas-path", false),
+    selectedNetworkDetailContentViewIdentifier: new WI.Setting("network-detail-content-view-identifier", "preview"),
 
     // Experimental
     experimentalShowCanvasContextsInResources: new WI.Setting("experimental-show-canvas-contexts-in-resources", false),
index c90610b..347cd38 100644 (file)
     <link rel="stylesheet" href="Views/NavigationBar.css">
     <link rel="stylesheet" href="Views/NavigationSidebarPanel.css">
     <link rel="stylesheet" href="Views/NetworkGridContentView.css">
+    <link rel="stylesheet" href="Views/NetworkResourceDetailView.css">
     <link rel="stylesheet" href="Views/NetworkTabContentView.css">
     <link rel="stylesheet" href="Views/NetworkTableContentView.css">
     <link rel="stylesheet" href="Views/NetworkTimelineOverviewGraph.css">
     <script src="Views/MultipleScopeBarItem.js"></script>
     <script src="Views/NavigationBar.js"></script>
     <script src="Views/NetworkGridContentView.js"></script>
+    <script src="Views/NetworkResourceDetailView.js"></script>
     <script src="Views/NetworkTableContentView.js"></script>
     <script src="Views/NetworkTimelineOverviewGraph.js"></script>
     <script src="Views/NetworkTimelineView.js"></script>
index 2f61898..9044373 100644 (file)
@@ -33,6 +33,6 @@
     flex: 1;
 }
 
-.content-browser > .navigation-bar .item {
+.content-browser > .navigation-bar .item {
     height: 100%;
 }
index abca5a2..cc4ab97 100644 (file)
@@ -25,7 +25,7 @@
 
 WI.ContentBrowser = class ContentBrowser extends WI.View
 {
-    constructor(element, delegate, disableBackForward, disableFindBanner, flexibleNavigationItem)
+    constructor(element, delegate, disableBackForward, disableFindBanner, flexibleNavigationItem, contentViewNavigationItemGroup)
     {
         super(element);
 
@@ -83,6 +83,8 @@ WI.ContentBrowser = class ContentBrowser extends WI.View
         this._flexibleNavigationItem = flexibleNavigationItem || new WI.FlexibleSpaceNavigationItem;
         this._navigationBar.addNavigationItem(this._flexibleNavigationItem);
 
+        this._currentContentViewNavigationItemsGroup = contentViewNavigationItemGroup || null;
+
         WI.ContentView.addEventListener(WI.ContentView.Event.SelectionPathComponentsDidChange, this._contentViewSelectionPathComponentDidChange, this);
         WI.ContentView.addEventListener(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this);
         WI.ContentView.addEventListener(WI.ContentView.Event.NumberOfSearchResultsDidChange, this._contentViewNumberOfSearchResultsDidChange, this);
@@ -358,6 +360,9 @@ WI.ContentBrowser = class ContentBrowser extends WI.View
         var selectionPathComponents = contentView ? contentView.selectionPathComponents || [] : [];
         this._contentViewSelectionPathNavigationItem.components = selectionPathComponents;
 
+        if (this._currentContentViewNavigationItemsGroup)
+            return;
+
         if (!selectionPathComponents.length) {
             this._hierarchicalPathNavigationItem.alwaysShowLastPathComponentSeparator = false;
             this._navigationBar.removeNavigationItem(this._contentViewSelectionPathNavigationItem);
@@ -388,6 +393,8 @@ WI.ContentBrowser = class ContentBrowser extends WI.View
         if (!currentContentView) {
             this._removeAllNavigationItems();
             this._currentContentViewNavigationItems = [];
+            if (this._currentContentViewNavigationItemsGroup)
+                this._currentContentViewNavigationItems.push(this._contentViewSelectionPathNavigationItem);
             return;
         }
 
@@ -412,19 +419,25 @@ WI.ContentBrowser = class ContentBrowser extends WI.View
 
         // Keep track of items we'll be adding to the navigation bar.
         let newNavigationItems = [];
+        let shouldInsert = !this._currentContentViewNavigationItemsGroup;
 
         // Go through each of the items of the new content view and add a divider before them.
         currentContentView.navigationItems.forEach(function(navigationItem, index) {
             // Add dividers before items unless it's the first item and not a button.
             if (index !== 0 || navigationItem instanceof WI.ButtonNavigationItem) {
                 let divider = new WI.DividerNavigationItem;
-                navigationBar.insertNavigationItem(divider, insertionIndex++);
+                if (shouldInsert)
+                    navigationBar.insertNavigationItem(divider, insertionIndex++);
                 newNavigationItems.push(divider);
             }
-            navigationBar.insertNavigationItem(navigationItem, insertionIndex++);
+            if (shouldInsert)
+                navigationBar.insertNavigationItem(navigationItem, insertionIndex++);
             newNavigationItems.push(navigationItem);
         });
 
+        if (this._currentContentViewNavigationItemsGroup)
+            this._currentContentViewNavigationItemsGroup.navigationItems = [this._contentViewSelectionPathNavigationItem].concat(newNavigationItems);
+
         // Remember the navigation items we inserted so we can remove them
         // for the next content view.
         this._currentContentViewNavigationItems = newNavigationItems;
@@ -432,9 +445,13 @@ WI.ContentBrowser = class ContentBrowser extends WI.View
 
     _removeAllNavigationItems()
     {
-        for (let navigationItem of this._currentContentViewNavigationItems) {
-            if (navigationItem.parentNavigationBar)
-                navigationItem.parentNavigationBar.removeNavigationItem(navigationItem);
+        if (this._currentContentViewNavigationItemsGroup)
+            this._currentContentViewNavigationItemsGroup.navigationItems = [];
+        else {
+            for (let navigationItem of this._currentContentViewNavigationItems) {
+                if (navigationItem.parentNavigationBar)
+                    navigationItem.parentNavigationBar.removeNavigationItem(navigationItem);
+            }
         }
     }
 
index 6094eaa..34f06f5 100644 (file)
@@ -83,6 +83,10 @@ WI.ContentViewContainer = class ContentViewContainer extends WI.View
         if (!(contentView instanceof WI.ContentView))
             return null;
 
+        // No change.
+        if (contentView === this.currentContentView && !cookie)
+            return contentView;
+
         // ContentViews can be shared between containers. If this content view is
         // not owned by us, it may need to be transferred to this container.
         if (contentView.parentContainer !== this)
index 433db56..034405e 100644 (file)
 :matches(.navigation-bar, .toolbar) .item.flexible-space {
     flex: 1;
 }
+
+:matches(.navigation-bar, .toolbar) .item.flexible-space.align-start > .item {
+    -webkit-margin-end: auto;
+}
+
+:matches(.navigation-bar, .toolbar) .item.flexible-space.align-end > .item {
+    -webkit-margin-start: auto;
+}
index 7d551ea..8252429 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 WI.FlexibleSpaceNavigationItem = class FlexibleSpaceNavigationItem extends WI.NavigationItem
 {
+    constructor(navigationItem, alignItem)
+    {
+        super();
+
+        console.assert(!navigationItem || navigationItem instanceof WI.NavigationItem);
+
+        this._navigationItem = navigationItem || null;
+
+        if (this._navigationItem) {
+            this._navigationItem = navigationItem;
+            this.element.classList.add(alignItem || WI.FlexibleSpaceNavigationItem.Align.Start);
+        }
+    }
+
     // Protected
 
     get additionalClassNames()
     {
         return ["flexible-space"];
     }
+
+    updateLayout(expandOnly)
+    {
+        super.updateLayout(expandOnly);
+
+        if (!this._navigationItem)
+            return;
+
+        if (expandOnly) {
+            let flexibleWidth = this.width;
+            if (!flexibleWidth)
+                return;
+
+            this.element.appendChild(this._navigationItem.element);
+
+            this._navigationItem.updateLayout(true);
+            let itemWidth = this._navigationItem.width;
+
+            let remainingWidth = flexibleWidth - itemWidth;
+            if (remainingWidth <= 0)
+                this.element.removeChild(this._navigationItem.element);
+        }
+    }
+};
+
+WI.FlexibleSpaceNavigationItem.Align = {
+    Start: "align-start",
+    End: "align-end",
 };
index a55e5ca..30771cf 100644 (file)
@@ -27,24 +27,41 @@ WI.GroupNavigationItem = class GroupNavigationItem extends WI.NavigationItem
 {
     constructor(navigationItems)
     {
-        console.assert(Array.isArray(navigationItems));
+        console.assert(!navigationItems || Array.isArray(navigationItems));
 
         super();
 
-        this._navigationItems = navigationItems;
+        this._needsUpdate = false;
 
-        for (let item of this._navigationItems) {
-            console.assert(item instanceof WI.NavigationItem);
-            this.element.appendChild(item.element);
-        }
+        this.navigationItems = navigationItems || [];
     }
 
     // Public
 
-    get navigationItems() { return this._navigationItems; }
+    get navigationItems()
+    {
+        return this._navigationItems;
+    }
+    
+    set navigationItems(items)
+    {
+        this._navigationItems = items;
+
+        // Wait until a layout to update the DOM.
+        this._needsUpdate = true;
+    }
+
+    get width()
+    {
+        this._updateItems();
+
+        return super.width;
+    }
 
     get minimumWidth()
     {
+        this._updateItems();
+
         return this._navigationItems.reduce((total, item) => total + item.minimumWidth, 0);
     }
 
@@ -73,4 +90,21 @@ WI.GroupNavigationItem = class GroupNavigationItem extends WI.NavigationItem
 
         super.didDetach();
     }
+
+    // Private
+
+    _updateItems()
+    {
+        if (!this._needsUpdate)
+            return;
+
+        this._needsUpdate = false;
+
+        this.element.removeChildren();
+
+        for (let item of this._navigationItems) {
+            console.assert(item instanceof WI.NavigationItem);
+            this.element.appendChild(item.element);
+        }
+    }
 }
index 6c2b6b8..d3be3aa 100644 (file)
@@ -29,6 +29,9 @@ WI.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends
     {
         super(identifier);
 
+        this._collapsedComponent = null;
+        this._needsUpdate = false;
+
         this.components = components;
     }
 
@@ -60,20 +63,18 @@ WI.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends
         if (this._components && componentsEqual(this._components, newComponents))
             return;
 
-        for (var i = 0; this._components && i < this._components.length; ++i)
-            this._components[i].removeEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
+        if (this._components) {
+            for (let component of this._components)
+                component.removeEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
+        }
 
-        // Make a shallow copy of the newComponents array using slice.
         this._components = newComponents.slice(0);
 
-        for (var i = 0; i < this._components.length; ++i)
-            this._components[i].addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
-
-        this.element.removeChildren();
-        delete this._collapsedComponent;
+        for (let component of this._components)
+            component.addEventListener(WI.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this);
 
-        for (var i = 0; i < newComponents.length; ++i)
-            this.element.appendChild(newComponents[i].element);
+        // Wait until layout to update the DOM.
+        this._needsUpdate = true;
 
         // Update layout for the so other items can adjust to the extra space (or lack thereof) too.
         if (this.parentNavigationBar)
@@ -100,6 +101,8 @@ WI.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends
 
     updateLayout(expandOnly)
     {
+        this._updateComponentsIfNeeded();
+
         super.updateLayout(expandOnly);
 
         var navigationBar = this.parentNavigationBar;
@@ -108,7 +111,7 @@ WI.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends
 
         if (this._collapsedComponent) {
             this.element.removeChild(this._collapsedComponent.element);
-            delete this._collapsedComponent;
+            this._collapsedComponent = null;
         }
 
         // Expand our components to full width to test if the items can fit at full width.
@@ -244,6 +247,20 @@ WI.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends
 
     // Private
 
+    _updateComponentsIfNeeded()
+    {
+        if (!this._needsUpdate)
+            return;
+
+        this._needsUpdate = false;
+
+        this.element.removeChildren();
+        this._collapsedComponent = null;
+
+        for (let component of this._components)
+            this.element.appendChild(component.element);
+    }
+
     _siblingPathComponentWasSelected(event)
     {
         this.dispatchEventToListeners(WI.HierarchicalPathNavigationItem.Event.PathComponentWasSelected, event.data);
index fb8a27f..c61f033 100644 (file)
@@ -42,6 +42,9 @@ WI.NavigationBar = class NavigationBar extends WI.View
         this.element.addEventListener("keydown", this._keyDown.bind(this), false);
         this.element.addEventListener("mousedown", this._mouseDown.bind(this), false);
 
+        this._mouseMovedEventListener = this._mouseMoved.bind(this);
+        this._mouseUpEventListener = this._mouseUp.bind(this);
+
         this._forceLayout = false;
         this._minimumWidth = NaN;
         this._navigationItems = [];
@@ -301,9 +304,6 @@ WI.NavigationBar = class NavigationBar extends WI.View
 
         this._mouseIsDown = true;
 
-        this._mouseMovedEventListener = this._mouseMoved.bind(this);
-        this._mouseUpEventListener = this._mouseUp.bind(this);
-
         if (typeof this.selectedNavigationItem.dontPreventDefaultOnNavigationBarMouseDown === "function"
             && this.selectedNavigationItem.dontPreventDefaultOnNavigationBarMouseDown()
             && this._previousSelectedNavigationItem === this.selectedNavigationItem)
@@ -363,9 +363,6 @@ WI.NavigationBar = class NavigationBar extends WI.View
         document.removeEventListener("mousemove", this._mouseMovedEventListener, false);
         document.removeEventListener("mouseup", this._mouseUpEventListener, false);
 
-        delete this._mouseMovedEventListener;
-        delete this._mouseUpEventListener;
-
         // Restore the tabIndex so the navigation bar can be in the keyboard tab loop.
         this.element.tabIndex = 0;
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.css b/Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.css
new file mode 100644 (file)
index 0000000..11ec802
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.network-resource-detail {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    /* left or right set by NetworkTableView on display / resize */
+    z-index: 10;
+    background-color: white;
+}
+
+.network-resource-detail .navigation-bar {
+    position: -webkit-sticky;
+    top: 0;
+    z-index: 1;
+}
+
+.network-resource-detail .item.close > .glyph {
+    border-radius: 2px;
+    padding: 2px;
+    background: white;
+}
+
+.network-resource-detail .item.close > .glyph:hover {
+    background-color: var(--button-background-color-hover);
+}
+
+.network-resource-detail .item.close > .glyph:active {
+    background-color: var(--button-background-color-pressed);
+}
+
+.network .network-resource-detail .navigation-bar .item.radio.button.text-only {
+    color: inherit;
+    background-color: inherit;
+}
+
+.network .network-resource-detail .navigation-bar .item.radio.button.text-only.selected {
+    color: var(--selected-background-color);
+    background-color: white;
+}
+
+.network-resource-detail > .content-browser {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js b/Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js
new file mode 100644 (file)
index 0000000..0f46eab
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.NetworkResourceDetailView = class NetworkResourceDetailView extends WI.View
+{
+    constructor(resource, delegate)
+    {
+        super();
+
+        console.assert(resource instanceof WI.Resource);
+
+        this._resource = resource;
+        this._delegate = delegate || null;
+
+        this.element.classList.add("network-resource-detail");
+
+        this._contentBrowser = null;
+        this._headersContentView = null;
+        this._cookiesContentView = null;
+        this._timingContentView = null;
+        this._detailsContentView = null;
+    }
+
+    // Public
+
+    get resource() { return this._resource; }
+
+    shown()
+    {
+        if (!this._contentBrowser)
+            return;
+
+        this._showPreferredContentView();
+        this._contentBrowser.shown();
+    }
+
+    hidden()
+    {
+        this._contentBrowser.hidden();
+    }
+
+    dispose()
+    {
+        this._delegate = null;
+
+        this._contentBrowser.contentViewContainer.closeAllContentViews();
+    }
+
+    // Protected
+
+    initialLayout()
+    {
+        let closeNavigationItem = new WI.ButtonNavigationItem("close", WI.UIString("Close detail view"), "Images/CloseLarge.svg", 16, 16);
+        closeNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCloseButton.bind(this));
+        closeNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.High;
+
+        let contentViewNavigationItemsGroup = new WI.GroupNavigationItem;
+        let contentViewNavigationItemsFlexItem = new WI.FlexibleSpaceNavigationItem(contentViewNavigationItemsGroup, WI.FlexibleSpaceNavigationItem.Align.End);
+        contentViewNavigationItemsFlexItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+
+        const disableBackForward = true;
+        const disableFindBanner = false;
+        this._contentBrowser = new WI.ContentBrowser(null, this, disableBackForward, disableFindBanner, contentViewNavigationItemsFlexItem, contentViewNavigationItemsGroup);
+
+        // Insert all of our custom navigation items at the start of the ContentBrowser's NavigationBar.
+        let index = 0;
+        this._contentBrowser.navigationBar.insertNavigationItem(closeNavigationItem, index++);
+        this._contentBrowser.navigationBar.insertNavigationItem(new WI.FlexibleSpaceNavigationItem, index++);
+        this._contentBrowser.navigationBar.insertNavigationItem(new WI.RadioButtonNavigationItem("preview", WI.UIString("Preview")), index++);
+        this._contentBrowser.navigationBar.insertNavigationItem(new WI.RadioButtonNavigationItem("headers", WI.UIString("Headers")), index++);
+        this._contentBrowser.navigationBar.insertNavigationItem(new WI.RadioButtonNavigationItem("cookies", WI.UIString("Cookies")), index++);
+        this._contentBrowser.navigationBar.insertNavigationItem(new WI.RadioButtonNavigationItem("timing", WI.UIString("Timing")), index++);
+        this._contentBrowser.navigationBar.insertNavigationItem(new WI.RadioButtonNavigationItem("details", WI.UIString("Details")), index++);
+        this._contentBrowser.navigationBar.addEventListener(WI.NavigationBar.Event.NavigationItemSelected, this._navigationItemSelected, this);
+
+        this.addSubview(this._contentBrowser);
+
+        this._showPreferredContentView();
+    }
+
+    // Private
+
+    _showPreferredContentView()
+    {
+        // Restore the preferred navigation item.
+        let firstNavigationItem = null;
+        let defaultIdentifier = WI.settings.selectedNetworkDetailContentViewIdentifier.value;
+        for (let navigationItem of this._contentBrowser.navigationBar.navigationItems) {
+            if (!(navigationItem instanceof WI.RadioButtonNavigationItem))
+                continue;
+
+            if (!firstNavigationItem)
+                firstNavigationItem = navigationItem;
+
+            if (navigationItem.identifier === defaultIdentifier) {
+                this._contentBrowser.navigationBar.selectedNavigationItem = navigationItem;
+                return;
+            }
+        }
+
+        console.assert(firstNavigationItem, "Should have found at least one navigation item above");
+        this._contentBrowser.navigationBar.selectedNavigationItem = firstNavigationItem;
+    }
+
+    _showContentViewForNavigationItem(navigationItem)
+    {
+        switch (navigationItem.identifier) {
+        case "preview":
+            this._contentBrowser.showContentViewForRepresentedObject(this._resource);
+            break;
+        case "headers":
+            // FIXME: Provide a Resource Headers View.
+            if (!this._headersContentView)
+                this._headersContentView = new WI.DebugContentView("Headers");
+            this._contentBrowser.showContentView(this._headersContentView);
+            break;
+        case "cookies":
+            // FIXME: Provide a Resource Cookies View.
+            if (!this._cookiesContentView)
+                this._cookiesContentView = new WI.DebugContentView("Cookies");
+            this._contentBrowser.showContentView(this._cookiesContentView);
+            break;
+        case "timing":
+            // FIXME: Provide a Resource Timing View.
+            if (!this._timingContentView)
+                this._timingContentView = new WI.DebugContentView("Timing");
+            this._contentBrowser.showContentView(this._timingContentView);
+            break;
+        case "details":
+            // FIXME: Provide a Resource Details View.
+            if (!this._detailsContentView)
+                this._detailsContentView = new WI.DebugContentView("Details");
+            this._contentBrowser.showContentView(this._detailsContentView);
+            break;
+        }
+    }
+
+    _navigationItemSelected(event)
+    {
+        let navigationItem = event.target.selectedNavigationItem;
+        if (!(navigationItem instanceof WI.RadioButtonNavigationItem))
+            return;
+
+        this._showContentViewForNavigationItem(navigationItem);
+
+        console.assert(navigationItem.identifier);
+        WI.settings.selectedNetworkDetailContentViewIdentifier.value = navigationItem.identifier;
+    }
+
+    _handleCloseButton(event)
+    {
+        if (this._delegate && this._delegate.networkResourceDetailViewClose)
+            this._delegate.networkResourceDetailViewClose(this);
+    }
+};
index 02e17b7..a41aeb4 100644 (file)
@@ -59,3 +59,11 @@ body[dir=rtl] .content-view.network .table .cell.name > .status {
     width: 14px;
     height: 14px;
 }
+
+.showing-detail .table .cell:not(.name) {
+    display: none;
+}
+
+.showing-detail .table .resizer:not(:first-of-type) {
+    display: none;
+}
index cbf3c40..0e0e485 100644 (file)
@@ -38,7 +38,10 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._table = null;
         this._nameColumnWidthSetting = new WI.Setting("network-table-content-view-name-column-width", 250);
 
-        // FIXME: Resource Detail View.
+        this._selectedResource = null;
+        this._resourceDetailView = null;
+        this._resourceDetailViewMap = new Map;
+
         // FIXME: Network Timeline.
         // FIXME: Filter text field.
         // FIXME: Throttling.
@@ -143,17 +146,34 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
     {
         super.shown();
 
+        if (this._resourceDetailView)
+            this._resourceDetailView.shown();
+
         if (this._table)
             this._table.restoreScrollPosition();
     }
 
+    hidden()
+    {
+        if (this._resourceDetailView)
+            this._resourceDetailView.hidden();
+
+        super.hidden();
+    }
+
     closed()
     {
-        super.closed();
+        this._hideResourceDetailView();
+
+        for (let detailView of this._resourceDetailViewMap.values())
+            detailView.dispose();
+        this._resourceDetailViewMap.clear();
 
         WI.Frame.removeEventListener(null, null, this);
         WI.Resource.removeEventListener(null, null, this);
         WI.timelineManager.persistentNetworkTimeline.removeEventListener(WI.Timeline.Event.RecordAdded, this._networkTimelineRecordAdded, this);
+
+        super.closed();
     }
 
     reset()
@@ -162,12 +182,27 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._filteredEntries = [];
         this._pendingInsertions = [];
 
+        for (let detailView of this._resourceDetailViewMap.values())
+            detailView.dispose();
+        this._resourceDetailViewMap.clear();
+
         if (this._table) {
+            this._hideResourceDetailView();
+            this._selectedResource = null;
             this._table.clearSelectedRow();
             this._table.reloadData();
         }
     }
 
+    // NetworkResourceDetailView delegate
+
+    networkResourceDetailViewClose(resourceDetailView)
+    {
+        this._hideResourceDetailView();
+        this._selectedResource = null;
+        this._table.clearSelectedRow();
+    }
+
     // Table dataSource
 
     tableNumberOfRows(table)
@@ -182,6 +217,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         if (!this._entriesSortComparator)
             return;
 
+        this._hideResourceDetailView();
+
         this._entries = this._entries.sort(this._entriesSortComparator);
         this._updateFilteredEntries();
         this._table.reloadData();
@@ -191,7 +228,10 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     tableCellClicked(table, cell, column, rowIndex, event)
     {
-        // FIXME: Show resource detail view.
+        if (column !== this._nameColumn)
+            return;
+
+        this._table.selectRow(rowIndex);
     }
 
     tableCellContextMenuClicked(table, cell, column, rowIndex, event)
@@ -208,7 +248,18 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     tableSelectedRowChanged(table, rowIndex)
     {
-        // FIXME: Show resource detail view.
+        if (isNaN(rowIndex)) {
+            this._selectedResource = null;
+            this._hideResourceDetailView();
+            return;
+        }
+
+        let entry = this._filteredEntries[rowIndex];
+        if (entry.resource === this._selectedResource)
+            return;
+
+        this._selectedResource = entry.resource;
+        this._showResourceDetailView(this._selectedResource);
     }
 
     tablePopulateCell(table, cell, column, rowIndex)
@@ -521,6 +572,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
     layout()
     {
         this._processPendingEntries();
+        this._positionDetailView();
     }
 
     handleClearShortcut(event)
@@ -577,6 +629,58 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._filteredEntries[rowIndex] = entry;
     }
 
+    _hideResourceDetailView()
+    {
+        if (!this._resourceDetailView)
+            return;
+
+        this.element.classList.remove("showing-detail");
+        this._table.scrollContainer.style.removeProperty("width");
+
+        this.removeSubview(this._resourceDetailView);
+
+        this._resourceDetailView.hidden();
+        this._resourceDetailView = null;
+
+        this._table.resize();
+    }
+
+    _showResourceDetailView(resource)
+    {
+        let oldResourceDetailView = this._resourceDetailView;
+
+        this._resourceDetailView = this._resourceDetailViewMap.get(resource);
+        if (!this._resourceDetailView) {
+            this._resourceDetailView = new WI.NetworkResourceDetailView(resource, this);
+            this._resourceDetailViewMap.set(resource, this._resourceDetailView);
+        }
+
+        if (oldResourceDetailView) {
+            oldResourceDetailView.hidden();
+            this.replaceSubview(oldResourceDetailView, this._resourceDetailView);
+        } else
+            this.addSubview(this._resourceDetailView);
+        this._resourceDetailView.shown();
+
+        this.element.classList.add("showing-detail");
+        this._table.scrollContainer.style.width = this._nameColumn.width + "px";
+
+        // FIXME: It would be nice to avoid this.
+        // Currently the ResourceDetailView is in the heirarchy but has not yet done a layout so we
+        // end up seeing the table behind it. This forces us to layout now instead of after a beat.
+        this.updateLayout();
+    }
+
+    _positionDetailView()
+    {
+        if (!this._resourceDetailView)
+            return;
+
+        let side = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL ? "right" : "left";
+        this._resourceDetailView.element.style[side] = this._nameColumn.width + "px";
+        this._table.scrollContainer.style.width = this._nameColumn.width + "px";
+    }
+
     _resourceCachingDisabledSettingChanged()
     {
         this._disableResourceCacheNavigationItem.activated = WI.resourceCachingDisabledSetting.value;
@@ -753,6 +857,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             this._filteredEntries = this._entries.filter(this._passFilter, this);
         else
             this._filteredEntries = this._entries.slice();
+
+        this._restoreSelectedRow();
     }
 
     _generateTypeFilter()
@@ -790,13 +896,33 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         if (this._areFilterListsIdentical(oldFilter, newFilter))
             return;
 
+        // Even if the selected resource would still be visible, lets close the detail view if a filter changes.
+        this._hideResourceDetailView();
+
         this._activeTypeFilters = newFilter;
         this._updateFilteredEntries();
         this._table.reloadData();
     }
 
+    _restoreSelectedRow()
+    {
+        if (!this._selectedResource)
+            return;
+
+        let rowIndex = this._rowIndexForResource(this._selectedResource);
+        if (rowIndex === -1) {
+            this._selectedResource = null;
+            this._table.clearSelectedRow();
+            return;
+        }
+
+        this._table.selectRow(rowIndex);
+    }
+
     _tableNameColumnDidChangeWidth(event)
     {
         this._nameColumnWidthSetting.value = event.target.width;
+
+        this._positionDetailView();
     }
 };
index e468b0f..f9bbe3b 100644 (file)
@@ -26,6 +26,7 @@
 .resizer {
     position: absolute;
     z-index: var(--z-index-resizer);
+    pointer-events: all;
 }
 
 .resizer.vertical-rule {
index 0bb9a2c..3d84ae9 100644 (file)
@@ -94,6 +94,15 @@ body[dir=rtl] .table > .header > :matches(.sort-ascending, .sort-descending)::af
     overflow-y: scroll;
 }
 
+.table > .resizers {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    pointer-events: none;
+}
+
 .table > .data-container.not-scrollable {
     overflow-y: hidden;
 }
index 458dd64..463bc0b 100644 (file)
@@ -73,6 +73,9 @@ WI.Table = class Table extends WI.View
         this._fillerRow = this._listElement.appendChild(document.createElement("li"));
         this._fillerRow.className = "filler";
 
+        this._resizersElement = this._element.appendChild(document.createElement("div"));
+        this._resizersElement.className = "resizers";
+
         this._cachedRows = new Map;
 
         this._columnSpecs = new Map;
@@ -121,6 +124,7 @@ WI.Table = class Table extends WI.View
     get delegate() { return this._delegate; }
     get rowHeight() { return this._rowHeight; }
     get selectedRow() { return this._selectedRowIndex; }
+    get scrollContainer() { return this._scrollContainerElement; }
 
     get sortOrder()
     {
@@ -986,12 +990,12 @@ WI.Table = class Table extends WI.View
                 do {
                     let resizer = new WI.Resizer(WI.Resizer.RuleOrientation.Vertical, this);
                     this._resizers.push(resizer);
-                    this.element.appendChild(resizer.element);
+                    this._resizersElement.appendChild(resizer.element);
                 } while (this._resizers.length < resizersNeededCount);
             } else {
                 do {
                     let resizer = this._resizers.pop();
-                    this.element.removeChild(resizer.element);
+                    this._resizersElement.removeChild(resizer.element);
                 } while (this._resizers.length > resizersNeededCount);
             }
         }
index b0175aa..d784491 100644 (file)
@@ -50,6 +50,7 @@
     --border-color-dark: hsl(0, 0%, 57%);
 
     --button-background-color: white;
+    --button-background-color-hover: hsl(0, 0%, 88%);
     --button-background-color-pressed: hsl(0, 0%, 85%);
 
     --panel-background-color: hsl(0, 0%, 93%);