tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit b6b652290eba0431c4a0e4f2ff0ad2e8a7f64622
parent 5a79798042f2fc1839f5f4ea6c16da601d40d644
Author: Sarah Clements <sclements@mozilla.com>
Date:   Mon, 24 Nov 2025 11:55:17 +0000

Bug 1998199 - Create dragAndDropElements that account for splitview containers r=nsharpley,tabbrowser-reviewers,dao

* Create new dragAndDropElements array that includes splitview container but not its children
* Replace use of ariaFocusableItems in drag and drop modules and tabbrowser.js
* Update insertTabAtIndex to prevent tabs from inserted into a splitview container

Differential Revision: https://phabricator.services.mozilla.com/D271247

Diffstat:
Mbrowser/components/tabbrowser/content/drag-and-drop.js | 47+++++++++++++++++++++++++----------------------
Mbrowser/components/tabbrowser/content/tab-stacking.js | 46++++++++++++++++++++++++----------------------
Mbrowser/components/tabbrowser/content/tab.js | 2+-
Mbrowser/components/tabbrowser/content/tabbrowser.js | 27+++++++++++++++++++++------
Mbrowser/components/tabbrowser/content/tabs.js | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mbrowser/components/tabbrowser/content/tabsplitview.js | 5+++++
6 files changed, 141 insertions(+), 63 deletions(-)

