Changeset 283859 in webkit


Ignore:
Timestamp:
Oct 8, 2021 6:40:57 PM (9 months ago)
Author:
BJ Burg
Message:

Web Inspector: add TabBar context menu support for WI.WebInspectorExtensionTabContentView
https://bugs.webkit.org/show_bug.cgi?id=231181
<rdar://74698241>

Reviewed by Devin Rousso.

The existing TabBar/TabBrowser system relies on the fact that each tab class can
be instantiated once. This is no longer true with extension tabs, as all of them
are instances of the same WI.WebInspectorExtensionTabContentView class.

The new approach builds on bug 230758 by introducing a 'visible' property on ContentView.
This is necessary to mark extension tabs as "attached by not showing" due to the
override of shouldNotRemoveFromDOMWhenHidden().

  • UserInterface/Base/Main.js:

(WI._createTabContentViewForType):
(WI.isNewTabWithTypeAllowed):
List WebInspectorExtensionTabContentView as a known tab class. But do not allow
this tab class to be instantiated directly. Extension tabs are intended to be
created using WebInspectorExtensionController.createTabForExtension().

  • UserInterface/Controllers/WebInspectorExtensionController.js:

(WI.WebInspectorExtensionController.prototype.unregisterExtension):
(WI.WebInspectorExtensionController.prototype.createTabForExtension):
Drive-by, suppress animations when extension tabs are created and destroyed. Otherwise
there is a lot of unnecessary and glitchy animation when multiple extension tabs are
created upon loading Web Inspector the first time.

(WI.WebInspectorExtensionController.prototype.showExtensionTab):
(WI.WebInspectorExtensionController.prototype.hideExtensionTab): Added.
These methods are counterparts. They toggle tabContentView.visible to make the tab
visible and not visible. Note that 'hiding' does not actually close/destroy the
extension tab. Extension tabs are hidden by setting .not-visible ('display:none') and
removing the tab's TabBarItem from the TabBar.

(WI.WebInspectorExtensionController.prototype.addContextMenuItemsForClosedExtensionTabs):
(WI.WebInspectorExtensionController.prototype.addContextMenuItemsForAllExtensionTabs):
Added. TabBar delegates the creation of extension tab context menu items to this class.
The first method only shows hidden extension tabs (for the Reopen Closed Tabs + item).
The second method shows visible and not visible extension tabs and feeds the main context menu.

  • UserInterface/Views/ContentView.css:

(.content-view.not-visible):

  • UserInterface/Views/ContentViewContainer.css:

(.content-view-container > .content-view):
(.content-view-container > .content-view.not-visible): Deleted.
This style class is now managed by ContentView.js. So, move the style declaration.

  • UserInterface/Views/ContentView.js:

(WI.ContentView):
(WI.ContentView.prototype.get visible):
(WI.ContentView.prototype.set visible):
Add a flag so that clients can determine when the content view is not visible and not closed.
This can be true if a subclass overrides shouldNotRemoveFromDOMWhenHidden() to return true.

  • UserInterface/Views/ContentViewContainer.js:

(WI.ContentViewContainer.prototype._disassociateFromContentView):
Fix this code to not detach extension tabs that are hidden.

(WI.ContentViewContainer.prototype._showEntry):
(WI.ContentViewContainer.prototype._hideEntry):
Adopt new setter for ContentView.prototype.hidden.

  • UserInterface/Views/TabBar.js:

(WI.TabBar.prototype._handleAddClosedTabsTabBarItemMouseDown):
Don't add generic context menu items for WebInspectorExtensionTabContentView. Call out
to WebInspectorExtensionController to create the appropriate extension tab context menu items.
(WI.TabBar.prototype._handleTabContainerContextMenu):
(WI.TabBar):

  • UserInterface/Views/WebInspectorExtensionTabContentView.js:

(WI.WebInspectorExtensionTabContentView.isTabAllowed): Drive-by, gate creation of this class
on Web Extensions being enabled (InspectorFrontendHost.supportsWebExtensions).

