Bug 1555060 Stop using dom structure for <tabs>/<tab> relationships

A bunch of existing code assumes that <tab> elements are the immediate
and only children of a <tabs> element, and uses dom apis to traverse
relationships between these elements.  To simplify conversion of <tabs>
to a custom element (and hopefully improve readability a bit at the same
time!), introduce new apis:

On <tab>
this.parentNode -> this.container
this.nextElementSibling -> this.container.findNextTab(...)
this.previousElementSibiling -> this.container.findNextTab(...)

On <tabs>
this.children -> this.allTabs

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

--HG--
extra : source : f4e21e465f384b90fa1e768141c4db708748bf66
extra : histedit_source : 95d8a4242e8e04df9e29c2b647558d37e910b845
This commit is contained in:
Andrew Swan 2019-06-11 14:49:46 -07:00
parent c716c93bbf
commit f1d137eff5
21 changed files with 192 additions and 120 deletions

View File

@ -46,7 +46,7 @@
////////////////////////////////////////////////////////////////////////
// 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
var tabs = Array.from(tabBrowser().tabContainer.children);
var tabs = Array.from(tabBrowser().tabContainer.allTabs);
// For preloaded tabs, there might be items in this array where this relation
// doesn't hold, so just deal with that:
var panels = tabs.map(t => t.linkedBrowser.closest("tabpanels > *"));

View File

