Title: [283859] trunk/Source/WebInspectorUI
Revision
283859
Author
[email protected]
Date
2021-10-08 18:40:57 -0700 (Fri, 08 Oct 2021)

Log 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).

Modified Paths

Diff

Modified: trunk/Source/WebInspectorUI/ChangeLog (283858 => 283859)


--- trunk/Source/WebInspectorUI/ChangeLog	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/ChangeLog	2021-10-09 01:40:57 UTC (rev 283859)
@@ -1,5 +1,81 @@
 2021-10-08  BJ Burg  <[email protected]>
 
+        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).
+
+2021-10-08  BJ Burg  <[email protected]>
+
         [Cocoa] Web Inspector: provide a way for _WKInspectorExtension clients to be notified when the inspected page navigates
         https://bugs.webkit.org/show_bug.cgi?id=231338
         <rdar://71200338>

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Main.js (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Base/Main.js	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Main.js	2021-10-09 01:40:57 UTC (rev 283859)
@@ -672,6 +672,10 @@
         return null;
     }
 
+    console.assert(tabClass !== WI.WebInspectorExtensionTabContentView, "Extension tabs must be created via WebInspectorExtensionController.createTabForExtension().");
+    if (tabClass === WI.WebInspectorExtensionTabContentView)
+        return null;
+
     console.assert(WI.TabContentView.isPrototypeOf(tabClass));
     return new tabClass;
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js	2021-10-09 01:40:57 UTC (rev 283859)
@@ -68,7 +68,7 @@
         let extensionTabIDsToRemove = this._tabIDsForExtensionIDMap.take(extensionID) || [];
         for (let extensionTabID of extensionTabIDsToRemove) {
             let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID);
-            WI.tabBrowser.closeTabForContentView(tabContentView);
+            WI.tabBrowser.closeTabForContentView(tabContentView, {suppressAnimations: true});
         }
 
         this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionRemoved, {extension});
@@ -87,7 +87,7 @@
 
         this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID);
         this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView);
-        WI.tabBrowser.addTabForContentView(tabContentView);
+        WI.tabBrowser.addTabForContentView(tabContentView, {suppressAnimations: true});
 
         // The calling convention is to return an error string or a result object.
         return {extensionTabID};
@@ -168,6 +168,7 @@
             return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
         }
 
+        tabContentView.visible = true;
         let success = WI.tabBrowser.showTabForContentView(tabContentView, {
             initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI,
         });
@@ -178,6 +179,53 @@
         }
     }
 
+    hideExtensionTab(extensionTabID, options = {})
+    {
+        let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
+        if (!tabContentView) {
+            WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
+            return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
+        }
+
+        tabContentView.visible = false;
+        WI.tabBrowser.closeTabForContentView(tabContentView, options);
+
+        console.assert(!tabContentView.visible);
+        console.assert(!tabContentView.isClosed);
+    }
+
+    addContextMenuItemsForClosedExtensionTabs(contextMenu)
+    {
+        contextMenu.appendSeparator();
+
+        for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
+            // If the extension tab has been unchecked in the TabBar context menu, then the tabBarItem
+            // for the extension tab will not be connected to a parent TabBar.
+            let shouldIncludeTab = !tabContentView.visible || !tabContentView.tabBarItem.parentTabBar;
+            if (!shouldIncludeTab)
+                continue;
+
+            contextMenu.appendItem(tabContentView.tabInfo().displayName, () => {
+                this.showExtensionTab(tabContentView.extensionTabID);
+            });
+        }
+    }
+
+    addContextMenuItemsForAllExtensionTabs(contextMenu)
+    {
+        contextMenu.appendSeparator();
+
+        for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
+            let checked = tabContentView.visible || !!tabContentView.tabBarItem.parentTabBar;
+            contextMenu.appendCheckboxItem(tabContentView.tabInfo().displayName, () => {
+                if (!checked)
+                    this.showExtensionTab(tabContentView.extensionTabID);
+                else
+                    this.hideExtensionTab(tabContentView.extensionTabID);
+            }, checked);
+        }
+    }
+
     evaluateScriptInExtensionTab(extensionTabID, scriptSource)
     {
         let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js.orig (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js.orig	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js.orig	2021-10-09 01:40:57 UTC (rev 283859)
@@ -33,6 +33,8 @@
         this._extensionTabContentViewForExtensionTabIDMap = new Map;
         this._tabIDsForExtensionIDMap = new Multimap;
         this._nextExtensionTabID = 1;
+
+        WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
     }
 
     // Public
@@ -66,7 +68,7 @@
         let extensionTabIDsToRemove = this._tabIDsForExtensionIDMap.take(extensionID) || [];
         for (let extensionTabID of extensionTabIDsToRemove) {
             let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID);
-            WI.tabBrowser.closeTabForContentView(tabContentView, {suppressAnimations: true});
+            WI.tabBrowser.closeTabForContentView(tabContentView);
         }
 
         this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionRemoved, {extension});
