Modified: trunk/Source/WebInspectorUI/ChangeLog (286886 => 286887)
--- trunk/Source/WebInspectorUI/ChangeLog 2021-12-11 00:46:51 UTC (rev 286886)
+++ trunk/Source/WebInspectorUI/ChangeLog 2021-12-11 00:47:02 UTC (rev 286887)
@@ -1,3 +1,67 @@
+2021-12-10 BJ Burg <bb...@apple.com>
+
+ Web Inspector: save and restore extension tab positions
+ https://bugs.webkit.org/show_bug.cgi?id=234115
+ <rdar://85560636>
+
+ Reviewed by Devin Rousso and Patrick Angle.
+
+ The existing tab state restoration system works by saving or loading tab positions
+ from persistent storage and saving or restoring each tab's state using one cookie per tab type.
+
+ With extension tabs, it is now possible to have more than one tab per tab type.
+ Additionally, extension tabs can be added at any time via InspectorFrontendAPI.
+ Given these challenges, we need a different system for saving and restoring extension tabs.
+
+ Extension tab restoration is now handled by WI.WebInspectorExtensionController.
+ We consider a tab to be an 'anchor' tab if it is saveable, visible, and not pinnable.
+ In other words, an anchor tab is one of the built-in singleton tabs like Console, Elements, etc.
+
+ When the tab bar item list is modified, for each extension tab, we save the observed
+ 'anchor' tab's type and a distance from that anchor tab's insertion index.
+ Updates to extension tab positions are saved to persistent storage at most every 5 seconds.
+
+ When it is time to place an extension tab with createTabForExtension() or showExtensionTab(),
+ perform the reverse operation of computing an insertion index from a anchorTabType and distanceFromAnchorTab.
+
+ This patch was tested with one extension, multiple extensions, showing/hiding extension tabs,
+ remote inspecting a JSContext, and remote inspecting a WKWebView.
+
+ * UserInterface/Views/TabBar.js:
+ (WI.TabBar.prototype.get visibleTabBarItemsFromLeftToRight): Added.
+
+ * UserInterface/Controllers/WebInspectorExtensionController.js:
+ (WI.WebInspectorExtensionController):
+ (WI.WebInspectorExtensionController.get extensionTabPositionsObjectStoreKey): Added.
+
+ (WI.WebInspectorExtensionController.prototype.registerExtension):
+ (WI.WebInspectorExtensionController.prototype.unregisterExtension):
+ Add and remove WI.TabBar event listeners that notify us of changes to the tab bar.
+
+ (WI.WebInspectorExtensionController.prototype.createTabForExtension): Deleted.
+ (WI.WebInspectorExtensionController.prototype.async createTabForExtension): Renamed.
+ Load saved tab positions from persistent storage if needed. Compute the insertion index for the new tab.
+ This method is already expected to return a promise, so make it `async` to allow using `await`.
+
+ (WI.WebInspectorExtensionController.prototype.showExtensionTab):
+ Compute the insertion index for the new tab.
+
+ (WI.WebInspectorExtensionController.prototype.async _loadExtensionTabPositions):
+ Load saved tab positions from persistent storage, allowing for the case where nothing has been saved yet.
+
+ (WI.WebInspectorExtensionController.prototype.async _saveExtensionTabPositions):
+ Recompute and save tab positions for all extension tabs. Then write to persistent storage
+ at most every 5 seconds using a WI.Debouncer.
+
+ (WI.WebInspectorExtensionController.prototype._insertionIndexForExtensionTab): Added, wrapper method.
+ (WI.WebInspectorExtensionController.prototype._computeIndicesForExtensionTab):
+ Compute the anchorTabType, distanceFromAnchorTab, and insertionIndex for the extension tab.
+ If saving tab positions, pass `options.recomputePositions` to ignore saved positions
+ and recompute these fields based on what is currently visible in the tab bar.
+
+ * UserInterface/Views/WebInspectorExtensionTabContentView.js:
+ (WI.WebInspectorExtensionTabContentView.prototype.get savedTabPositionKey): Added.
+
2021-12-10 Nikita Vasilyev <nvasil...@apple.com>
Web Inspector: Add a swatch for justify-content, justify-items, and justify-self
Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js (286886 => 286887)
--- trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js 2021-12-11 00:46:51 UTC (rev 286886)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/WebInspectorExtensionController.js 2021-12-11 00:47:02 UTC (rev 286887)
@@ -34,9 +34,19 @@
this._tabIDsForExtensionIDMap = new Multimap;
this._nextExtensionTabID = 1;
+ this._extensionTabPositions = null;
+ this._saveTabPositionsDebouncer = null;
+
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
}
+ // Static
+
+ static get extensionTabPositionsObjectStoreKey()
+ {
+ return "extension-tab-positions";
+ }
+
// Public
get registeredExtensionIDs()
@@ -51,6 +61,12 @@
return WI.WebInspectorExtension.ErrorCode.RegistrationFailed;
}
+ if (!this._extensionForExtensionIDMap.size) {
+ WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this);
+ WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this);
+ WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this);
+ }
+
let extension = new WI.WebInspectorExtension(extensionID, extensionBundleIdentifier, displayName);
this._extensionForExtensionIDMap.set(extensionID, extension);
@@ -65,6 +81,12 @@
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
+ if (!this._extensionForExtensionIDMap.size) {
+ WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this);
+ WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this);
+ WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this);
+ }
+
let extensionTabIDsToRemove = this._tabIDsForExtensionIDMap.take(extensionID) || [];
for (let extensionTabID of extensionTabIDsToRemove) {
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID);
@@ -77,7 +99,7 @@
this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionRemoved, {extension});
}
- createTabForExtension(extensionID, tabName, tabIconURL, sourceURL)
+ async createTabForExtension(extensionID, tabName, tabIconURL, sourceURL)
{
let extension = this._extensionForExtensionIDMap.get(extensionID);
if (!extension) {
@@ -90,8 +112,15 @@
this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID);
this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView);
- WI.tabBrowser.addTabForContentView(tabContentView, {suppressAnimations: true});
+ if (!this._extensionTabPositions)
+ await this._loadExtensionTabPositions();
+
+ WI.tabBrowser.addTabForContentView(tabContentView, {
+ suppressAnimations: true,
+ insertionIndex: this._insertionIndexForExtensionTab(tabContentView),
+ });
+
// The calling convention is to return an error string or a result object.
return {"result": extensionTabID};
}
@@ -174,6 +203,7 @@
tabContentView.visible = true;
let success = WI.tabBrowser.showTabForContentView(tabContentView, {
...options,
+ insertionIndex: this._insertionIndexForExtensionTab(tabContentView),
initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI,
});
@@ -284,6 +314,85 @@
// Private
+ async _loadExtensionTabPositions()
+ {
+ let savedTabPositions = await WI.objectStores.general.get(WebInspectorExtensionController.extensionTabPositionsObjectStoreKey);
+ this._extensionTabPositions = savedTabPositions || {};
+ }
+
+ _saveExtensionTabPositions()
+ {
+ if (!this._extensionTabPositions)
+ return;
+
+ this._saveTabPositionsDebouncer ||= new Debouncer(() => {
+ for (let tabBarItem of WI.tabBrowser.tabBar.visibleTabBarItemsFromLeftToRight) {
+ if (!(tabBarItem.representedObject instanceof WI.WebInspectorExtensionTabContentView))
+ continue;
+
+ let {anchorTabType, anchorTabIndex, distanceFromAnchorTab} = this._computeIndicesForExtensionTab(tabBarItem.representedObject, {recomputePositions: true});
+ this._extensionTabPositions[tabBarItem.representedObject.savedTabPositionKey] = {anchorTabType, distanceFromAnchorTab};
+ }
+
+ WI.objectStores.general.put(this._extensionTabPositions, WebInspectorExtensionController.extensionTabPositionsObjectStoreKey);
+ });
+ this._saveTabPositionsDebouncer.delayForTime(5000);
+ }
+
+ _insertionIndexForExtensionTab(tabContentView, options = {})
+ {
+ let {anchorTabType, anchorTabIndex, distanceFromAnchorTab} = this._computeIndicesForExtensionTab(tabContentView, options);
+ return anchorTabIndex + distanceFromAnchorTab + 1;
+ }
+
+ _computeIndicesForExtensionTab(tabContentView, {recomputePositions} = {})
+ {
+ // Note: pinned tabs always appear on the trailing edge, so we can ignore them
+ // for the purposes of computing an `insertionIndex`` for `tabContentView`.
+ let anchorTabIndex = 0;
+ let savedPositions = this._extensionTabPositions[tabContentView.savedTabPositionKey] || {};
+ let anchorTabType = (recomputePositions && savedPositions.anchorTabType) || null;
+ let distanceFromAnchorTab = (recomputePositions && savedPositions.distanceFromAnchorTab) || 0;
+
+ let visibleTabBarItems = WI.tabBrowser.tabBar.visibleTabBarItemsFromLeftToRight;
+ for (let i = 0; i < visibleTabBarItems.length; ++i) {
+ let visibleTab = visibleTabBarItems[i].representedObject;
+ if (!visibleTab)
+ continue;
+
+ if (visibleTab === tabContentView)
+ break;
+
+ if (visibleTab instanceof WI.WebInspectorExtensionTabContentView)
+ continue;
+
+ if (recomputePositions) {
+ anchorTabType = visibleTab.type || null;
+ continue;
+ }
+
+ if (visibleTab.type !== anchorTabType)
+ continue;
+
+ anchorTabIndex = i;
+ break;
+ }
+
+ // Find the count of extension tabs after the anchor tab to compute the real distanceFromAnchorTab.
+ // Adding `distanceFromAnchorTab` to `anchorTabIndex` should not insert the tab after a different anchor tab.
+ for (let i = 1; i < visibleTabBarItems.length - anchorTabIndex; ++i) {
+ if (visibleTabBarItems[anchorTabIndex + i].representedObject?.constructor?.shouldSaveTab?.()) {
+ distanceFromAnchorTab = Number.constrain(distanceFromAnchorTab, 0, Math.max(0, i - 1));
+ return {anchorTabType, anchorTabIndex, distanceFromAnchorTab};
+ }
+ }
+
+ // If the anchor tab is now hidden upon restoring, place the extension at the end.
+ // This could happen if a smaller set of tabs are enabled for the inspection target.
+ anchorTabIndex = visibleTabBarItems.length - 1;
+ return {anchorTabType, anchorTabIndex, distanceFromAnchorTab};
+ }
+
_handleMainResourceDidChange(event)
{
if (!event.target.isMainFrame())