@ -105,6 +105,10 @@ class MozTabbrowserTab extends MozElements.MozTab {
}
}
get container() {
return gBrowser.tabContainer;
}
set _visuallySelected(val) {
if (val == (this.getAttribute("visuallyselected") == "true")) {
return val;
@ -271,7 +275,7 @@ class MozTabbrowserTab extends MozElements.MozTab {
on_mousedown(event) {
let eventMaySelectTab = true;
let tabContainer = this.parentNode;
let tabContainer = this.container;
if (tabContainer._closeTabByDblclick &&
event.button == 0 &&
@ -392,7 +396,7 @@ class MozTabbrowserTab extends MozElements.MozTab {
event.stopPropagation();
}
let tabContainer = this.parentNode;
let tabContainer = this.container;
if (tabContainer._closeTabByDblclick &&
this._selectedOnFirstMouseDown &&
this.selected &&
@ -416,7 +420,7 @@ class MozTabbrowserTab extends MozElements.MozTab {
return;
}
let tabContainer = this.parentNode;
let tabContainer = this.container;
let visibleTabs = tabContainer._getVisibleTabs();
let tabIndex = visibleTabs.indexOf(this);
@ -464,7 +468,7 @@ class MozTabbrowserTab extends MozElements.MozTab {
}
_mouseleave() {
let tabContainer = this.parentNode;
let tabContainer = this.container;
if (tabContainer._beforeHoveredTab) {
tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
tabContainer._beforeHoveredTab = null;

View File

@ -205,8 +205,7 @@ window._gBrowser = {
},
get tabs() {
delete this.tabs;
return this.tabs = this.tabContainer.children;
return this.tabContainer.allTabs;
},
get tabbox() {
@ -634,12 +633,12 @@ window._gBrowser = {
syncThrobberAnimations(aTab) {
aTab.ownerGlobal.promiseDocumentFlushed(() => {
if (!aTab.parentNode) {
if (!aTab.container) {
return;
}
const animations =
Array.from(aTab.parentNode.getElementsByTagName("tab"))
Array.from(aTab.container.getElementsByTagName("tab"))
.map(tab => {
const throbber = tab.throbber;
return throbber ? throbber.getAnimations({ subtree: true }) : [];
@ -2416,9 +2415,7 @@ window._gBrowser = {
index = Math.min(index, this.tabs.length);
}
// Use .item() instead of [] because we need .item() to return null in
// order to append the tab at the end in case index == tabs.length.
let tabAfter = this.tabs.item(index);
let tabAfter = this.tabs[index] || null;
this.tabContainer.insertBefore(t, tabAfter);
if (tabAfter) {
this._updateTabsAfterInsert();
@ -2842,7 +2839,7 @@ window._gBrowser = {
aTab.removeAttribute("bursting");
setTimeout(function(tab, tabbrowser) {
if (tab.parentNode &&
if (tab.container &&
window.getComputedStyle(tab).maxWidth == "0.1px") {
console.assert(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
tabbrowser._endRemoveTab(tab);
@ -3180,17 +3177,16 @@ window._gBrowser = {
}
// Try to find a remaining tab that comes after the given tab
let tab = aTab;
do {
tab = tab.nextElementSibling;
} while (tab && !remainingTabs.includes(tab));
let tab = this.tabContainer.findNextTab(aTab, {
direction: 1,
filter: _tab => remainingTabs.includes(_tab),
});
if (!tab) {
tab = aTab;
do {
tab = tab.previousElementSibling;
} while (tab && !remainingTabs.includes(tab));
tab = this.tabContainer.findNextTab(aTab, {
direction: -1,
filter: _tab => remainingTabs.includes(_tab),
});
}
return tab;
@ -3730,9 +3726,7 @@ window._gBrowser = {
// invalidate cache
this._visibleTabs = null;
// use .item() instead of [] because dragging to the end of the strip goes out of
// bounds: .item() returns null (so it acts like appendChild), but [] throws
this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
this.tabContainer.insertBefore(aTab, this.tabs[aIndex] || null);
this._updateTabsAfterInsert();
if (wasFocused)
@ -3751,9 +3745,10 @@ window._gBrowser = {
},
moveTabForward() {
let nextTab = this.selectedTab.nextElementSibling;
while (nextTab && nextTab.hidden)
nextTab = nextTab.nextElementSibling;
let nextTab = this.tabContainer.findNextTab(this.selectedTab, {
direction: 1,
filter: tab => !tab.hidden,
});
if (nextTab)
this.moveTabTo(this.selectedTab, nextTab._tPos);
@ -3797,7 +3792,7 @@ window._gBrowser = {
let newTab = this.addWebTab("about:blank", params);
let newBrowser = this.getBrowserForTab(newTab);
aTab.parentNode._finishAnimateTabMove();
aTab.container._finishAnimateTabMove();
if (!createLazyBrowser) {
// Stop the about:blank load.
@ -3820,9 +3815,10 @@ window._gBrowser = {
},
moveTabBackward() {
let previousTab = this.selectedTab.previousElementSibling;
while (previousTab && previousTab.hidden)
previousTab = previousTab.previousElementSibling;
let previousTab = this.tabContainer.findNextTab(this.selectedTab, {
direction: -1,
filter: tab => !tab.hidden,
});
if (previousTab)
this.moveTabTo(this.selectedTab, previousTab._tPos);
@ -5452,8 +5448,12 @@ var TabContextMenu = {
let lastVisibleTab = visibleTabs[visibleTabs.length - 1];
let tabsToMove = contextTabIsSelected ? selectedTabs : [this.contextTab];
let lastTabToMove = tabsToMove[tabsToMove.length - 1];
let isLastPinnedTab = lastTabToMove.pinned &&
(!lastTabToMove.nextElementSibling || !lastTabToMove.nextElementSibling.pinned);
let isLastPinnedTab = false;
if (lastTabToMove.pinned) {
let sibling = gBrowser.tabContainer.findNextTab(lastTabToMove);
isLastPinnedTab = !sibling || !sibling.pinned;
}
contextMoveTabToEnd.disabled = (lastTabToMove == lastVisibleTab || isLastPinnedTab) &&
allSelectedTabsAdjacent;
let contextMoveTabToStart = document.getElementById("context_moveToStart");

View File

@ -2,7 +2,7 @@ add_task(async function() {
var win = openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", "chrome,all,dialog=no");
await SimpleTest.promiseFocus(win);
let tab = win.gBrowser.tabContainer.firstElementChild;
let tab = win.gBrowser.tabs[0];
await promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
is(win.gBrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
@ -17,7 +17,7 @@ add_task(async function() {
var win = openDialog(AppConstants.BROWSER_CHROME_URL, "_blank", "chrome,all,dialog=no");
await SimpleTest.promiseFocus(win);
let tab = win.gBrowser.tabContainer.firstElementChild;
let tab = win.gBrowser.tabs[0];
await promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
var newTab = BrowserTestUtils.addTab(win.gBrowser);
@ -25,7 +25,7 @@ add_task(async function() {
win.gBrowser.removeTab(tab);
ok(!win.closed, "Window stays open");
if (!win.closed) {
is(win.gBrowser.tabContainer.childElementCount, 1, "Window has one tab");
is(win.gBrowser.tabs.length, 1, "Window has one tab");
is(win.gBrowser.browsers.length, 1, "Window has one browser");
is(win.gBrowser.selectedTab, newTab, "Remaining tab is selected");
is(win.gBrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");

View File

@ -91,12 +91,12 @@ add_task(async function() {
ok(selectedTab.selected,
"Ctrl+Tab*2 -> Ctrl+W -> Ctrl+Shift+Tab*2 keeps the selected tab");
}
gBrowser.removeTab(gBrowser.tabContainer.lastElementChild);
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
checkTabs(2);
await ctrlTabTest([1], 1, 0);
gBrowser.removeTab(gBrowser.tabContainer.lastElementChild);
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
checkTabs(1);
{ // test for bug 445768

View File

@ -3,21 +3,20 @@ function test() {
BrowserTestUtils.addTab(gBrowser);
BrowserTestUtils.addTab(gBrowser);
var tabs = gBrowser.tabs;
var owner;
is(tabs.length, 4, "4 tabs are open");
is(gBrowser.tabs.length, 4, "4 tabs are open");
owner = gBrowser.selectedTab = tabs[2];
owner = gBrowser.selectedTab = gBrowser.tabs[2];
BrowserOpenTab();
is(gBrowser.selectedTab, tabs[4], "newly opened tab is selected");
is(gBrowser.selectedTab, gBrowser.tabs[4], "newly opened tab is selected");
gBrowser.removeCurrentTab();
is(gBrowser.selectedTab, owner, "owner is selected");
owner = gBrowser.selectedTab;
BrowserOpenTab();
gBrowser.selectedTab = tabs[1];
gBrowser.selectedTab = tabs[4];
gBrowser.selectedTab = gBrowser.tabs[1];
gBrowser.selectedTab = gBrowser.tabs[4];
gBrowser.removeCurrentTab();
isnot(gBrowser.selectedTab, owner, "selecting a different tab clears the owner relation");
@ -25,8 +24,8 @@ function test() {
BrowserOpenTab();
gBrowser.moveTabTo(gBrowser.selectedTab, 0);
gBrowser.removeCurrentTab();
is(gBrowser.selectedTab, owner, "owner relatitionship persists when tab is moved");
is(gBrowser.selectedTab, owner, "owner relationship persists when tab is moved");
while (tabs.length > 1)
while (gBrowser.tabs.length > 1)
gBrowser.removeCurrentTab();
}

View File

@ -114,7 +114,7 @@ add_task(async function() {
// Now switch to the first tab. We shouldn't flush layout at all.
await withPerfObserver(async function() {
let firstTab = gBrowser.tabContainer.firstElementChild;
let firstTab = gBrowser.tabs[0];
await BrowserTestUtils.switchTab(gBrowser, firstTab);
await BrowserTestUtils.waitForCondition(() => {
return gBrowser.tabContainer.arrowScrollbox.hasAttribute("scrolledtostart");
@ -132,7 +132,7 @@ add_task(async function() {
// removals to put the tab strip out of the overflow state, so we'll just
// keep testing removals until that occurs.
while (gBrowser.tabContainer.hasAttribute("overflow")) {
lastTab = gBrowser.tabContainer.lastElementChild;
lastTab = gBrowser.tabs[gBrowser.tabs.length - 1];
if (gBrowser.selectedTab !== lastTab) {
await BrowserTestUtils.switchTab(gBrowser, lastTab);
}

View File

@ -164,7 +164,7 @@ async function test_playing_icon_on_hidden_tab(tab) {
await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE, true, true),
await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE, true, true),
];
let tabContainer = tab.parentNode;
let tabContainer = tab.container;
let alltabsButton = document.getElementById("alltabs-button");
let alltabsBadge = alltabsButton.badgeLabel;

View File

@ -59,9 +59,11 @@ add_task(async function test() {
EventUtils.synthesizeMouseAtCenter(newTabButton, metaKeyEvent);
openEvent = await promiseTabOpened;
newTab = openEvent.target;
is(newTab.previousElementSibling, tab3,
"New tab should be opened after tab3 when tab1 and tab3 are selected");
is(newTab.nextElementSibling, null,
let previous = gBrowser.tabContainer.findNextTab(newTab, {direction: -1});
is(previous, tab3,
"New tab should be opened after tab3 when tab1 and tab3 are selected");
let next = gBrowser.tabContainer.findNextTab(newTab, {direction: 1});
is(next, null,
"New tab should be opened at the end of the tabstrip when tab1 and tab3 are selected");
BrowserTestUtils.removeTab(newTab);
@ -74,9 +76,11 @@ add_task(async function test() {
EventUtils.synthesizeMouseAtCenter(newTabButton, {});
openEvent = await promiseTabOpened;
newTab = openEvent.target;
is(newTab.previousElementSibling, tab3,
previous = gBrowser.tabContainer.findNextTab(newTab, {direction: -1});
is(previous, tab3,
"New tab should be opened after tab3 when ctrlKey is not used without multiselection");
is(newTab.nextElementSibling, null,
next = gBrowser.tabContainer.findNextTab(newTab, {direction: 1});
is(next, null,
"New tab should be opened at the end of the tabstrip when ctrlKey is not used without multiselection");
BrowserTestUtils.removeTab(newTab);
@ -90,9 +94,11 @@ add_task(async function test() {
EventUtils.synthesizeMouseAtCenter(newTabButton, {});
openEvent = await promiseTabOpened;
newTab = openEvent.target;
is(newTab.previousElementSibling, tab3,
previous = gBrowser.tabContainer.findNextTab(newTab, {direction: -1});
is(previous, tab3,
"New tab should be opened after tab3 when ctrlKey is not used with multiselection");
is(newTab.nextElementSibling, null,
next = gBrowser.tabContainer.findNextTab(newTab, {direction: 1});
is(next, null,
"New tab should be opened at the end of the tabstrip when ctrlKey is not used with multiselection");
BrowserTestUtils.removeTab(newTab);

View File

@ -10,7 +10,6 @@ add_task(async function() {
let arrowScrollbox = gBrowser.tabContainer.arrowScrollbox;
let scrollbox = arrowScrollbox.scrollbox;
let originalSmoothScroll = arrowScrollbox.smoothScroll;
let tabs = gBrowser.tabs;
let tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
let rect = ele => ele.getBoundingClientRect();
@ -25,7 +24,7 @@ add_task(async function() {
let elementFromPoint = x => arrowScrollbox._elementFromPoint(x);
let nextLeftElement = () => elementFromPoint(left(scrollbox) - 1);
let nextRightElement = () => elementFromPoint(right(scrollbox) + 1);
let firstScrollable = () => tabs[gBrowser._numPinnedTabs];
let firstScrollable = () => gBrowser.tabs[gBrowser._numPinnedTabs];
let waitForNextFrame = async function() {
await window.promiseDocumentFlushed(() => {});
await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
@ -36,11 +35,11 @@ add_task(async function() {
arrowScrollbox.smoothScroll = originalSmoothScroll;
});
while (tabs.length < tabCountForOverflow) {
while (gBrowser.tabs.length < tabCountForOverflow) {
BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true });
}
gBrowser.pinTab(tabs[0]);
gBrowser.pinTab(gBrowser.tabs[0]);
await BrowserTestUtils.waitForCondition(() => {
return Array.from(gBrowser.tabs).every(tab => tab._fullyOpen);
@ -62,7 +61,7 @@ add_task(async function() {
await waitForNextFrame();
isRight(element, "Scrolled one tab to the right with a single click");
gBrowser.selectedTab = tabs[tabs.length - 1];
gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1];
await waitForNextFrame();
ok(right(gBrowser.selectedTab) <= right(scrollbox), "Selecting the last tab scrolls it into view " +
"(" + right(gBrowser.selectedTab) + " <= " + right(scrollbox) + ")");
@ -88,7 +87,7 @@ add_task(async function() {
ok(left(scrollbox) <= firstScrollableLeft, "Scrolled to the start with a triple click " +
"(" + left(scrollbox) + " <= " + firstScrollableLeft + ")");
while (tabs.length > 1) {
while (gBrowser.tabs.length > 1) {
BrowserTestUtils.removeTab(gBrowser.tabs[0]);
}
});

View File

@ -9,7 +9,6 @@ add_task(async function() {
let initialTabsLength = gBrowser.tabs.length;
let arrowScrollbox = gBrowser.tabContainer.arrowScrollbox;
let tabs = gBrowser.tabs;
let tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
let width = ele => ele.getBoundingClientRect().width;
@ -20,24 +19,26 @@ add_task(async function() {
let newTab2 = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:about", {skipAnimation: true});
let newTab3 = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:config", {skipAnimation: true});
while (tabs.length < tabCountForOverflow) {
while (gBrowser.tabs.length < tabCountForOverflow) {
BrowserTestUtils.addTab(gBrowser, "about:blank", { skipAnimation: true });
}
registerCleanupFunction(function() {
while (tabs.length > initialTabsLength) {
gBrowser.removeTab(gBrowser.tabs[initialTabsLength]);
while (gBrowser.tabs.length > initialTabsLength) {
gBrowser.removeTab(gBrowser.tabContainer.getItemAtIndex(initialTabsLength));
}
});
is(gBrowser.tabs.length, tabCountForOverflow, "new tabs are opened");
is(gBrowser.tabs[initialTabsLength], newTab1, "newTab1 position is correct");
is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
let tabs = gBrowser.tabs;
is(tabs.length, tabCountForOverflow, "new tabs are opened");
is(tabs[initialTabsLength], newTab1, "newTab1 position is correct");
is(tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
is(tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
await dragAndDrop(newTab1, newTab2, false);
is(gBrowser.tabs.length, tabCountForOverflow, "tabs are still there");
is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
tabs = gBrowser.tabs;
is(tabs.length, tabCountForOverflow, "tabs are still there");
is(tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
is(tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
is(tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
});

View File

@ -52,8 +52,8 @@ add_task(async function test() {
is(browser2.contentPrincipal.userContextId, 2, "Tab2 UCI must be 2");
let found = false;
for (let i = 0; i < gBrowser.tabContainer.children.length; ++i) {
let tab = gBrowser.tabContainer.children[i];
for (let i = 0; i < gBrowser.tabs.length; ++i) {
let tab = gBrowser.tabs[i];
let browser = gBrowser.getBrowserForTab(tab);
if (browser.contentTitle == "?new") {
is(browser.contentPrincipal.userContextId, 1, "Tab3 UCI must be 1");

View File

@ -90,7 +90,7 @@ add_task(async function test_sidebarpanels_click() {
// Remove tabs created by sub-tests.
while (gBrowser.tabs.length > 1) {
gBrowser.removeTab(gBrowser.tabContainer.lastElementChild);
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
}
}
});

View File

@ -150,7 +150,7 @@ function ensure_opentabs_match_db() {
if (browserWin.closed)
continue;
for (let i = 0; i < browserWin.gBrowser.tabContainer.childElementCount; i++) {
for (let i = 0; i < browserWin.gBrowser.tabs.length; i++) {
let browser = browserWin.gBrowser.getBrowserAtIndex(i);
let url = browser.currentURI.spec;
if (browserWin.isBlankPageURL(url))

View File

@ -58,11 +58,11 @@ function test() {
checkSelectedTab();
// Remove #3 (non active)
gBrowser.removeTab(gBrowser.tabContainer.lastElementChild);
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
checkPreviews(2, "Expected number of previews after closing unselected via browser");
// Remove #1 (active)
gBrowser.removeTab(gBrowser.tabContainer.firstElementChild);
gBrowser.removeTab(gBrowser.tabs[0]);
checkPreviews(1, "Expected number of previews after closing selected tab via browser");
// Add a new tab

View File

@ -186,7 +186,7 @@ PushRecord.prototype = {
continue;
}
// `gBrowser` on Desktop; `BrowserApp` on Fennec.
let tabs = window.gBrowser ? window.gBrowser.tabContainer.children :
let tabs = window.gBrowser ? window.gBrowser.tabs :
window.BrowserApp.tabs;
for (let tab of tabs) {
// `linkedBrowser` on Desktop; `browser` on Fennec.

View File

@ -564,7 +564,7 @@ Tester.prototype = {
// Remove stale tabs
if (this.currentTest && window.gBrowser && gBrowser.tabs.length > 1) {
while (gBrowser.tabs.length > 1) {
let lastTab = gBrowser.tabContainer.lastElementChild;
let lastTab = gBrowser.tabs[gBrowser.tabs.length - 1];
if (!lastTab.closing) {
// Report the stale tab as an error only when they're not closing.
// Tests can finish without waiting for the closing tabs.

View File

@ -487,8 +487,8 @@ _ContextualIdentityService.prototype = {
}
let tabbrowser = win.gBrowser;
for (let i = tabbrowser.tabContainer.children.length - 1; i >= 0; --i) {
let tab = tabbrowser.tabContainer.children[i];
for (let i = tabbrowser.tabs.length - 1; i >= 0; --i) {
let tab = tabbrowser.tabs[i];
if (tab.hasAttribute("usercontextid") &&
(!userContextId ||
parseInt(tab.getAttribute("usercontextid"), 10) == userContextId)) {

View File

@ -82,34 +82,34 @@ function test_tabbox()
var tabs = document.getElementById("tabs");
var tabpanels = document.getElementById("tabpanels");
test_tabbox_State(tabbox, "tabbox initial", 0, tabs.firstChild, tabpanels.firstChild);
test_tabbox_State(tabbox, "tabbox initial", 0, tabs.allTabs[0], tabpanels.firstChild);
// check the selectedIndex property
tabbox.selectedIndex = 1;
test_tabbox_State(tabbox, "tabbox selectedIndex 1", 1, tabs.lastChild, tabpanels.lastChild);
test_tabbox_State(tabbox, "tabbox selectedIndex 1", 1, tabs.allTabs[tabs.allTabs.length - 1], tabpanels.lastChild);
tabbox.selectedIndex = 2;
test_tabbox_State(tabbox, "tabbox selectedIndex 2", 1, tabs.lastChild, tabpanels.lastChild);
test_tabbox_State(tabbox, "tabbox selectedIndex 2", 1, tabs.allTabs[tabs.allTabs.length - 1], tabpanels.lastChild);
// tabbox must have a selection, so setting to -1 should do nothing
tabbox.selectedIndex = -1;
test_tabbox_State(tabbox, "tabbox selectedIndex -1", 1, tabs.lastChild, tabpanels.lastChild);
test_tabbox_State(tabbox, "tabbox selectedIndex -1", 1, tabs.allTabs[tabs.allTabs.length - 1], tabpanels.lastChild);
// check the selectedTab property
tabbox.selectedTab = tabs.firstChild;
test_tabbox_State(tabbox, "tabbox selected", 0, tabs.firstChild, tabpanels.firstChild);
tabbox.selectedTab = tabs.allTabs[0];
test_tabbox_State(tabbox, "tabbox selected", 0, tabs.allTabs[0], tabpanels.firstChild);
// setting selectedTab to null should not do anything
tabbox.selectedTab = null;
test_tabbox_State(tabbox, "tabbox selectedTab null", 0, tabs.firstChild, tabpanels.firstChild);
test_tabbox_State(tabbox, "tabbox selectedTab null", 0, tabs.allTabs[0], tabpanels.firstChild);
// check the selectedPanel property
tabbox.selectedPanel = tabpanels.lastChild;
test_tabbox_State(tabbox, "tabbox selectedPanel", 0, tabs.firstChild, tabpanels.lastChild);
test_tabbox_State(tabbox, "tabbox selectedPanel", 0, tabs.allTabs[0], tabpanels.lastChild);
// setting selectedPanel to null should not do anything
tabbox.selectedPanel = null;
test_tabbox_State(tabbox, "tabbox selectedPanel null", 0, tabs.firstChild, tabpanels.lastChild);
test_tabbox_State(tabbox, "tabbox selectedPanel null", 0, tabs.allTabs[0], tabpanels.lastChild);
tabbox.selectedIndex = 0;
test_tabpanels(tabpanels, tabbox);

View File

@ -233,7 +233,7 @@ class MozTabpanels extends MozXULElement {
if (tabpanelIdx == -1)
return null;
let tabElms = tabsElm.children;
let tabElms = tabsElm.allTabs;
let tabElmFromIndex = tabElms[tabpanelIdx];
let tabpanelId = aTabPanelElm.id;
@ -324,7 +324,7 @@ MozElements.MozTab = class MozTab extends MozElements.BaseText {
// Call this before setting the 'ignorefocus' attribute because this
// will pass on focus if the formerly selected tab was focused as well.
this.parentNode._selectNewTab(this);
this.closest("tabs")._selectNewTab(this);
var isTabFocused = false;
try {
@ -352,7 +352,7 @@ MozElements.MozTab = class MozTab extends MozElements.BaseText {
switch (event.keyCode) {
case KeyEvent.DOM_VK_LEFT: {
let direction = window.getComputedStyle(this.parentNode).direction;
this.parentNode.advanceSelectedTab(direction == "ltr" ? -1 : 1,
this.container.advanceSelectedTab(direction == "ltr" ? -1 : 1,
this.arrowKeysShouldWrap);
event.preventDefault();
break;
@ -360,30 +360,30 @@ MozElements.MozTab = class MozTab extends MozElements.BaseText {
case KeyEvent.DOM_VK_RIGHT: {
let direction = window.getComputedStyle(this.parentNode).direction;
this.parentNode.advanceSelectedTab(direction == "ltr" ? 1 : -1,
this.container.advanceSelectedTab(direction == "ltr" ? 1 : -1,
this.arrowKeysShouldWrap);
event.preventDefault();
break;
}
case KeyEvent.DOM_VK_UP:
this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
this.container.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
event.preventDefault();
break;
case KeyEvent.DOM_VK_DOWN:
this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
this.container.advanceSelectedTab(1, this.arrowKeysShouldWrap);
event.preventDefault();
break;
case KeyEvent.DOM_VK_HOME:
this.parentNode._selectNewTab(this.parentNode.children[0]);
this.container._selectNewTab(this.container.allTabs[0]);
event.preventDefault();
break;
case KeyEvent.DOM_VK_END: {
let tabs = this.parentNode.children;
this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
let {allTabs} = this.container;
this.container._selectNewTab(allTabs[allTabs.length - 1], -1);
event.preventDefault();
break;
}

View File

@ -46,6 +46,12 @@
]]>
</constructor>
<property name="allTabs">
<getter>
return this.children;
</getter>
</property>
<!-- nsIDOMXULRelatedElement -->
<method name="getRelatedElement">
<parameter name="aTabElm"/>
@ -269,6 +275,69 @@
</body>
</method>
<method name="findNextTab">
<parameter name="startTab"/>
<parameter name="opts"/>
<body>
<![CDATA[
/**
* Find an adjacent tab.
*
* startTab A <tab> element to start searching from.
* opts.direction 1 to search forward, -1 to search backward.
* opts.wrap If true, wrap around if the search reaches
* the end (or beginning) of the tab strip.
* opts.startWithAdjacent If true (which is the default), start
* searching from the next tab after
* (or before) startTab. If false,
* startTab may be returned if it passes
* the filter.
* opts.filter A function to select which tabs to return.
*
* returns the next <tab> element or, if none exists, null.
*/
let {
direction = 1,
wrap = false,
startWithAdjacent = true,
filter = tab => true,
} = opts || {};
let tab = startTab;
if (!startWithAdjacent && filter(tab)) {
return tab;
}
let tabs = this.allTabs;
let i = tabs.indexOf(tab);
if (i < 0) {
return null;
}
while (true) {
i += direction;
if (wrap) {
if (i < 0) {
i = tabs.length - 1;
} else if (i >= tabs.length) {
i = 0;
}
} else if (i < 0 || i >= tabs.length) {
return null;
}
tab = tabs[i];
if (tab == startTab) {
return null;
}
if (filter(tab)) {
return tab;
}
}
]]>
</body>
</method>
<method name="_selectNewTab">
<parameter name="aNewTab"/>
<parameter name="aFallbackDir"/>
@ -277,15 +346,12 @@
<![CDATA[
this.ariaFocusedItem = null;
var requestedTab = aNewTab;
while (aNewTab.hidden || aNewTab.disabled || !this._canAdvanceToTab(aNewTab)) {
aNewTab = aFallbackDir == -1 ? aNewTab.previousElementSibling : aNewTab.nextElementSibling;
if (!aNewTab && aWrap)
aNewTab = aFallbackDir == -1 ? this.children[this.children.length - 1] :
this.children[0];
if (!aNewTab || aNewTab == requestedTab)
return;
}
aNewTab = this.findNextTab(aNewTab, {
direction: aFallbackDir,
wrap: aWrap,
startWithAdjacent: false,
filter: tab => !tab.hidden && !tab.disabled && this._canAdvanceToTab(tab),
});
var isTabFocused = false;
try {
@ -332,14 +398,11 @@
<body>
<![CDATA[
var startTab = this.ariaFocusedItem || this.selectedItem;
var next = startTab[(aDir == -1 ? "previous" : "next") + "ElementSibling"];
if (!next && aWrap) {
next = aDir == -1 ? this.children[this.children.length - 1] :
this.children[0];
}
if (next && next != startTab) {
this._selectNewTab(next, aDir, aWrap);
}
let newTab = this.findNextTab(startTab, {
direction: aDir,
wrap: aWrap,
});
this._selectNewTab(newTab, aDir, aWrap);
]]>
</body>
</method>