@@ -85,7 +87,7 @@
 
         this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID);
         this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView);
-        WI.tabBrowser.addTabForContentView(tabContentView, {suppressAnimations: true});
+        WI.tabBrowser.addTabForContentView(tabContentView);
 
         // The calling convention is to return an error string or a result object.
         return {extensionTabID};
@@ -166,7 +168,6 @@
             return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
         }
 
-        tabContentView.visible = true;
         let success = WI.tabBrowser.showTabForContentView(tabContentView, {
             initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI,
         });
@@ -177,53 +178,6 @@
         }
     }
 
-    hideExtensionTab(extensionTabID, options = {})
-    {
-        let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
-        if (!tabContentView) {
-            WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
-            return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
-        }
-
-        tabContentView.visible = false;
-        WI.tabBrowser.closeTabForContentView(tabContentView, options);
-
-        console.assert(!tabContentView.visible);
-        console.assert(!tabContentView.isClosed);
-    }
-
-    addContextMenuItemsForClosedExtensionTabs(contextMenu)
-    {
-        contextMenu.appendSeparator();
-
-        for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
-            // If the extension tab has been unchecked in the TabBar context menu, then the tabBarItem
-            // for the extension tab will not be connected to a parent TabBar.
-            let shouldIncludeTab = !tabContentView.visible || !tabContentView.tabBarItem.parentTabBar;
-            if (!shouldIncludeTab)
-                continue;
-
-            contextMenu.appendItem(tabContentView.tabInfo().displayName, () => {
-                this.showExtensionTab(tabContentView.extensionTabID);
-            });
-        }
-    }
-
-    addContextMenuItemsForAllExtensionTabs(contextMenu)
-    {
-        contextMenu.appendSeparator();
-
-        for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
-            let checked = tabContentView.visible || !!tabContentView.tabBarItem.parentTabBar;
-            contextMenu.appendCheckboxItem(tabContentView.tabInfo().displayName, () => {
-                if (!checked)
-                    this.showExtensionTab(tabContentView.extensionTabID);
-                else
-                    this.hideExtensionTab(tabContentView.extensionTabID);
-            }, checked);
-        }
-    }
-
     evaluateScriptInExtensionTab(extensionTabID, scriptSource)
     {
         let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
@@ -244,6 +198,20 @@
             return {error: error.message};
         }
     }