Location:
trunk/Source/WebInspectorUI
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebInspectorUI/ChangeLog

    r283857 r283859  
     12021-10-08  BJ Burg  <bburg@apple.com>
     2
     3        Web Inspector: add TabBar context menu support for WI.WebInspectorExtensionTabContentView
     4        https://bugs.webkit.org/show_bug.cgi?id=231181
     5        <rdar://74698241>
     6
     7        Reviewed by Devin Rousso.
     8
     9        The existing TabBar/TabBrowser system relies on the fact that each tab class can
     10        be instantiated once. This is no longer true with extension tabs, as all of them
     11        are instances of the same WI.WebInspectorExtensionTabContentView class.
     12
     13        The new approach builds on bug 230758 by introducing a 'visible' property on ContentView.
     14        This is necessary to mark extension tabs as "attached by not showing" due to the
     15        override of shouldNotRemoveFromDOMWhenHidden().
     16
     17        * UserInterface/Base/Main.js:
     18        (WI._createTabContentViewForType):
     19        (WI.isNewTabWithTypeAllowed):
     20        List WebInspectorExtensionTabContentView as a known tab class. But do not allow
     21        this tab class to be instantiated directly. Extension tabs are intended to be
     22        created using WebInspectorExtensionController.createTabForExtension().
     23
     24        * UserInterface/Controllers/WebInspectorExtensionController.js:
     25        (WI.WebInspectorExtensionController.prototype.unregisterExtension):
     26        (WI.WebInspectorExtensionController.prototype.createTabForExtension):
     27        Drive-by, suppress animations when extension tabs are created and destroyed. Otherwise
     28        there is a lot of unnecessary and glitchy animation when multiple extension tabs are
     29        created upon loading Web Inspector the first time.
     30
     31        (WI.WebInspectorExtensionController.prototype.showExtensionTab):
     32        (WI.WebInspectorExtensionController.prototype.hideExtensionTab): Added.
     33        These methods are counterparts. They toggle tabContentView.visible to make the tab
     34        visible and not visible. Note that 'hiding' does not actually close/destroy the
     35        extension tab. Extension tabs are hidden by setting .not-visible ('display:none') and
     36        removing the tab's TabBarItem from the TabBar.
     37
     38        (WI.WebInspectorExtensionController.prototype.addContextMenuItemsForClosedExtensionTabs):
     39        (WI.WebInspectorExtensionController.prototype.addContextMenuItemsForAllExtensionTabs):
     40        Added. TabBar delegates the creation of extension tab context menu items to this class.
     41        The first method only shows hidden extension tabs (for the Reopen Closed Tabs + item).
     42        The second method shows visible and not visible extension tabs and feeds the main context menu.
     43
     44        * UserInterface/Views/ContentView.css:
     45        (.content-view.not-visible):
     46        * UserInterface/Views/ContentViewContainer.css:
     47        (.content-view-container > .content-view):
     48        (.content-view-container > .content-view.not-visible): Deleted.
     49        This style class is now managed by ContentView.js. So, move the style declaration.
     50
     51        * UserInterface/Views/ContentView.js:
     52        (WI.ContentView):
     53        (WI.ContentView.prototype.get visible):
     54        (WI.ContentView.prototype.set visible):
     55        Add a flag so that clients can determine when the content view is not visible and not closed.
     56        This can be true if a subclass overrides shouldNotRemoveFromDOMWhenHidden() to return true.
     57
     58        * UserInterface/Views/ContentViewContainer.js:
     59        (WI.ContentViewContainer.prototype._disassociateFromContentView):
     60        Fix this code to not detach extension tabs that are hidden.
     61
     62        (WI.ContentViewContainer.prototype._showEntry):
     63        (WI.ContentViewContainer.prototype._hideEntry):
     64        Adopt new setter for ContentView.prototype.hidden.
     65
     66        * UserInterface/Views/TabBar.js:
     67        (WI.TabBar.prototype._handleAddClosedTabsTabBarItemMouseDown):
     68        Don't add generic context menu items for WebInspectorExtensionTabContentView. Call out
     69        to WebInspectorExtensionController to create the appropriate extension tab context menu items.
     70        (WI.TabBar.prototype._handleTabContainerContextMenu):
     71        (WI.TabBar):
     72
     73        * UserInterface/Views/WebInspectorExtensionTabContentView.js:
     74        (WI.WebInspectorExtensionTabContentView.isTabAllowed): Drive-by, gate creation of this class
     75        on Web Extensions being enabled (InspectorFrontendHost.supportsWebExtensions).
     76
    1772021-10-08  BJ Burg  <bburg@apple.com>
    278
  • trunk/Source/WebInspectorUI/UserInterface/Base/Main.js

    r281182 r283859  
    673673    }
    674674
     675    console.assert(tabClass !== WI.WebInspectorExtensionTabContentView, "Extension tabs must be created via WebInspectorExtensionController.createTabForExtension().");
     676    if (tabClass === WI.WebInspectorExtensionTabContentView)
     677        return null;
     678
    675679    console.assert(WI.TabContentView.isPrototypeOf(tabClass));
    676680    return new tabClass;
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js

    r283857 r283859  
    6969        for (let extensionTabID of extensionTabIDsToRemove) {
    7070            let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID);
    71             WI.tabBrowser.closeTabForContentView(tabContentView);
     71            WI.tabBrowser.closeTabForContentView(tabContentView, {suppressAnimations: true});
    7272        }
    7373
     
    8888        this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID);
    8989        this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView);
    90         WI.tabBrowser.addTabForContentView(tabContentView);
     90        WI.tabBrowser.addTabForContentView(tabContentView, {suppressAnimations: true});
    9191
    9292        // The calling convention is to return an error string or a result object.
     
    169169        }
    170170
     171        tabContentView.visible = true;
    171172        let success = WI.tabBrowser.showTabForContentView(tabContentView, {
    172173            initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI,
     
    176177            WI.reportInternalError("Unable to show extension tab with extensionTabID: " + extensionTabID);
    177178            return WI.WebInspectorExtension.ErrorCode.InternalError;
     179        }
     180    }
     181
     182    hideExtensionTab(extensionTabID, options = {})
     183    {
     184        let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
     185        if (!tabContentView) {
     186            WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
     187            return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
     188        }
     189
     190        tabContentView.visible = false;
     191        WI.tabBrowser.closeTabForContentView(tabContentView, options);
     192
     193        console.assert(!tabContentView.visible);
     194        console.assert(!tabContentView.isClosed);
     195    }
     196
     197    addContextMenuItemsForClosedExtensionTabs(contextMenu)
     198    {
     199        contextMenu.appendSeparator();
     200
     201        for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
     202            // If the extension tab has been unchecked in the TabBar context menu, then the tabBarItem
     203            // for the extension tab will not be connected to a parent TabBar.
     204            let shouldIncludeTab = !tabContentView.visible || !tabContentView.tabBarItem.parentTabBar;
     205            if (!shouldIncludeTab)
     206                continue;
     207
     208            contextMenu.appendItem(tabContentView.tabInfo().displayName, () => {
     209                this.showExtensionTab(tabContentView.extensionTabID);
     210            });
     211        }
     212    }
     213
     214    addContextMenuItemsForAllExtensionTabs(contextMenu)
     215    {
     216        contextMenu.appendSeparator();
     217
     218        for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
     219            let checked = tabContentView.visible || !!tabContentView.tabBarItem.parentTabBar;
     220            contextMenu.appendCheckboxItem(tabContentView.tabInfo().displayName, () => {
     221                if (!checked)
     222                    this.showExtensionTab(tabContentView.extensionTabID);
     223                else
     224                    this.hideExtensionTab(tabContentView.extensionTabID);
     225            }, checked);
    178226        }
    179227    }
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js.orig

    r283857 r283859  
    3434        this._tabIDsForExtensionIDMap = new Multimap;
    3535        this._nextExtensionTabID = 1;
     36
     37        WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
    3638    }
    3739
     
    6769        for (let extensionTabID of extensionTabIDsToRemove) {
    6870            let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID);
    69             WI.tabBrowser.closeTabForContentView(tabContentView, {suppressAnimations: true});
     71            WI.tabBrowser.closeTabForContentView(tabContentView);
    7072        }
    7173
     
    8688        this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID);
    8789        this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView);
    88         WI.tabBrowser.addTabForContentView(tabContentView, {suppressAnimations: true});
     90        WI.tabBrowser.addTabForContentView(tabContentView);
    8991
    9092        // The calling convention is to return an error string or a result object.
     
    167169        }
    168170
    169         tabContentView.visible = true;
    170171        let success = WI.tabBrowser.showTabForContentView(tabContentView, {
    171172            initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI,
     
    175176            WI.reportInternalError("Unable to show extension tab with extensionTabID: " + extensionTabID);
    176177            return WI.WebInspectorExtension.ErrorCode.InternalError;
    177         }
    178     }
    179 
    180     hideExtensionTab(extensionTabID, options = {})
    181     {
    182         let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
    183         if (!tabContentView) {
    184             WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
    185             return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
    186         }
    187 
    188         tabContentView.visible = false;
    189         WI.tabBrowser.closeTabForContentView(tabContentView, options);
    190 
    191         console.assert(!tabContentView.visible);
    192         console.assert(!tabContentView.isClosed);
    193     }
    194 
    195     addContextMenuItemsForClosedExtensionTabs(contextMenu)
    196     {
    197         contextMenu.appendSeparator();
    198 
    199         for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
    200             // If the extension tab has been unchecked in the TabBar context menu, then the tabBarItem
    201             // for the extension tab will not be connected to a parent TabBar.
    202             let shouldIncludeTab = !tabContentView.visible || !tabContentView.tabBarItem.parentTabBar;
    203             if (!shouldIncludeTab)
    204                 continue;
    205 
    206             contextMenu.appendItem(tabContentView.tabInfo().displayName, () => {
    207                 this.showExtensionTab(tabContentView.extensionTabID);
    208             });
    209         }
    210     }
    211 
    212     addContextMenuItemsForAllExtensionTabs(contextMenu)
    213     {
    214         contextMenu.appendSeparator();
    215 
    216         for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
    217             let checked = tabContentView.visible || !!tabContentView.tabBarItem.parentTabBar;
    218             contextMenu.appendCheckboxItem(tabContentView.tabInfo().displayName, () => {
    219                 if (!checked)
    220                     this.showExtensionTab(tabContentView.extensionTabID);
    221                 else
    222                     this.hideExtensionTab(tabContentView.extensionTabID);
    223             }, checked);
    224178        }
    225179    }
     
    245199        }
    246200    }
     201
     202    // Private
     203
     204    _handleMainResourceDidChange(event)
     205    {
     206        if (!event.target.isMainFrame())
     207            return;
     208
     209        // Don't fire the event unless one or more extensions are registered.
     210        if (!this._extensionForExtensionIDMap.size)
     211            return;
     212
     213        InspectorFrontendHost.inspectedPageDidNavigate(WI.networkManager.mainFrame.url);
     214    }
    247215};
    248216
  • trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.css

    r226076 r283859  
    11/*
    2  * Copyright (C) 2013 Apple Inc. All rights reserved.
     2 * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    6767    line-height: 22px;
    6868}
     69
     70.content-view.not-visible {
     71    display: none;
     72}
  • trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js

    r283728 r283859  
    3939        this._parentContainer = null;
    4040        this._isClosed = false;
     41        this._visible = false;
    4142    }
    4243
     
    349350    get isClosed() { return this._isClosed; }
    350351
     352    get visible()
     353    {
     354        return !this.isClosed && this._visible;
     355    }
     356
     357    set visible(value)
     358    {
     359        this._visible = !!value;
     360        this.element.classList.toggle("not-visible", !this._visible);
     361    }
     362
    351363    get representedObject()
    352364    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/ContentViewContainer.js

    r283728 r283859  
    11/*
    2  * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
     2 * Copyright (C) 2013–2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    422422        }
    423423
    424         // Deselected extension tabs are still attached to the DOM via `this.element`,
    425         // so this is the last chance to actually remove the subview and detach from the DOM.
    426         if (contentView.constructor.shouldNotRemoveFromDOMWhenHidden() && contentView.isAttached)
    427             this.removeSubview(contentView);
     424        // Hidden/non-visible extension tabs must remain attached to the DOM to avoid reloading.
     425        if (contentView.constructor.shouldNotRemoveFromDOMWhenHidden() && !contentView.visible)
     426            return;
     427
     428        this.removeSubview(contentView);
    428429
    429430        console.assert(!contentView.isAttached);
     
    464465            this.addSubview(entry.contentView);
    465466        else if (entry.contentView.constructor.shouldNotRemoveFromDOMWhenHidden()) {
    466             entry.contentView.element.classList.remove("hidden-simulating-dom-detached");
     467            entry.contentView.visible = true;
    467468            entry.contentView._didMoveToParent(this);
    468469        }
     
    483484        if (this.subviews.includes(entry.contentView)) {
    484485            if (entry.contentView.constructor.shouldNotRemoveFromDOMWhenHidden()) {
    485                 entry.contentView.element.classList.add("hidden-simulating-dom-detached");
     486                entry.contentView.visible = false;
    486487                entry.contentView._didMoveToParent(null);
    487488            } else
  • trunk/Source/WebInspectorUI/UserInterface/Views/TabBrowser.js

    r283728 r283859  
    211211        this._tabBar.removeTabBarItem(tabContentView.tabBarItem, options);
    212212
     213        let shouldSaveTab = this.selectedTabContentView?.constructor.shouldSaveTab() || this.selectedTabContentView?.constructor.shouldPinTab();
    213214        console.assert(this._recentTabContentViews.length === this._tabBar.tabCount);
    214         console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
     215        console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0] || !shouldSaveTab);
    215216
    216217        return true;
     
    297298        this._contentViewContainer.closeContentView(tabContentView);
    298299
     300        let shouldSaveTab = this.selectedTabContentView?.constructor.shouldSaveTab() || this.selectedTabContentView?.constructor.shouldPinTab();
    299301        console.assert(this._recentTabContentViews.length === this._tabBar.tabCount);
    300         console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
     302        console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0] || !shouldSaveTab);
    301303    }
    302304
  • trunk/Source/WebInspectorUI/UserInterface/Views/WebInspectorExtensionTabContentView.js

    r283728 r283859  
    4646
    4747        this._frameContentDidLoad = false;
     48    }
     49
     50    // Static
     51
     52    static shouldSaveTab() { return false; }
     53    static shouldNotRemoveFromDOMWhenHidden() { return true; }
     54
     55    static isTabAllowed()
     56    {
     57        return InspectorFrontendHost.supportsWebExtensions;
    4858    }
    4959
Note: See TracChangeset for help on using the changeset viewer.