diff --git a/browser/components/tabbrowser/content/drag-and-drop.js b/browser/components/tabbrowser/content/drag-and-drop.js @@ -8,19 +8,22 @@ { const isTab = element => gBrowser.isTab(element); const isTabGroupLabel = element => gBrowser.isTabGroupLabel(element); + const isSplitViewWrapper = element => gBrowser.isSplitViewWrapper(element); /** - * The elements in the tab strip from `this.ariaFocusableItems` that contain + * The elements in the tab strip from `this.dragAndDropElements` that contain * logical information are: * * - <tab> (.tabbrowser-tab) * - <tab-group> label element (.tab-group-label) + * - <tab-split-view-wrapper> * * The elements in the tab strip that contain the space inside of the <tabs> * element are: * * - <tab> (.tabbrowser-tab) * - <tab-group> label element wrapper (.tab-group-label-container) + * - <tab-split-view-wrapper> * * When working with tab strip items, if you need logical information, you * can get it directly, e.g. `element.elementIndex` or `element._tPos`. If @@ -32,7 +35,7 @@ * @returns {MozTabbrowserTab|vbox} */ const elementToMove = element => { - if (isTab(element)) { + if (isTab(element) || isSplitViewWrapper(element)) { return element; } if (isTabGroupLabel(element)) { @@ -195,7 +198,7 @@ newMargin = pixelsToScroll > 0 ? maxMargin : minMargin; } else { let newIndex = this._getDropIndex(event); - let children = this._tabbrowserTabs.ariaFocusableItems; + let children = this._tabbrowserTabs.dragAndDropElements; if (newIndex == children.length) { let itemRect = children.at(-1).getBoundingClientRect(); if (this._tabbrowserTabs.verticalMode) { @@ -270,7 +273,7 @@ let duplicatedDraggedTab; let duplicatedTabs = []; let dropTarget = - this._tabbrowserTabs.ariaFocusableItems[this._getDropIndex(event)]; + this._tabbrowserTabs.dragAndDropElements[this._getDropIndex(event)]; for (let tab of movingTabs) { let duplicatedTab = gBrowser.duplicateTab(tab); duplicatedTabs.push(duplicatedTab); @@ -307,7 +310,7 @@ newTranslateY -= tabHeight; } } else { - let tabs = this._tabbrowserTabs.ariaFocusableItems.slice( + let tabs = this._tabbrowserTabs.dragAndDropElements.slice( isPinned ? 0 : numPinned, isPinned ? numPinned : undefined ); @@ -532,8 +535,8 @@ // Restore tab selection gBrowser.addRangeToMultiSelectedTabs( - this._tabbrowserTabs.ariaFocusableItems[dropIndex], - this._tabbrowserTabs.ariaFocusableItems[newIndex - 1] + this._tabbrowserTabs.dragAndDropElements[dropIndex], + this._tabbrowserTabs.dragAndDropElements[newIndex - 1] ); } else { // Pass true to disallow dropping javascript: or data: urls @@ -579,7 +582,7 @@ } } - let nextItem = this._tabbrowserTabs.ariaFocusableItems[newIndex]; + let nextItem = this._tabbrowserTabs.dragAndDropElements[newIndex]; let tabGroup = isTab(nextItem) && nextItem.group; gBrowser.loadTabs(urls, { inBackground, @@ -786,7 +789,7 @@ _getDropIndex(event) { let item = this._getDragTarget(event); if (!item) { - return this._tabbrowserTabs.ariaFocusableItems.length; + return this._tabbrowserTabs.dragAndDropElements.length; } let isBeforeMiddle; @@ -1175,7 +1178,7 @@ } let isPinned = tab.pinned; let numPinned = gBrowser.pinnedTabCount; - let allTabs = this._tabbrowserTabs.ariaFocusableItems; + let dragAndDropElements = this._tabbrowserTabs.dragAndDropElements; let isGrid = this._isContainerVerticalPinnedGrid(tab); let periphery = document.getElementById( "tabbrowser-arrowscrollbox-periphery" @@ -1219,7 +1222,7 @@ /** @type {Map<MozTabbrowserTab, DOMRect>} */ const pinnedTabsOrigBounds = new Map(); - for (let t of allTabs) { + for (let t of dragAndDropElements) { t = elementToMove(t); let tabRect = window.windowUtils.getBoundsWithoutFlushing(t); @@ -1387,7 +1390,7 @@ // Update tabs in the same container as the dragged tabs so as not // to fill the space when the dragged tabs become absolute - for (let t of allTabs) { + for (let t of dragAndDropElements) { let tabIsPinned = t.pinned; t = elementToMove(t); if (!t.hasAttribute("dragtarget")) { @@ -1454,7 +1457,7 @@ let addAnimationData = (movingTab, isBeforeSelectedTab) => { let lowerIndex = Math.min(movingTab.elementIndex, draggedTabIndex) + 1; let higherIndex = Math.max(movingTab.elementIndex, draggedTabIndex); - let middleItems = this._tabbrowserTabs.ariaFocusableItems + let middleItems = this._tabbrowserTabs.dragAndDropElements .slice(lowerIndex, higherIndex) .filter(item => !item.multiselected); if (!middleItems.length) { @@ -1551,7 +1554,7 @@ } // Slide the relevant tabs to their new position. - for (let item of this._tabbrowserTabs.ariaFocusableItems) { + for (let item of this._tabbrowserTabs.dragAndDropElements) { item = elementToMove(item); if (item._moveTogetherSelectedTabsData?.translatePos) { let translatePos = @@ -1596,7 +1599,7 @@ gBrowser.moveTabAfter(selectedTabs[i], tab); } - for (let item of this._tabbrowserTabs.ariaFocusableItems) { + for (let item of this._tabbrowserTabs.dragAndDropElements) { item = elementToMove(item); item.style.transform = ""; item.removeAttribute("multiselected-move-together"); @@ -1835,8 +1838,8 @@ let isPinned = draggedTab.pinned; let numPinned = gBrowser.pinnedTabCount; - let allTabs = this._tabbrowserTabs.ariaFocusableItems; - let tabs = allTabs.slice( + let dragAndDropElements = this._tabbrowserTabs.dragAndDropElements; + let tabs = dragAndDropElements.slice( isPinned ? 0 : numPinned, isPinned ? numPinned : undefined ); @@ -2092,14 +2095,14 @@ // logically drop the dragged element(s). // // It's tempting to set `dropElement` to - // `this.ariaFocusableItems.at(oldDropElementIndex)`, and that is correct + // `this.dragAndDropElements.at(oldDropElementIndex)`, and that is correct // for most cases, but there are edge cases: // // 1) the drop element index range needs to be one larger than the number of // items that can move in the tab strip. The simplest example is when all // tabs are ungrouped and unpinned: for 5 tabs, the drop element index needs // to be able to go from 0 (become the first tab) to 5 (become the last tab). - // `this.ariaFocusableItems.at(5)` would be `undefined` when dragging to the + // `this.dragAndDropElements.at(5)` would be `undefined` when dragging to the // end of the tab strip. In this specific case, it works to fall back to // setting the drop element to the last tab. // @@ -2128,7 +2131,7 @@ maxElementIndexForDropElement ); let oldDropElementCandidate = - this._tabbrowserTabs.ariaFocusableItems.at(index); + this._tabbrowserTabs.dragAndDropElements.at(index); if (!movingTabsSet.has(oldDropElementCandidate)) { dropElement = oldDropElementCandidate; } @@ -2218,7 +2221,7 @@ : dropElement.elementIndex < numPinned; if (isOutOfBounds) { // Drop after last pinned tab - dropElement = this._tabbrowserTabs.ariaFocusableItems[numPinned - 1]; + dropElement = this._tabbrowserTabs.dragAndDropElements[numPinned - 1]; dropBefore = false; } } @@ -2423,7 +2426,7 @@ this.#setMovingTabMode(false); - for (let item of this._tabbrowserTabs.ariaFocusableItems) { + for (let item of this._tabbrowserTabs.dragAndDropElements) { this._resetGroupTarget(item); item = elementToMove(item); item.style.transform = ""; diff --git a/browser/components/tabbrowser/content/tab-stacking.js b/browser/components/tabbrowser/content/tab-stacking.js @@ -8,19 +8,21 @@ { const isTab = element => gBrowser.isTab(element); const isTabGroupLabel = element => gBrowser.isTabGroupLabel(element); - + const isSplitViewWrapper = element => gBrowser.isSplitViewWrapper(element); /** - * The elements in the tab strip from `this.ariaFocusableItems` that contain + * The elements in the tab strip from `this.dragAndDropElements` that contain * logical information are: * * - <tab> (.tabbrowser-tab) * - <tab-group> label element (.tab-group-label) + * - <tab-split-view-wrapper> * * The elements in the tab strip that contain the space inside of the <tabs> * element are: * * - <tab> (.tabbrowser-tab) * - <tab-group> label element wrapper (.tab-group-label-container) + * - <tab-split-view-wrapper> * * When working with tab strip items, if you need logical information, you * can get it directly, e.g. `element.elementIndex` or `element._tPos`. If @@ -32,7 +34,7 @@ * @returns {MozTabbrowserTab|vbox} */ const elementToMove = element => { - if (isTab(element)) { + if (isTab(element) || isSplitViewWrapper(element)) { return element; } if (isTabGroupLabel(element)) { @@ -87,7 +89,7 @@ let duplicatedDraggedTab; let duplicatedTabs = []; let dropTarget = - this._tabbrowserTabs.ariaFocusableItems[this._getDropIndex(event)]; + this._tabbrowserTabs.dragAndDropElements[this._getDropIndex(event)]; for (let tab of movingTabs) { let duplicatedTab = gBrowser.duplicateTab(tab); duplicatedTabs.push(duplicatedTab); @@ -110,7 +112,7 @@ let newTranslateY = oldTranslateY - translateOffsetY; let isPinned = draggedTab.pinned; let numPinned = gBrowser.pinnedTabCount; - let tabs = this._tabbrowserTabs.ariaFocusableItems.slice( + let tabs = this._tabbrowserTabs.dragAndDropElements.slice( isPinned ? 0 : numPinned, isPinned ? numPinned : undefined ); @@ -356,8 +358,8 @@ // Restore tab selection gBrowser.addRangeToMultiSelectedTabs( - this._tabbrowserTabs.ariaFocusableItems[dropIndex], - this._tabbrowserTabs.ariaFocusableItems[newIndex - 1] + this._tabbrowserTabs.dragAndDropElements[dropIndex], + this._tabbrowserTabs.dragAndDropElements[newIndex - 1] ); } else { // Pass true to disallow dropping javascript: or data: urls @@ -403,7 +405,7 @@ } } - let nextItem = this._tabbrowserTabs.ariaFocusableItems[newIndex]; + let nextItem = this._tabbrowserTabs.dragAndDropElements[newIndex]; let tabGroup = isTab(nextItem) && nextItem.group; gBrowser.loadTabs(urls, { inBackground, @@ -419,7 +421,7 @@ })(); } - for (let tab of this._tabbrowserTabs.ariaFocusableItems) { + for (let tab of this._tabbrowserTabs.dragAndDropElements) { delete tab.currentIndex; } @@ -494,7 +496,7 @@ // selected tabs have moved together. These values make the math in _animateTabMove and // _animateExpandedPinnedTabMove possible and less prone to edge cases when dragging // multiple tabs. - for (let unmovingTab of this._tabbrowserTabs.ariaFocusableItems) { + for (let unmovingTab of this._tabbrowserTabs.dragAndDropElements) { if (unmovingTab.multiselected) { unmovingTab.currentIndex = tab.elementIndex; // Skip because this multiselected tab should @@ -539,7 +541,7 @@ // translation needed for the background tab with the new index to move there. let unmovingTabRect = unmovingTab.getBoundingClientRect(); let oldTabRect = - this._tabbrowserTabs.ariaFocusableItems[ + this._tabbrowserTabs.dragAndDropElements[ newIndex ].getBoundingClientRect(); unmovingTab._moveTogetherSelectedTabsData.translateX = @@ -571,7 +573,7 @@ // Slide the relevant tabs to their new position. // non-moving tabs adjust for RTL - for (let item of this._tabbrowserTabs.ariaFocusableItems) { + for (let item of this._tabbrowserTabs.dragAndDropElements) { if ( !tab._dragData.movingTabsSet.has(item) && (item._moveTogetherSelectedTabsData?.translateX || @@ -625,7 +627,7 @@ gBrowser.moveTabAfter(selectedTabs[i], tab); } - for (let item of this._tabbrowserTabs.ariaFocusableItems) { + for (let item of this._tabbrowserTabs.dragAndDropElements) { delete item._moveTogetherSelectedTabsData; item = elementToMove(item); item.style.transform = ""; @@ -647,7 +649,7 @@ return; } let isPinned = tab.pinned; - let allTabs = this._tabbrowserTabs.ariaFocusableItems; + let dragAndDropElements = this._tabbrowserTabs.dragAndDropElements; let isGrid = this._isContainerVerticalPinnedGrid(tab); let periphery = document.getElementById( "tabbrowser-arrowscrollbox-periphery" @@ -692,7 +694,7 @@ const tabsOrigBounds = new Map(); - for (let t of allTabs) { + for (let t of dragAndDropElements) { t = elementToMove(t); let tabRect = window.windowUtils.getBoundsWithoutFlushing(t); @@ -818,7 +820,7 @@ // Update tabs in the same container as the dragged tabs so as not // to fill the space when the dragged tabs become absolute - for (let t of allTabs) { + for (let t of dragAndDropElements) { let tabIsPinned = t.pinned; t = elementToMove(t); if (!t.hasAttribute("dragtarget")) { @@ -869,8 +871,8 @@ let isPinned = draggedTab.pinned; let numPinned = gBrowser.pinnedTabCount; - let allTabs = this._tabbrowserTabs.ariaFocusableItems; - let tabs = allTabs.slice( + let dragAndDropElements = this._tabbrowserTabs.dragAndDropElements; + let tabs = dragAndDropElements.slice( isPinned ? 0 : numPinned, isPinned ? numPinned : undefined ); @@ -1127,14 +1129,14 @@ // logically drop the dragged element(s). // // It's tempting to set `dropElement` to - // `this.ariaFocusableItems.at(oldDropElementIndex)`, and that is correct + // `this.dragAndDropElements.at(oldDropElementIndex)`, and that is correct // for most cases, but there are edge cases: // // 1) the drop element index range needs to be one larger than the number of // items that can move in the tab strip. The simplest example is when all // tabs are ungrouped and unpinned: for 5 tabs, the drop element index needs // to be able to go from 0 (become the first tab) to 5 (become the last tab). - // `this.ariaFocusableItems.at(5)` would be `undefined` when dragging to the + // `this.dragAndDropElements.at(5)` would be `undefined` when dragging to the // end of the tab strip. In this specific case, it works to fall back to // setting the drop element to the last tab. // @@ -1163,7 +1165,7 @@ oldDropElementIndex, maxElementIndexForDropElement ); - let oldDropElementCandidate = this._tabbrowserTabs.ariaFocusableItems + let oldDropElementCandidate = this._tabbrowserTabs.dragAndDropElements .filter(t => !movingTabsSet.has(t) || t == draggedTab) .at(index); if (!movingTabsSet.has(oldDropElementCandidate)) { @@ -1258,7 +1260,7 @@ : dropElement.elementIndex < numPinned; if (isOutOfBounds) { // Drop after last pinned tab - dropElement = this._tabbrowserTabs.ariaFocusableItems[numPinned - 1]; + dropElement = this._tabbrowserTabs.dragAndDropElements[numPinned - 1]; dropBefore = false; } } diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js @@ -153,7 +153,7 @@ throw new Error("Tab is not visible, so does not have an elementIndex"); } // Make sure the index is up to date. - this.container.ariaFocusableItems; + this.container.dragAndDropElements; return this.#elementIndex; } diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js @@ -3176,10 +3176,10 @@ if (elementIndex < 0) { return -1; } - if (elementIndex >= this.tabContainer.ariaFocusableItems.length) { + if (elementIndex >= this.tabContainer.dragAndDropElements.length) { return this.tabs.length; } - let element = this.tabContainer.ariaFocusableItems[elementIndex]; + let element = this.tabContainer.dragAndDropElements[elementIndex]; if (this.isTabGroupLabel(element)) { element = element.group.tabs[0]; } @@ -3235,6 +3235,11 @@ return null; } + this.dispatchEvent( + new CustomEvent("SplitViewCreated", { + bubbles: true, + }) + ); return splitview; } @@ -4338,6 +4343,11 @@ previousTab.pinned ) { elementIndex = Infinity; + } else if (previousTab.visible && previousTab.splitview) { + elementIndex = + this.tabContainer.dragAndDropElements.indexOf( + previousTab.splitview + ) + 1; } else if (previousTab.visible) { elementIndex = previousTab.elementIndex + 1; } else if (previousTab == FirefoxViewHandler.tab) { @@ -4359,7 +4369,7 @@ let allItems; let index; if (typeof elementIndex == "number") { - allItems = this.tabContainer.ariaFocusableItems; + allItems = this.tabContainer.dragAndDropElements; index = elementIndex; } else { allItems = this.tabs; @@ -4378,6 +4388,8 @@ if (pinned && !itemAfter?.pinned) { itemAfter = null; + } else if (itemAfter?.splitview) { + itemAfter = itemAfter.splitview?.nextElementSibling || null; } // Prevent a flash of unstyled content by setting up the tab content // and inherited attributes before appending it (see Bug 1592054): @@ -4386,7 +4398,10 @@ this.tabContainer._invalidateCachedTabs(); if (tabGroup) { - if (this.isTab(itemAfter) && itemAfter.group == tabGroup) { + if ( + (this.isTab(itemAfter) && itemAfter.group == tabGroup) || + this.isSplitViewWrapper(itemAfter) + ) { // Place at the front of, or between tabs in, the same tab group this.tabContainer.insertBefore(tab, itemAfter); } else { @@ -6432,7 +6447,7 @@ * The desired position, expressed as the index within the `tabs` array. * @param {number} [options.elementIndex] * The desired position, expressed as the index within the - * `MozTabbrowserTabs::ariaFocusableItems` array. + * `MozTabbrowserTabs::dragAndDropElements` array. * @param {boolean} [options.forceUngrouped=false] * Force `element` to move into position as a standalone tab, overriding * any possibility of entering a tab group. For example, setting `true` @@ -6870,7 +6885,7 @@ let nextElement; if (typeof elementIndex == "number") { index = elementIndex; - nextElement = this.tabContainer.ariaFocusableItems.at(elementIndex); + nextElement = this.tabContainer.dragAndDropElements.at(elementIndex); } else { index = tabIndex; nextElement = this.tabs.at(tabIndex); diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js @@ -13,6 +13,7 @@ const isTab = element => gBrowser.isTab(element); const isTabGroup = element => gBrowser.isTabGroup(element); const isTabGroupLabel = element => gBrowser.isTabGroupLabel(element); + const isSplitViewWrapper = element => gBrowser.isSplitViewWrapper(element); class MozTabbrowserTabs extends MozElements.TabsBase { static observedAttributes = ["orient"]; @@ -38,6 +39,8 @@ this.addEventListener("TabGroupAnimationComplete", this); this.addEventListener("TabGroupCreate", this); this.addEventListener("TabGroupRemoved", this); + this.addEventListener("SplitViewCreated", this); + this.addEventListener("SplitViewRemoved", this); this.addEventListener("transitionend", this); this.addEventListener("dblclick", this); this.addEventListener("click", this); @@ -396,6 +399,14 @@ this._invalidateCachedTabs(); } + on_SplitViewCreated() { + this._invalidateCachedTabs(); + } + + on_SplitViewRemoved() { + this._invalidateCachedTabs(); + } + /** * @param {TransitionEvent} event */ @@ -915,6 +926,9 @@ /** @type {FocusableItem[]} */ #focusableItems; + /** @type {dragAndDropElements[]} */ + #dragAndDropElements; + /** * @returns {FocusableItem[]} * @override @@ -924,36 +938,25 @@ return this.#focusableItems; } - let elementIndex = 0; - let unpinnedChildren = Array.from(this.arrowScrollbox.children); let pinnedChildren = Array.from(this.pinnedTabsContainer.children); let focusableItems = []; for (let child of pinnedChildren) { if (isTab(child)) { - child.elementIndex = elementIndex++; focusableItems.push(child); } } for (let child of unpinnedChildren) { if (isTab(child) && child.visible) { - child.elementIndex = elementIndex++; focusableItems.push(child); } else if (isTabGroup(child)) { - child.labelElement.elementIndex = elementIndex++; focusableItems.push(child.labelElement); let visibleTabsInGroup = child.tabs.filter(tab => tab.visible); - visibleTabsInGroup.forEach(tab => { - tab.elementIndex = elementIndex++; - }); focusableItems.push(...visibleTabsInGroup); } else if (child.tagName == "tab-split-view-wrapper") { let visibleTabsInSplitView = child.tabs.filter(tab => tab.visible); - visibleTabsInSplitView.forEach(tab => { - tab.elementIndex = elementIndex++; - }); focusableItems.push(...visibleTabsInSplitView); } } @@ -964,6 +967,55 @@ } /** + * @returns {dragAndDropElements[]} + * Representation of every drag and drop element including tabs, tab group labels and split view wrapper. + * We keep this separate from ariaFocusableItems because not every element for drag n'drop also needs to be + * focusable (ex, we don't want the splitview container to be focusable, only its children). + */ + get dragAndDropElements() { + if (this.#dragAndDropElements) { + return this.#dragAndDropElements; + } + + let elementIndex = 0; + let dragAndDropElements = []; + let unpinnedChildren = Array.from(this.arrowScrollbox.children); + let pinnedChildren = Array.from(this.pinnedTabsContainer.children); + + for (let child of [...pinnedChildren, ...unpinnedChildren]) { + if ( + !( + (isTab(child) && child.visible) || + isTabGroup(child) || + isSplitViewWrapper(child) + ) + ) { + continue; + } + + if (isTabGroup(child)) { + child.labelElement.elementIndex = elementIndex++; + dragAndDropElements.push(child.labelElement); + + let visibleChildren = Array.from(child.children).filter( + ele => ele.visible || ele.tagName == "tab-split-view-wrapper" + ); + + visibleChildren.forEach(tab => { + tab.elementIndex = elementIndex++; + }); + dragAndDropElements.push(...visibleChildren); + } else { + child.elementIndex = elementIndex++; + dragAndDropElements.push(child); + } + } + + this.#dragAndDropElements = dragAndDropElements; + return this.#dragAndDropElements; + } + + /** * Moves the ARIA focus in the tab strip left or right, as appropriate, to * the next tab or tab group label. * @@ -1000,8 +1052,9 @@ this.#visibleTabs = null; // Focusable items must also be visible, but they do not depend on // this.#visibleTabs, so changes to visible tabs need to also invalidate - // the focusable items cache + // the focusable items and dragAndDropElements cache. this.#focusableItems = null; + this.#dragAndDropElements = null; } #isMovingTab() { diff --git a/browser/components/tabbrowser/content/tabsplitview.js b/browser/components/tabbrowser/content/tabsplitview.js @@ -64,6 +64,11 @@ this.#tabChangeObserver?.disconnect(); this.ownerGlobal.removeEventListener("TabSelect", this); this.#deactivate(); + this.dispatchEvent( + new CustomEvent("SplitViewRemoved", { + bubbles: true, + }) + ); } #observeTabChanges() {