+
+    // Private
+
+    _handleMainResourceDidChange(event)
+    {
+        if (!event.target.isMainFrame())
+            return;
+
+        // Don't fire the event unless one or more extensions are registered.
+        if (!this._extensionForExtensionIDMap.size)
+            return;
+
+        InspectorFrontendHost.inspectedPageDidNavigate(WI.networkManager.mainFrame.url);
+    }
 };
 
 WI.WebInspectorExtensionController.Event = {

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.css (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.css	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.css	2021-10-09 01:40:57 UTC (rev 283859)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -66,3 +66,7 @@
 .content-view > .message-text-view > .description {
     line-height: 22px;
 }
+
+.content-view.not-visible {
+    display: none;
+}

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js	2021-10-09 01:40:57 UTC (rev 283859)
@@ -38,6 +38,7 @@
 
         this._parentContainer = null;
         this._isClosed = false;
+        this._visible = false;
     }
 
     // Static
@@ -348,6 +349,17 @@
 
     get isClosed() { return this._isClosed; }
 
+    get visible()
+    {
+        return !this.isClosed && this._visible;
+    }
+
+    set visible(value)
+    {
+        this._visible = !!value;
+        this.element.classList.toggle("not-visible", !this._visible);
+    }
+
     get representedObject()
     {
         return this._representedObject;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContentViewContainer.js (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ContentViewContainer.js	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContentViewContainer.js	2021-10-09 01:40:57 UTC (rev 283859)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2013–2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -421,11 +421,12 @@
             return;
         }
 
-        // Deselected extension tabs are still attached to the DOM via `this.element`,
-        // so this is the last chance to actually remove the subview and detach from the DOM.
-        if (contentView.constructor.shouldNotRemoveFromDOMWhenHidden() && contentView.isAttached)
-            this.removeSubview(contentView);
+        // Hidden/non-visible extension tabs must remain attached to the DOM to avoid reloading.
+        if (contentView.constructor.shouldNotRemoveFromDOMWhenHidden() && !contentView.visible)
+            return;
 
+        this.removeSubview(contentView);
+
         console.assert(!contentView.isAttached);
 
         if (!contentView._parentContainer)
@@ -463,7 +464,7 @@
         if (!this.subviews.includes(entry.contentView))
             this.addSubview(entry.contentView);
         else if (entry.contentView.constructor.shouldNotRemoveFromDOMWhenHidden()) {
-            entry.contentView.element.classList.remove("hidden-simulating-dom-detached");
+            entry.contentView.visible = true;
             entry.contentView._didMoveToParent(this);
         }
 
@@ -482,7 +483,7 @@
         entry.prepareToHide();
         if (this.subviews.includes(entry.contentView)) {
             if (entry.contentView.constructor.shouldNotRemoveFromDOMWhenHidden()) {
-                entry.contentView.element.classList.add("hidden-simulating-dom-detached");
+                entry.contentView.visible = false;
                 entry.contentView._didMoveToParent(null);
             } else
                 this.removeSubview(entry.contentView);

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TabBrowser.js (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Views/TabBrowser.js	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TabBrowser.js	2021-10-09 01:40:57 UTC (rev 283859)
@@ -210,8 +210,9 @@
 
         this._tabBar.removeTabBarItem(tabContentView.tabBarItem, options);
 
+        let shouldSaveTab = this.selectedTabContentView?.constructor.shouldSaveTab() || this.selectedTabContentView?.constructor.shouldPinTab();
         console.assert(this._recentTabContentViews.length === this._tabBar.tabCount);
-        console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
+        console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0] || !shouldSaveTab);
 
         return true;
     }
@@ -296,8 +297,9 @@
 
         this._contentViewContainer.closeContentView(tabContentView);
 
+        let shouldSaveTab = this.selectedTabContentView?.constructor.shouldSaveTab() || this.selectedTabContentView?.constructor.shouldPinTab();
         console.assert(this._recentTabContentViews.length === this._tabBar.tabCount);
-        console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0]);
+        console.assert(!this.selectedTabContentView || this.selectedTabContentView === this._recentTabContentViews[0] || !shouldSaveTab);
     }
 
     _handleSidebarPanelSelected(event)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/WebInspectorExtensionTabContentView.js (283858 => 283859)


--- trunk/Source/WebInspectorUI/UserInterface/Views/WebInspectorExtensionTabContentView.js	2021-10-09 01:39:28 UTC (rev 283858)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/WebInspectorExtensionTabContentView.js	2021-10-09 01:40:57 UTC (rev 283859)
@@ -47,6 +47,16 @@
         this._frameContentDidLoad = false;
     }
 
+    // Static
+
+    static shouldSaveTab() { return false; }
+    static shouldNotRemoveFromDOMWhenHidden() { return true; }
+
+    static isTabAllowed()
+    {
+        return InspectorFrontendHost.supportsWebExtensions;
+    }
+
     // Public
 
     get extensionTabID() { return this._extensionTabID; }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to