Backed out 4 changesets (bug 1519514) for causing several browser chrome failures. CLOSED TREE

Backed out changeset 485c2c76fab6 (bug 1519514)
Backed out changeset 8488d800a785 (bug 1519514)
Backed out changeset 858b9456eb3c (bug 1519514)
Backed out changeset 2cd983857de6 (bug 1519514)
This commit is contained in:
Cosmin Sabou
2019-05-29 11:01:47 +03:00
parent 7f77ade546
commit 61c6a5e4c5
31 changed files with 778 additions and 806 deletions

View File

@@ -977,7 +977,7 @@ var gIdentityHandler = {
let ctx = canvas.getContext("2d");
ctx.font = `${14 * scale}px sans-serif`;
ctx.fillText(`${value}`, 20 * scale, 14 * scale);
let tabIcon = gBrowser.selectedTab.iconImage;
let tabIcon = document.getAnonymousElementByAttribute(gBrowser.selectedTab, "anonid", "tab-icon-image");
let image = new Image();
image.src = tabIcon.src;
ctx.drawImage(image, 0, 0, 16 * scale, 16 * scale);

View File

@@ -158,6 +158,10 @@ panelview[mainview] > .panel-header {
visibility: hidden; /* temporary space to keep a tab's close button under the cursor */
}
.tabbrowser-tab {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
}
.tabbrowser-tab:not([pinned]) {
-moz-box-flex: 100;
max-width: 225px;

View File

@@ -96,7 +96,6 @@
Services.scriptloader.loadSubScript("chrome://browser/content/browser-sidebar.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/browser-tabsintitlebar.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/tabbrowser.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/tabbrowser-tab.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/search/autocomplete-popup.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this);
@@ -729,7 +728,7 @@
setfocus="false"
tooltip="tabbrowser-tab-tooltip"
stopwatchid="FX_TAB_CLICK_MS">
<tab is="tabbrowser-tab" class="tabbrowser-tab" selected="true" visuallyselected="true" fadein="true"/>
<tab class="tabbrowser-tab" selected="true" visuallyselected="true" fadein="true"/>
</tabs>
<toolbarbutton id="new-tab-button"

View File

@@ -1,574 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
class MozTabbrowserTab extends MozElements.MozTab {
constructor() {
super();
this.addEventListener("mouseover", (event) => {
if (event.originalTarget.classList.contains("tab-close-button")) {
this.mOverCloseButton = true;
}
this._mouseenter();
});
this.addEventListener("mouseout", (event) => {
if (event.originalTarget.classList.contains("tab-close-button")) {
this.mOverCloseButton = false;
}
this._mouseleave();
});
this.addEventListener("dragstart", (event) => {
this.style.MozUserFocus = "";
}, true);
this.addEventListener("dragstart", (event) => {
if (this.mOverCloseButton) {
event.stopPropagation();
}
});
this.addEventListener("mousedown", (event) => {
let tabContainer = this.parentNode;
if (tabContainer._closeTabByDblclick &&
event.button == 0 &&
event.detail == 1) {
this._selectedOnFirstMouseDown = this.selected;
}
if (this.selected) {
this.style.MozUserFocus = "ignore";
} else if (event.originalTarget.classList.contains("tab-close-button") ||
event.originalTarget.classList.contains("tab-icon-sound") ||
event.originalTarget.classList.contains("tab-icon-overlay")) {
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
}
if (event.button == 1) {
gBrowser.warmupTab(gBrowser._findTabToBlurTo(this));
}
if (event.button == 0 && tabContainer._multiselectEnabled) {
let shiftKey = event.shiftKey;
let accelKey = event.getModifierState("Accel");
if (shiftKey) {
const lastSelectedTab = gBrowser.lastMultiSelectedTab;
if (!accelKey) {
gBrowser.selectedTab = lastSelectedTab;
// Make sure selection is cleared when tab-switch doesn't happen.
gBrowser.clearMultiSelectedTabs(false);
}
gBrowser.addRangeToMultiSelectedTabs(lastSelectedTab, this);
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
} else if (accelKey) {
// Ctrl (Cmd for mac) key is pressed
if (this.multiselected) {
gBrowser.removeFromMultiSelectedTabs(this, true);
} else if (this != gBrowser.selectedTab) {
gBrowser.addToMultiSelectedTabs(this, false);
gBrowser.lastMultiSelectedTab = this;
}
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
} else if (!this.selected && this.multiselected) {
gBrowser.lockClearMultiSelectionOnce();
}
}
}, true);
this.addEventListener("mouseup", (event) => {
// Make sure that clear-selection is released.
// Otherwise selection using Shift key may be broken.
gBrowser.unlockClearMultiSelection();
this.style.MozUserFocus = "";
});
this.addEventListener("click", (event) => {
if (event.button != 0) {
return;
}
if (event.getModifierState("Accel") || event.shiftKey) {
return;
}
if (gBrowser.multiSelectedTabsCount > 0 &&
!event.originalTarget.classList.contains("tab-close-button") &&
!event.originalTarget.classList.contains("tab-icon-sound") &&
!event.originalTarget.classList.contains("tab-icon-overlay")) {
// Tabs were previously multi-selected and user clicks on a tab
// without holding Ctrl/Cmd Key
// Force positional attributes to update when the
// target (of the click) is the "active" tab.
let updatePositionalAttr = gBrowser.selectedTab == this;
gBrowser.clearMultiSelectedTabs(updatePositionalAttr);
}
if (event.originalTarget.classList.contains("tab-icon-sound") ||
(event.originalTarget.classList.contains("tab-icon-overlay") &&
(event.originalTarget.hasAttribute("soundplaying") ||
event.originalTarget.hasAttribute("muted") ||
event.originalTarget.hasAttribute("activemedia-blocked")))) {
if (this.multiselected) {
gBrowser.toggleMuteAudioOnMultiSelectedTabs(this);
} else {
this.toggleMuteAudio();
}
return;
}
if (event.originalTarget.classList.contains("tab-close-button")) {
if (this.multiselected) {
gBrowser.removeMultiSelectedTabs();
} else {
gBrowser.removeTab(this, {
animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
});
}
// This enables double-click protection for the tab container
// (see tabbrowser-tabs 'click' handler).
gBrowser.tabContainer._blockDblClick = true;
}
});
this.addEventListener("dblclick", (event) => {
if (event.button != 0) {
return;
}
// for the one-close-button case
if (event.originalTarget.classList.contains("tab-close-button")) {
event.stopPropagation();
}
let tabContainer = this.parentNode;
if (tabContainer._closeTabByDblclick &&
this._selectedOnFirstMouseDown &&
this.selected &&
!(event.originalTarget.classList.contains("tab-icon-sound") ||
event.originalTarget.classList.contains("tab-icon-overlay"))) {
gBrowser.removeTab(this, {
animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
});
}
}, true);
this.addEventListener("animationend", (event) => {
if (event.originalTarget.classList.contains("tab-loading-burst")) {
this.removeAttribute("bursting");
}
});
this._selectedOnFirstMouseDown = false;
/**
* Describes how the tab ended up in this mute state. May be any of:
*
* - undefined: The tabs mute state has never changed.
* - null: The mute state was last changed through the UI.
* - Any string: The ID was changed through an extension API. The string
* must be the ID of the extension which changed it.
*/
this.muteReason = undefined;
this.mOverCloseButton = false;
this.mCorrespondingMenuitem = null;
}
static get inheritedAttributes() {
return {
".tab-background": "selected=visuallyselected,fadein,multiselected",
".tab-line": "selected=visuallyselected,multiselected,before-multiselected",
".tab-loading-burst": "pinned,bursting,notselectedsinceload",
".tab-content": "pinned,selected=visuallyselected,titlechanged,attention",
".tab-throbber": "fadein,pinned,busy,progress,selected=visuallyselected",
".tab-icon-pending": "fadein,pinned,busy,progress,selected=visuallyselected,pendingicon",
".tab-icon-image": "src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing",
".tab-sharing-icon-overlay": "sharing,selected=visuallyselected,pinned",
".tab-icon-overlay": "crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked",
".tab-label-container": "pinned,selected=visuallyselected,labeldirection",
".tab-label": "text=label,accesskey,fadein,pinned,selected=visuallyselected,attention",
".tab-icon-pip": "pictureinpicture",
".tab-icon-sound": "soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked,pictureinpicture",
".tab-close-button": "fadein,pinned,selected=visuallyselected",
};
}
get fragment() {
if (!this._fragment) {
this._fragment = MozXULElement.parseXULToFragment(`
<stack class="tab-stack" flex="1">
<vbox class="tab-background">
<hbox class="tab-line"></hbox>
<spacer flex="1" class="tab-background-inner"></spacer>
<hbox class="tab-bottom-line"></hbox>
</vbox>
<hbox class="tab-loading-burst"></hbox>
<hbox class="tab-content" align="center">
<hbox class="tab-throbber" layer="true"></hbox>
<hbox class="tab-icon-pending"></hbox>
<image class="tab-icon-image" validate="never" role="presentation"></image>
<image class="tab-sharing-icon-overlay" role="presentation"></image>
<image class="tab-icon-overlay" role="presentation"></image>
<hbox class="tab-label-container"
onoverflow="this.setAttribute('textoverflow', 'true');"
onunderflow="this.removeAttribute('textoverflow');"
flex="1">
<label class="tab-text tab-label" role="presentation"></label>
</hbox>
<image class="tab-icon-pip"
role="presentation"></image>
<image class="tab-icon-sound" role="presentation"></image>
<image class="tab-close-button close-icon" role="presentation"></image>
</hbox>
</stack>
`);
}
return this.ownerDocument.importNode(this._fragment, true);
}
connectedCallback() {
if (this._initialized) {
return;
}
this.textContent = "";
this.appendChild(this.fragment);
this.initializeAttributeInheritance();
this.setAttribute("context", "tabContextMenu");
this._initialized = true;
if (!("_lastAccessed" in this)) {
this.updateLastAccessed();
}
}
set _visuallySelected(val) {
if (val == (this.getAttribute("visuallyselected") == "true")) {
return val;
}
if (val) {
this.setAttribute("visuallyselected", "true");
} else {
this.removeAttribute("visuallyselected");
}
gBrowser._tabAttrModified(this, ["visuallyselected"]);
return val;
}
set _selected(val) {
// in e10s we want to only pseudo-select a tab before its rendering is done, so that
// the rest of the system knows that the tab is selected, but we don't want to update its
// visual status to selected until after we receive confirmation that its content has painted.
if (val)
this.setAttribute("selected", "true");
else
this.removeAttribute("selected");
// If we're non-e10s we should update the visual selection as well at the same time,
// *or* if we're e10s and the visually selected tab isn't changing, in which case the
// tab switcher code won't run and update anything else (like the before- and after-
// selected attributes).
if (!gMultiProcessBrowser || (val && this.hasAttribute("visuallyselected"))) {
this._visuallySelected = val;
}
return val;
}
get pinned() {
return this.getAttribute("pinned") == "true";
}
get hidden() {
return this.getAttribute("hidden") == "true";
}
get muted() {
return this.getAttribute("muted") == "true";
}
get multiselected() {
return this.getAttribute("multiselected") == "true";
}
get beforeMultiselected() {
return this.getAttribute("before-multiselected") == "true";
}
get userContextId() {
return this.hasAttribute("usercontextid") ?
parseInt(this.getAttribute("usercontextid")) :
0;
}
get soundPlaying() {
return this.getAttribute("soundplaying") == "true";
}
get pictureinpicture() {
return this.getAttribute("pictureinpicture") == "true";
}
get activeMediaBlocked() {
return this.getAttribute("activemedia-blocked") == "true";
}
get isEmpty() {
// Determines if a tab is "empty", usually used in the context of determining
// if it's ok to close the tab.
if (this.hasAttribute("busy"))
return false;
if (this.hasAttribute("customizemode"))
return false;
let browser = this.linkedBrowser;
if (!isBlankPageURL(browser.currentURI.spec))
return false;
if (!checkEmptyPageOrigin(browser))
return false;
if (browser.canGoForward || browser.canGoBack)
return false;
return true;
}
get lastAccessed() {
return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
}
get _overPlayingIcon() {
let iconVisible = this.hasAttribute("soundplaying") ||
this.hasAttribute("muted") ||
this.hasAttribute("activemedia-blocked");
let soundPlayingIcon = this.soundPlayingIcon;
let overlayIcon = this.overlayIcon;
return soundPlayingIcon && soundPlayingIcon.matches(":hover") ||
(overlayIcon && overlayIcon.matches(":hover") && iconVisible);
}
get soundPlayingIcon() {
return this.querySelector(".tab-icon-sound");
}
get overlayIcon() {
return this.querySelector(".tab-icon-overlay");
}
get throbber() {
return this.querySelector(".tab-throbber");
}
get iconImage() {
return this.querySelector(".tab-icon-image");
}
get sharingIcon() {
return this.querySelector(".tab-sharing-icon-overlay");
}
get textLabel() {
return this.querySelector(".tab-label");
}
get closeButton() {
return this.querySelector(".tab-close-button");
}
updateLastAccessed(aDate) {
this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
}
/**
* While it would make sense to track this in a field, the field will get nuked
* once the node is gone from the DOM, which causes us to think the tab is not
* closed, which causes us to make wrong decisions. So we use an expando instead.
* <field name="closing">false</field>
*/
_mouseenter() {
if (this.hidden || this.closing) {
return;
}
let tabContainer = this.parentNode;
let visibleTabs = tabContainer._getVisibleTabs();
let tabIndex = visibleTabs.indexOf(this);
if (this.selected)
tabContainer._handleTabSelect();
if (tabIndex == 0) {
tabContainer._beforeHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex - 1];
let separatedByScrollButton =
tabContainer.getAttribute("overflow") == "true" &&
candidate.pinned && !this.pinned;
if (!candidate.selected && !separatedByScrollButton) {
tabContainer._beforeHoveredTab = candidate;
candidate.setAttribute("beforehovered", "true");
}
}
if (tabIndex == visibleTabs.length - 1) {
tabContainer._afterHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex + 1];
if (!candidate.selected) {
tabContainer._afterHoveredTab = candidate;
candidate.setAttribute("afterhovered", "true");
}
}
tabContainer._hoveredTab = this;
if (this.linkedPanel && !this.selected) {
this.linkedBrowser.unselectedTabHover(true);
this.startUnselectedTabHoverTimer();
}
// Prepare connection to host beforehand.
SessionStore.speculativeConnectOnTabHover(this);
let tabToWarm = this;
if (this.mOverCloseButton) {
tabToWarm = gBrowser._findTabToBlurTo(this);
}
gBrowser.warmupTab(tabToWarm);
}
_mouseleave() {
let tabContainer = this.parentNode;
if (tabContainer._beforeHoveredTab) {
tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
tabContainer._beforeHoveredTab = null;
}
if (tabContainer._afterHoveredTab) {
tabContainer._afterHoveredTab.removeAttribute("afterhovered");
tabContainer._afterHoveredTab = null;
}
tabContainer._hoveredTab = null;
if (this.linkedPanel && !this.selected) {
this.linkedBrowser.unselectedTabHover(false);
this.cancelUnselectedTabHoverTimer();
}
}
startUnselectedTabHoverTimer() {
// Only record data when we need to.
if (!this.linkedBrowser.shouldHandleUnselectedTabHover) {
return;
}
if (!TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.start("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
if (this._hoverTabTimer) {
clearTimeout(this._hoverTabTimer);
this._hoverTabTimer = null;
}
}
cancelUnselectedTabHoverTimer() {
// Since we're listening "mouseout" event, instead of "mouseleave".
// Every time the cursor is moving from the tab to its child node (icon),
// it would dispatch "mouseout"(for tab) first and then dispatch
// "mouseover" (for icon, eg: close button, speaker icon) soon.
// It causes we would cancel present TelemetryStopwatch immediately
// when cursor is moving on the icon, and then start a new one.
// In order to avoid this situation, we could delay cancellation and
// remove it if we get "mouseover" within very short period.
this._hoverTabTimer = setTimeout(() => {
if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
}, 100);
}
finishUnselectedTabHoverTimer() {
// Stop timer when the tab is opened.
if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.finish("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
}
toggleMuteAudio(aMuteReason) {
let browser = this.linkedBrowser;
let modifiedAttrs = [];
let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
if (this.hasAttribute("activemedia-blocked")) {
this.removeAttribute("activemedia-blocked");
modifiedAttrs.push("activemedia-blocked");
browser.resumeMedia();
hist.add(3 /* unblockByClickingIcon */ );
} else {
if (browser.audioMuted) {
if (this.linkedPanel) {
// "Lazy Browser" should not invoke its unmute method
browser.unmute();
}
this.removeAttribute("muted");
hist.add(1 /* unmute */ );
} else {
if (this.linkedPanel) {
// "Lazy Browser" should not invoke its mute method
browser.mute();
}
this.setAttribute("muted", "true");
hist.add(0 /* mute */ );
}
this.muteReason = aMuteReason || null;
modifiedAttrs.push("muted");
}
gBrowser._tabAttrModified(this, modifiedAttrs);
}
setUserContextId(aUserContextId) {
if (aUserContextId) {
if (this.linkedBrowser) {
this.linkedBrowser.setAttribute("usercontextid", aUserContextId);
}
this.setAttribute("usercontextid", aUserContextId);
} else {
if (this.linkedBrowser) {
this.linkedBrowser.removeAttribute("usercontextid");
}
this.removeAttribute("usercontextid");
}
ContextualIdentityService.setTabStyle(this);
}
}
customElements.define("tabbrowser-tab", MozTabbrowserTab, {
extends: "tab",
});
}

View File

@@ -655,7 +655,8 @@ window._gBrowser = {
const animations =
Array.from(aTab.parentNode.getElementsByTagName("tab"))
.map(tab => {
const throbber = tab.throbber;
const throbber =
document.getAnonymousElementByAttribute(tab, "anonid", "tab-throbber");
return throbber ? throbber.getAnimations({ subtree: true }) : [];
})
.reduce((a, b) => a.concat(b))
@@ -2318,7 +2319,8 @@ window._gBrowser = {
let openerTab = ((openerBrowser && this.getTabForBrowser(openerBrowser)) ||
(relatedToCurrent && this.selectedTab));
var t = document.createXULElement("tab", { is: "tabbrowser-tab" });
var t = document.createXULElement("tab");
t.openerTab = openerTab;
aURI = aURI || "about:blank";
@@ -4226,8 +4228,8 @@ window._gBrowser = {
createTooltip(event) {
event.stopPropagation();
let tab = document.tooltipNode ? document.tooltipNode.closest("tab") : null;
if (!tab) {
var tab = document.tooltipNode;
if (!tab || tab.localName != "tab") {
event.preventDefault();
return;
}
@@ -5408,9 +5410,8 @@ var TabContextMenu = {
});
},
updateContextMenu(aPopupMenu) {
let tab = aPopupMenu.triggerNode && aPopupMenu.triggerNode.closest("tab");
this.contextTab = tab || gBrowser.selectedTab;
this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
aPopupMenu.triggerNode : gBrowser.selectedTab;
let disabled = gBrowser.tabs.length == 1;
let multiselectionContext = this.contextTab.multiselected;

View File

@@ -991,10 +991,7 @@
<parameter name="event"/>
<parameter name="isLink"/>
<body><![CDATA[
let tab = event.target;
while (tab && tab.localName != "tab") {
tab = tab.parentNode;
}
let tab = event.target.localName == "tab" ? event.target : null;
if (tab && isLink) {
let {width} = tab.getBoundingClientRect();
if (event.screenX < tab.screenX + width * .25 ||
@@ -1241,7 +1238,8 @@
return;
}
let tab = event.target ? event.target.closest("tab") : null;
let tab = event.target;
if (tab.getAttribute("fadein") == "true") {
if (tab._fullyOpen) {
this._updateCloseButtons();
@@ -1332,9 +1330,8 @@
return;
}
let tab = event.target ? event.target.closest("tab") : null;
if (tab) {
gBrowser.removeTab(tab, {
if (event.target.localName == "tab") {
gBrowser.removeTab(event.target, {
animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
});
@@ -1908,4 +1905,579 @@
]]></handler>
</handlers>
</binding>
<binding id="tabbrowser-tab"
extends="chrome://global/content/bindings/tabbox.xml#tab">
<content context="tabContextMenu">
<xul:stack class="tab-stack" flex="1">
<xul:vbox xbl:inherits="selected=visuallyselected,fadein,multiselected"
class="tab-background">
<xul:hbox xbl:inherits="selected=visuallyselected,multiselected,before-multiselected"
class="tab-line"/>
<xul:spacer flex="1" class="tab-background-inner"/>
<xul:hbox class="tab-bottom-line"/>
</xul:vbox>
<xul:hbox xbl:inherits="pinned,bursting,notselectedsinceload"
anonid="tab-loading-burst"
class="tab-loading-burst"/>
<xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
class="tab-content" align="center">
<xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
anonid="tab-throbber"
class="tab-throbber"
layer="true"/>
<xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon"
anonid="tab-icon-pending"
class="tab-icon-pending"/>
<xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
anonid="tab-icon-image"
class="tab-icon-image"
validate="never"
role="presentation"/>
<xul:image xbl:inherits="sharing,selected=visuallyselected,pinned"
anonid="sharing-icon"
class="tab-sharing-icon-overlay"
role="presentation"/>
<xul:image xbl:inherits="crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked"
anonid="overlay-icon"
class="tab-icon-overlay"
role="presentation"/>
<xul:hbox class="tab-label-container"
xbl:inherits="pinned,selected=visuallyselected,labeldirection"
onoverflow="this.setAttribute('textoverflow', 'true');"
onunderflow="this.removeAttribute('textoverflow');"
flex="1">
<xul:label class="tab-text tab-label" anonid="tab-label"
xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
role="presentation"/>
</xul:hbox>
<xul:image xbl:inherits="pictureinpicture"
class="tab-icon-pip"
role="presentation"/>
<xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected,activemedia-blocked,pictureinpicture"
anonid="soundplaying-icon"
class="tab-icon-sound"
role="presentation"/>
<xul:image anonid="close-button"
xbl:inherits="fadein,pinned,selected=visuallyselected"
class="tab-close-button close-icon"
role="presentation"/>
</xul:hbox>
</xul:stack>
</content>
<implementation>
<constructor><![CDATA[
if (!("_lastAccessed" in this)) {
this.updateLastAccessed();
}
]]></constructor>
<property name="_visuallySelected">
<setter>
<![CDATA[
if (val == (this.getAttribute("visuallyselected") == "true")) {
return val;
}
if (val) {
this.setAttribute("visuallyselected", "true");
} else {
this.removeAttribute("visuallyselected");
}
gBrowser._tabAttrModified(this, ["visuallyselected"]);
return val;
]]>
</setter>
</property>
<property name="_selected">
<setter>
<![CDATA[
// in e10s we want to only pseudo-select a tab before its rendering is done, so that
// the rest of the system knows that the tab is selected, but we don't want to update its
// visual status to selected until after we receive confirmation that its content has painted.
if (val)
this.setAttribute("selected", "true");
else
this.removeAttribute("selected");
// If we're non-e10s we should update the visual selection as well at the same time,
// *or* if we're e10s and the visually selected tab isn't changing, in which case the
// tab switcher code won't run and update anything else (like the before- and after-
// selected attributes).
if (!gMultiProcessBrowser || (val && this.hasAttribute("visuallyselected"))) {
this._visuallySelected = val;
}
return val;
]]>
</setter>
</property>
<field name="_selectedOnFirstMouseDown">false</field>
<property name="pinned" readonly="true">
<getter>
return this.getAttribute("pinned") == "true";
</getter>
</property>
<property name="hidden" readonly="true">
<getter>
return this.getAttribute("hidden") == "true";
</getter>
</property>
<property name="muted" readonly="true">
<getter>
return this.getAttribute("muted") == "true";
</getter>
</property>
<property name="multiselected" readonly="true">
<getter>
return this.getAttribute("multiselected") == "true";
</getter>
</property>
<property name="beforeMultiselected" readonly="true">
<getter>
return this.getAttribute("before-multiselected") == "true";
</getter>
</property>
<!--
Describes how the tab ended up in this mute state. May be any of:
- undefined: The tabs mute state has never changed.
- null: The mute state was last changed through the UI.
- Any string: The ID was changed through an extension API. The string
must be the ID of the extension which changed it.
-->
<field name="muteReason">undefined</field>
<property name="userContextId" readonly="true">
<getter>
return this.hasAttribute("usercontextid")
? parseInt(this.getAttribute("usercontextid"))
: 0;
</getter>
</property>
<property name="soundPlaying" readonly="true">
<getter>
return this.getAttribute("soundplaying") == "true";
</getter>
</property>
<property name="pictureinpicture" readonly="true">
<getter>
return this.getAttribute("pictureinpicture") == "true";
</getter>
</property>
<property name="activeMediaBlocked" readonly="true">
<getter>
return this.getAttribute("activemedia-blocked") == "true";
</getter>
</property>
<property name="isEmpty" readonly="true">
<getter>
// Determines if a tab is "empty", usually used in the context of determining
// if it's ok to close the tab.
if (this.hasAttribute("busy"))
return false;
if (this.hasAttribute("customizemode"))
return false;
let browser = this.linkedBrowser;
if (!isBlankPageURL(browser.currentURI.spec))
return false;
if (!checkEmptyPageOrigin(browser))
return false;
if (browser.canGoForward || browser.canGoBack)
return false;
return true;
</getter>
</property>
<property name="lastAccessed">
<getter>
return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
</getter>
</property>
<method name="updateLastAccessed">
<parameter name="aDate"/>
<body><![CDATA[
this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
]]></body>
</method>
<field name="mOverCloseButton">false</field>
<property name="_overPlayingIcon" readonly="true">
<getter><![CDATA[
let iconVisible = this.hasAttribute("soundplaying") ||
this.hasAttribute("muted") ||
this.hasAttribute("activemedia-blocked");
let soundPlayingIcon =
document.getAnonymousElementByAttribute(this, "anonid", "soundplaying-icon");
let overlayIcon =
document.getAnonymousElementByAttribute(this, "anonid", "overlay-icon");
return soundPlayingIcon && soundPlayingIcon.matches(":hover") ||
(overlayIcon && overlayIcon.matches(":hover") && iconVisible);
]]></getter>
</property>
<field name="mCorrespondingMenuitem">null</field>
<!--
While it would make sense to track this in a field, the field will get nuked
once the node is gone from the DOM, which causes us to think the tab is not
closed, which causes us to make wrong decisions. So we use an expando instead.
<field name="closing">false</field>
-->
<method name="_mouseenter">
<body><![CDATA[
if (this.hidden || this.closing) {
return;
}
let tabContainer = this.parentNode;
let visibleTabs = tabContainer._getVisibleTabs();
let tabIndex = visibleTabs.indexOf(this);
if (this.selected)
tabContainer._handleTabSelect();
if (tabIndex == 0) {
tabContainer._beforeHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex - 1];
let separatedByScrollButton =
tabContainer.getAttribute("overflow") == "true" &&
candidate.pinned && !this.pinned;
if (!candidate.selected && !separatedByScrollButton) {
tabContainer._beforeHoveredTab = candidate;
candidate.setAttribute("beforehovered", "true");
}
}
if (tabIndex == visibleTabs.length - 1) {
tabContainer._afterHoveredTab = null;
} else {
let candidate = visibleTabs[tabIndex + 1];
if (!candidate.selected) {
tabContainer._afterHoveredTab = candidate;
candidate.setAttribute("afterhovered", "true");
}
}
tabContainer._hoveredTab = this;
if (this.linkedPanel && !this.selected) {
this.linkedBrowser.unselectedTabHover(true);
this.startUnselectedTabHoverTimer();
}
// Prepare connection to host beforehand.
SessionStore.speculativeConnectOnTabHover(this);
let tabToWarm = this;
if (this.mOverCloseButton) {
tabToWarm = gBrowser._findTabToBlurTo(this);
}
gBrowser.warmupTab(tabToWarm);
]]></body>
</method>
<method name="_mouseleave">
<body><![CDATA[
let tabContainer = this.parentNode;
if (tabContainer._beforeHoveredTab) {
tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
tabContainer._beforeHoveredTab = null;
}
if (tabContainer._afterHoveredTab) {
tabContainer._afterHoveredTab.removeAttribute("afterhovered");
tabContainer._afterHoveredTab = null;
}
tabContainer._hoveredTab = null;
if (this.linkedPanel && !this.selected) {
this.linkedBrowser.unselectedTabHover(false);
this.cancelUnselectedTabHoverTimer();
}
]]></body>
</method>
<method name="startUnselectedTabHoverTimer">
<body><![CDATA[
// Only record data when we need to.
if (!this.linkedBrowser.shouldHandleUnselectedTabHover) {
return;
}
if (!TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.start("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
if (this._hoverTabTimer) {
clearTimeout(this._hoverTabTimer);
this._hoverTabTimer = null;
}
]]></body>
</method>
<method name="cancelUnselectedTabHoverTimer">
<body><![CDATA[
// Since we're listening "mouseout" event, instead of "mouseleave".
// Every time the cursor is moving from the tab to its child node (icon),
// it would dispatch "mouseout"(for tab) first and then dispatch
// "mouseover" (for icon, eg: close button, speaker icon) soon.
// It causes we would cancel present TelemetryStopwatch immediately
// when cursor is moving on the icon, and then start a new one.
// In order to avoid this situation, we could delay cancellation and
// remove it if we get "mouseover" within very short period.
this._hoverTabTimer = setTimeout(() => {
if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
}, 100);
]]></body>
</method>
<method name="finishUnselectedTabHoverTimer">
<body><![CDATA[
// Stop timer when the tab is opened.
if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) {
TelemetryStopwatch.finish("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this);
}
]]></body>
</method>
<method name="toggleMuteAudio">
<parameter name="aMuteReason"/>
<body>
<![CDATA[
let browser = this.linkedBrowser;
let modifiedAttrs = [];
let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
if (this.hasAttribute("activemedia-blocked")) {
this.removeAttribute("activemedia-blocked");
modifiedAttrs.push("activemedia-blocked");
browser.resumeMedia();
hist.add(3 /* unblockByClickingIcon */);
} else {
if (browser.audioMuted) {
if (this.linkedPanel) {
// "Lazy Browser" should not invoke its unmute method
browser.unmute();
}
this.removeAttribute("muted");
hist.add(1 /* unmute */);
} else {
if (this.linkedPanel) {
// "Lazy Browser" should not invoke its mute method
browser.mute();
}
this.setAttribute("muted", "true");
hist.add(0 /* mute */);
}
this.muteReason = aMuteReason || null;
modifiedAttrs.push("muted");
}
gBrowser._tabAttrModified(this, modifiedAttrs);
]]>
</body>
</method>
<method name="setUserContextId">
<parameter name="aUserContextId"/>
<body>
<![CDATA[
if (aUserContextId) {
if (this.linkedBrowser) {
this.linkedBrowser.setAttribute("usercontextid", aUserContextId);
}
this.setAttribute("usercontextid", aUserContextId);
} else {
if (this.linkedBrowser) {
this.linkedBrowser.removeAttribute("usercontextid");
}
this.removeAttribute("usercontextid");
}
ContextualIdentityService.setTabStyle(this);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="mouseover"><![CDATA[
if (event.originalTarget.getAttribute("anonid") == "close-button") {
this.mOverCloseButton = true;
}
this._mouseenter();
]]></handler>
<handler event="mouseout"><![CDATA[
if (event.originalTarget.getAttribute("anonid") == "close-button") {
this.mOverCloseButton = false;
}
this._mouseleave();
]]></handler>
<handler event="dragstart" phase="capturing">
this.style.MozUserFocus = "";
</handler>
<handler event="dragstart"><![CDATA[
if (this.mOverCloseButton) {
event.stopPropagation();
}
]]></handler>
<handler event="mousedown" phase="capturing">
<![CDATA[
let tabContainer = this.parentNode;
if (tabContainer._closeTabByDblclick &&
event.button == 0 &&
event.detail == 1) {
this._selectedOnFirstMouseDown = this.selected;
}
if (this.selected) {
this.style.MozUserFocus = "ignore";
} else if (event.originalTarget.classList.contains("tab-close-button") ||
event.originalTarget.classList.contains("tab-icon-sound") ||
event.originalTarget.classList.contains("tab-icon-overlay")) {
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
}
if (event.button == 1) {
gBrowser.warmupTab(gBrowser._findTabToBlurTo(this));
}
if (event.button == 0 && tabContainer._multiselectEnabled) {
let shiftKey = event.shiftKey;
let accelKey = event.getModifierState("Accel");
if (shiftKey) {
const lastSelectedTab = gBrowser.lastMultiSelectedTab;
if (!accelKey) {
gBrowser.selectedTab = lastSelectedTab;
// Make sure selection is cleared when tab-switch doesn't happen.
gBrowser.clearMultiSelectedTabs(false);
}
gBrowser.addRangeToMultiSelectedTabs(lastSelectedTab, this);
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
} else if (accelKey) {
// Ctrl (Cmd for mac) key is pressed
if (this.multiselected) {
gBrowser.removeFromMultiSelectedTabs(this, true);
} else if (this != gBrowser.selectedTab) {
gBrowser.addToMultiSelectedTabs(this, false);
gBrowser.lastMultiSelectedTab = this;
}
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
} else if (!this.selected && this.multiselected) {
gBrowser.lockClearMultiSelectionOnce();
}
}
]]>
</handler>
<handler event="mouseup">
// Make sure that clear-selection is released.
// Otherwise selection using Shift key may be broken.
gBrowser.unlockClearMultiSelection();
this.style.MozUserFocus = "";
</handler>
<handler event="click" button="0"><![CDATA[
if (event.getModifierState("Accel") || event.shiftKey) {
return;
}
if (gBrowser.multiSelectedTabsCount > 0 &&
!event.originalTarget.classList.contains("tab-close-button") &&
!event.originalTarget.classList.contains("tab-icon-sound") &&
!event.originalTarget.classList.contains("tab-icon-overlay")) {
// Tabs were previously multi-selected and user clicks on a tab
// without holding Ctrl/Cmd Key
// Force positional attributes to update when the
// target (of the click) is the "active" tab.
let updatePositionalAttr = gBrowser.selectedTab == this;
gBrowser.clearMultiSelectedTabs(updatePositionalAttr);
}
if (event.originalTarget.classList.contains("tab-icon-sound") ||
(event.originalTarget.classList.contains("tab-icon-overlay") &&
(event.originalTarget.hasAttribute("soundplaying") ||
event.originalTarget.hasAttribute("muted") ||
event.originalTarget.hasAttribute("activemedia-blocked")))) {
if (this.multiselected) {
gBrowser.toggleMuteAudioOnMultiSelectedTabs(this);
} else {
this.toggleMuteAudio();
}
return;
}
if (event.originalTarget.getAttribute("anonid") == "close-button") {
if (this.multiselected) {
gBrowser.removeMultiSelectedTabs();
} else {
gBrowser.removeTab(this, {
animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
});
}
// This enables double-click protection for the tab container
// (see tabbrowser-tabs 'click' handler).
gBrowser.tabContainer._blockDblClick = true;
}
]]></handler>
<handler event="dblclick" button="0" phase="capturing"><![CDATA[
// for the one-close-button case
if (event.originalTarget.getAttribute("anonid") == "close-button") {
event.stopPropagation();
}
let tabContainer = this.parentNode;
if (tabContainer._closeTabByDblclick &&
this._selectedOnFirstMouseDown &&
this.selected &&
!(event.originalTarget.classList.contains("tab-icon-sound") ||
event.originalTarget.classList.contains("tab-icon-overlay"))) {
gBrowser.removeTab(this, {
animate: true,
byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE,
});
}
]]></handler>
<handler event="animationend">
<![CDATA[
if (event.originalTarget.getAttribute("anonid") == "tab-loading-burst") {
this.removeAttribute("bursting");
}
]]>
</handler>
</handlers>
</binding>
</bindings>

View File

@@ -38,7 +38,7 @@ add_task(async () => {
await waitForAttributeChange(tab, "label");
ok(tab.hasAttribute("busy"), "Should have seen the busy attribute");
let label = tab.textLabel;
let label = document.getAnonymousElementByAttribute(tab, "anonid", "tab-label");
let bounds = label.getBoundingClientRect();
await waitForAttributeChange(tab, "busy");
@@ -60,7 +60,7 @@ add_task(async () => {
is(icon.iconURL, "http://example.com/favicon.ico");
let tab = gBrowser.getTabForBrowser(browser);
let label = tab.textLabel;
let label = document.getAnonymousElementByAttribute(tab, "anonid", "tab-label");
let bounds = label.getBoundingClientRect();
await ContentTask.spawn(browser, null, () => {

View File

@@ -34,7 +34,7 @@ add_task(async function closeLastTabInWindow() {
let windowClosedPromise = BrowserTestUtils.domWindowClosed(newWin);
expectingDialog = true;
// close tab:
firstTab.closeButton.click();
document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
await windowClosedPromise;
ok(!expectingDialog, "There should have been a dialog.");
ok(newWin.closed, "Window should be closed.");
@@ -63,13 +63,13 @@ add_task(async function closeWindoWithSingleTabTwice() {
expectingDialog = true;
wantToClose = false;
let firstDialogShownPromise = new Promise((resolve, reject) => { resolveDialogPromise = resolve; });
firstTab.closeButton.click();
document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
await firstDialogShownPromise;
info("Got initial dialog, now trying again");
expectingDialog = true;
wantToClose = true;
resolveDialogPromise = null;
firstTab.closeButton.click();
document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
await windowClosedPromise;
ok(!expectingDialog, "There should have been a dialog.");
ok(newWin.closed, "Window should be closed.");

View File

@@ -61,10 +61,10 @@ add_task(async function() {
doCompletion();
});
// Click again:
testTab.closeButton.click();
document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click();
});
// Click once:
testTab.closeButton.click();
document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click();
});
await TestUtils.waitForCondition(() => !testTab.parentNode);
ok(!testTab.parentNode, "Tab should be closed completely");

View File

@@ -31,7 +31,7 @@ add_task(async function() {
info("Waiting for the load in that tab to finish");
await secondTabLoadedPromise;
let closeBtn = secondTab.closeButton;
let closeBtn = document.getAnonymousElementByAttribute(secondTab, "anonid", "close-button");
info("closing second tab (which will self-close in beforeunload)");
closeBtn.click();
ok(secondTab.closing, "Second tab should be marked as closing synchronously.");

View File

@@ -46,7 +46,6 @@ add_task(async function() {
"anonid", "moz-input-box").getBoundingClientRect();
let menuButtonRect =
document.getElementById("PanelUI-menu-button").getBoundingClientRect();
let firstTabRect = gBrowser.selectedTab.getBoundingClientRect();
let frameExpectations = {
filter: rects => rects.filter(r => !(
// We expect the menu button to get into the active state.
@@ -61,11 +60,6 @@ add_task(async function() {
r.x1 >= textBoxRect.left && r.x2 <= textBoxRect.right &&
r.y1 >= textBoxRect.top && r.y2 <= textBoxRect.bottom,
},
{name: "bug 1547341 - a first tab gets drawn early",
condition: r =>
r.x1 >= firstTabRect.left && r.x2 <= firstTabRect.right &&
r.y1 >= firstTabRect.top && r.y2 <= firstTabRect.bottom,
},
],
};

View File

@@ -31,12 +31,11 @@ add_task(async function() {
let tabStripRect = gBrowser.tabContainer.arrowScrollbox.getBoundingClientRect();
let firstTabRect = gBrowser.selectedTab.getBoundingClientRect();
let firstTabLabelRect = gBrowser.selectedTab.textLabel.getBoundingClientRect();
let firstTabLabelRect =
document.getAnonymousElementByAttribute(gBrowser.selectedTab, "anonid", "tab-label")
.getBoundingClientRect();
let textBoxRect = document.getAnonymousElementByAttribute(gURLBar.textbox,
"anonid", "moz-input-box").getBoundingClientRect();
let historyDropmarkerRect = document.getAnonymousElementByAttribute(
gURLBar.textbox, "anonid", "historydropmarker").getBoundingClientRect();
let inRange = (val, min, max) => min <= val && val <= max;
// Add a reflow observer and open a new tab.
@@ -94,12 +93,6 @@ add_task(async function() {
r.y1 >= firstTabLabelRect.y &&
r.y2 <= firstTabLabelRect.bottom,
},
{name: "bug 1547341 - addressbar history dropmarker is shown",
condition: r => r.x1 >= historyDropmarkerRect.x &&
r.x2 <= historyDropmarkerRect.right &&
r.y1 >= historyDropmarkerRect.y &&
r.y2 <= historyDropmarkerRect.bottom,
},
],
},
});

View File

@@ -111,7 +111,8 @@ async function test_muting_using_menu(tab, expectMuted) {
}
async function test_playing_icon_on_tab(tab, browser, isPinned) {
let icon = isPinned ? tab.overlayIcon : tab.soundPlayingIcon;
let icon = document.getAnonymousElementByAttribute(tab, "anonid",
isPinned ? "overlay-icon" : "soundplaying-icon");
let isActiveTab = tab === gBrowser.selectedTab;
await play(tab);
@@ -240,7 +241,9 @@ async function test_swapped_browser_while_playing(oldTab, newBrowser) {
is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
await test_tooltip(newTab.soundPlayingIcon, "Unmute tab", true);
let icon = document.getAnonymousElementByAttribute(newTab, "anonid",
"soundplaying-icon");
await test_tooltip(icon, "Unmute tab", true);
}
async function test_swapped_browser_while_not_playing(oldTab, newBrowser) {
@@ -278,14 +281,18 @@ async function test_swapped_browser_while_not_playing(oldTab, newBrowser) {
is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
await test_tooltip(newTab.soundPlayingIcon, "Unmute tab", true);
let icon = document.getAnonymousElementByAttribute(newTab, "anonid",
"soundplaying-icon");
await test_tooltip(icon, "Unmute tab", true);
}
async function test_browser_swapping(tab, browser) {
// First, test swapping with a playing but muted tab.
await play(tab);
await test_mute_tab(tab, tab.soundPlayingIcon, true);
let icon = document.getAnonymousElementByAttribute(tab, "anonid",
"soundplaying-icon");
await test_mute_tab(tab, icon, true);
await BrowserTestUtils.withNewTab({
gBrowser,
@@ -320,7 +327,7 @@ async function test_click_on_pinned_tab_after_mute() {
await play(tab);
// Mute the tab.
let icon = tab.overlayIcon;
let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon");
await test_mute_tab(tab, icon, true);
// Pause playback and wait for it to finish.
@@ -330,7 +337,8 @@ async function test_click_on_pinned_tab_after_mute() {
await test_mute_tab(tab, icon, false);
// Now click on the tab.
EventUtils.synthesizeMouseAtCenter(tab.iconImage, {button: 0});
let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image");
EventUtils.synthesizeMouseAtCenter(image, {button: 0});
is(tab, gBrowser.selectedTab, "Tab switch should be successful");

View File

@@ -29,7 +29,7 @@ add_task(async function usingTabCloseButton() {
is(gBrowser.selectedTab, tab1, "Tab1 is active");
// Closing a tab which is not multiselected
let tab4CloseBtn = tab4.closeButton;
let tab4CloseBtn = document.getAnonymousElementByAttribute(tab4, "anonid", "close-button");
let tab4Closing = BrowserTestUtils.waitForTabClosing(tab4);
tab4.mOverCloseButton = true;
@@ -48,7 +48,7 @@ add_task(async function usingTabCloseButton() {
is(gBrowser.multiSelectedTabsCount, 2, "Two multiselected tabs");
// Closing a selected tab
let tab2CloseBtn = tab2.closeButton;
let tab2CloseBtn = document.getAnonymousElementByAttribute(tab2, "anonid", "close-button");
tab2.mOverCloseButton = true;
let tab1Closing = BrowserTestUtils.waitForTabClosing(tab1);
let tab2Closing = BrowserTestUtils.waitForTabClosing(tab2);

View File

@@ -55,7 +55,7 @@ add_task(async function muteTabs_usingButton() {
}
// Mute tab0 which is not multiselected, thus other tabs muted state should not be affected
let tab0MuteAudioBtn = tab0.soundPlayingIcon;
let tab0MuteAudioBtn = document.getAnonymousElementByAttribute(tab0, "anonid", "soundplaying-icon");
await test_mute_tab(tab0, tab0MuteAudioBtn, true);
ok(muted(tab0), "Tab0 is muted");
@@ -85,7 +85,7 @@ add_task(async function muteTabs_usingButton() {
// b) unmuted tabs (tab1, tab3) will become muted.
// b) media-blocked tabs (tab2) will remain media-blocked.
// However tab4 (unmuted) which is not multiselected should not be affected.
let tab1MuteAudioBtn = tab1.soundPlayingIcon;
let tab1MuteAudioBtn = document.getAnonymousElementByAttribute(tab1, "anonid", "soundplaying-icon");
await test_mute_tab(tab1, tab1MuteAudioBtn, true);
// Check mute state
@@ -141,7 +141,7 @@ add_task(async function unmuteTabs_usingButton() {
// b) unmuted tabs (tab0) will remain unmuted.
// b) media-blocked tabs (tab1, tab2) will get playing. (media not blocked anymore)
// However tab4 (muted) which is not multiselected should not be affected.
let tab3MuteAudioBtn = tab3.soundPlayingIcon;
let tab3MuteAudioBtn = document.getAnonymousElementByAttribute(tab3, "anonid", "soundplaying-icon");
await test_mute_tab(tab3, tab3MuteAudioBtn, false);
ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is unmuted and not media-blocked");
@@ -249,7 +249,7 @@ add_task(async function playTabs_usingButton() {
// b) unmuted tabs (tab3) will remain unmuted.
// b) media-blocked tabs (tab1, tab2) will get playing. (media not blocked anymore)
// However tab4 (muted) which is not multiselected should not be affected.
let tab2MuteAudioBtn = tab2.soundPlayingIcon;
let tab2MuteAudioBtn = document.getAnonymousElementByAttribute(tab2, "anonid", "soundplaying-icon");
await test_mute_tab(tab2, tab2MuteAudioBtn, false);
ok(!muted(tab0) && !activeMediaBlocked(tab0), "Tab0 is unmuted and not activemedia-blocked");

View File

@@ -53,7 +53,7 @@ async function overflowTabs() {
function getLastCloseButton() {
let lastTab = gBrowser.tabs[gBrowser.tabs.length - 1];
return lastTab.closeButton;
return document.getAnonymousElementByAttribute(lastTab, "anonid", "close-button");
}
function getLastCloseButtonLocation() {

View File

@@ -33,7 +33,8 @@ var gTests = [
let tab = gBrowser.selectedTab;
is(tab.getAttribute("sharing"), aSharing,
"the tab has the attribute to show the " + aSharing + " icon");
let icon = tab.sharingIcon;
let icon =
document.getAnonymousElementByAttribute(tab, "anonid", "sharing-icon");
is(window.getComputedStyle(icon).display, "none",
"the animated sharing icon of the tab is hidden");

View File

@@ -95,7 +95,6 @@ browser.jar:
content/browser/contentSearchUI.css (content/contentSearchUI.css)
content/browser/tabbrowser.css (content/tabbrowser.css)
content/browser/tabbrowser.js (content/tabbrowser.js)
content/browser/tabbrowser-tab.js (content/tabbrowser-tab.js)
content/browser/tabbrowser.xml (content/tabbrowser.xml)
* content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
content/browser/utilityOverlay.js (content/utilityOverlay.js)

View File

@@ -1065,11 +1065,8 @@ const menuTracker = {
gMenuBuilder.build({menu, tab, pageUrl, inToolsMenu: true});
}
if (menu.id === "tabContextMenu") {
let trigger = menu.triggerNode;
while (trigger && trigger.localName != "tab") {
trigger = trigger.parentNode;
}
const tab = trigger || tabTracker.activeTab;
const trigger = menu.triggerNode;
const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab;
const pageUrl = tab.linkedBrowser.currentURI.spec;
gMenuBuilder.build({menu, tab, pageUrl, onTab: true});
}

View File

@@ -172,7 +172,9 @@ var UITour = {
["selectedTabIcon", {
query: (aDocument) => {
let selectedtab = aDocument.defaultView.gBrowser.selectedTab;
let element = selectedtab.iconImage;
let element = aDocument.getAnonymousElementByAttribute(selectedtab,
"anonid",
"tab-icon-image");
if (!element || !UITour.isElementVisible(element)) {
return null;
}

View File

@@ -233,7 +233,7 @@ class Tab(UIBaseLib):
:returns: Reference to the tab close button.
"""
return self.tab_element.find_element(By.CSS_SELECTOR, '.tab-close-button')
return self.tab_element.find_element(By.ANON_ATTRIBUTE, {'anonid': 'close-button'})
@property
def tab_element(self):

View File

@@ -21,7 +21,7 @@ add_task(async function test_support_tab_line() {
info("Checking selected tab line color");
let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
let line = selectedTab.querySelector(".tab-line");
let line = document.getAnonymousElementByAttribute(selectedTab, "class", "tab-line");
Assert.equal(window.getComputedStyle(line).backgroundColor,
`rgb(${hexToRGB(TAB_LINE_COLOR).join(", ")})`,
"Tab line should have theme color");

View File

@@ -33,7 +33,7 @@ add_task(async function test_support_tab_loading_filling() {
selectedTab.setAttribute("busy", "true");
selectedTab.setAttribute("progress", "true");
let throbber = selectedTab.throbber;
let throbber = document.getAnonymousElementByAttribute(selectedTab, "class", "tab-throbber");
Assert.equal(window.getComputedStyle(throbber, "::before").fill,
`rgb(${hexToRGB(TAB_LOADING_COLOR).join(", ")})`,
"Throbber is filled with theme color");

View File

@@ -22,10 +22,10 @@ add_task(async function test_tab_background_color_property() {
info("Checking selected tab color");
let openTab = document.querySelector(".tabbrowser-tab[visuallyselected=true]");
let openTabBackground = openTab.querySelector(".tab-background");
let openTabBackground = document.getAnonymousElementByAttribute(openTab, "class", "tab-background");
let selectedTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
let selectedTabBackground = selectedTab.querySelector(".tab-background");
let selectedTabBackground = document.getAnonymousElementByAttribute(selectedTab, "class", "tab-background");
let openTabGradient = window.getComputedStyle(openTabBackground).getPropertyValue("background-image");
let selectedTabGradient = window.getComputedStyle(selectedTabBackground).getPropertyValue("background-image");

View File

@@ -62,7 +62,7 @@ add_task(async function test_reader_button() {
let readerUrl = gBrowser.selectedBrowser.currentURI.spec;
ok(readerUrl.startsWith("about:reader"), "about:reader loaded after clicking reader mode button");
is_element_visible(readerButton, "Reader mode button is present on about:reader");
let iconEl = tab.iconImage;
let iconEl = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image");
await TestUtils.waitForCondition(() => iconEl.getBoundingClientRect().width != 0);
is_element_visible(iconEl, "Favicon should be visible");
is(iconEl.src, favicon, "Correct favicon should be loaded");

View File

@@ -8,7 +8,7 @@ var SuspendedType = {
};
async function click_unblock_icon(tab) {
let icon = tab.soundPlayingIcon;
let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon");
await hover_icon(icon, document.getElementById("tabbrowser-tab-tooltip"));
EventUtils.synthesizeMouseAtCenter(icon, {button: 0});

View File

@@ -1,7 +1,7 @@
const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_plugIn.html";
async function click_icon(tab) {
let icon = tab.soundPlayingIcon;
let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon");
await hover_icon(icon, document.getElementById("tabbrowser-tab-tooltip"));
EventUtils.synthesizeMouseAtCenter(icon, {button: 0});

View File

@@ -8,7 +8,7 @@ if (!gMultiProcessBrowser) {
const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_webAudio.html";
async function click_icon(tab) {
let icon = tab.soundPlayingIcon;
let icon = document.getAnonymousElementByAttribute(tab, "anonid", "soundplaying-icon");
await hover_icon(icon, document.getElementById("tabbrowser-tab-tooltip"));
EventUtils.synthesizeMouseAtCenter(icon, {button: 0});

View File

@@ -250,164 +250,4 @@ class MozTabpanels extends MozXULElement {
MozXULElement.implementCustomInterface(MozTabpanels, [Ci.nsIDOMXULRelatedElement]);
customElements.define("tabpanels", MozTabpanels);
MozElements.MozTab = class MozTab extends MozElements.BaseText {
constructor() {
super();
this.addEventListener("mousedown", (event) => {
if (event.button != 0 || this.disabled) {
return;
}
this.parentNode.ariaFocusedItem = null;
if (this != this.parentNode.selectedItem) { // Not selected yet
let stopwatchid = this.parentNode.getAttribute("stopwatchid");
if (stopwatchid) {
TelemetryStopwatch.start(stopwatchid);
}
// 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);
var isTabFocused = false;
try {
isTabFocused = (document.commandDispatcher.focusedElement == this);
} catch (e) {}
// Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
// focus the tab; we only want tabs to be focusable by the mouse if
// they are already focused. After a short timeout we'll reset
// '-moz-user-focus' so that tabs can be focused by keyboard again.
if (!isTabFocused) {
this.setAttribute("ignorefocus", "true");
setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
}
if (stopwatchid) {
TelemetryStopwatch.finish(stopwatchid);
}
}
// Otherwise this tab is already selected and we will fall
// through to mousedown behavior which sets focus on the current tab,
// Only a click on an already selected tab should focus the tab itself.
});
this.addEventListener("keydown", (event) => {
if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) {
return;
}
switch (event.keyCode) {
case KeyEvent.DOM_VK_LEFT: {
let direction = window.getComputedStyle(this.parentNode).direction;
this.parentNode.advanceSelectedTab(direction == "ltr" ? -1 : 1,
this.arrowKeysShouldWrap);
event.preventDefault();
} break;
case KeyEvent.DOM_VK_RIGHT: {
let direction = window.getComputedStyle(this.parentNode).direction;
this.parentNode.advanceSelectedTab(direction == "ltr" ? 1 : -1,
this.arrowKeysShouldWrap);
event.preventDefault();
} break;
case KeyEvent.DOM_VK_UP:
this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
event.preventDefault();
break;
case KeyEvent.DOM_VK_DOWN:
this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
event.preventDefault();
break;
case KeyEvent.DOM_VK_HOME:
this.parentNode._selectNewTab(this.parentNode.children[0]);
event.preventDefault();
break;
case KeyEvent.DOM_VK_END:
let tabs = this.parentNode.children;
this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
event.preventDefault();
break;
}
});
this.arrowKeysShouldWrap = /Mac/.test(navigator.platform);
}
static get inheritedAttributes() {
return {
".tab-middle": "align,dir,pack,orient,selected,visuallyselected",
".tab-icon": "validate,src=image",
".tab-text": "value=label,accesskey,crop,disabled",
};
}
get fragment() {
if (!this._fragment) {
this._fragment = MozXULElement.parseXULToFragment(`
<hbox class="tab-middle box-inherit" flex="1">
<image class="tab-icon" role="presentation"></image>
<label class="tab-text" flex="1" role="presentation"></label>
</hbox>
`);
}
return this.ownerDocument.importNode(this._fragment, true);
}
connectedCallback() {
if (!this._initialized) {
this.textContent = "";
this.appendChild(this.fragment);
this.initializeAttributeInheritance();
this._initialized = true;
}
}
set value(val) {
this.setAttribute("value", val);
return val;
}
get value() {
return this.getAttribute("value");
}
get control() {
var parent = this.parentNode;
return (parent.localName == "tabs") ? parent : null;
}
get selected() {
return this.getAttribute("selected") == "true";
}
set _selected(val) {
if (val) {
this.setAttribute("selected", "true");
this.setAttribute("visuallyselected", "true");
} else {
this.removeAttribute("selected");
this.removeAttribute("visuallyselected");
}
return val;
}
set linkedPanel(val) {
this.setAttribute("linkedpanel", val);
}
get linkedPanel() {
return this.getAttribute("linkedpanel");
}
};
MozXULElement.implementCustomInterface(MozElements.MozTab, [Ci.nsIDOMXULSelectControlItemElement]);
customElements.define("tab", MozElements.MozTab);
}

View File

@@ -374,4 +374,139 @@
</handler>
</handlers>
</binding>
<binding id="tab"
extends="chrome://global/content/bindings/general.xml#basetext">
<content>
<xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1">
<xul:image class="tab-icon"
xbl:inherits="validate,src=image"
role="presentation"/>
<xul:label class="tab-text"
xbl:inherits="value=label,accesskey,crop,disabled"
flex="1"
role="presentation"/>
</xul:hbox>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<property name="value" onset="this.setAttribute('value', val); return val;"
onget="return this.getAttribute('value');"/>
<property name="control" readonly="true">
<getter>
<![CDATA[
var parent = this.parentNode;
return (parent.localName == "tabs") ? parent : null;
]]>
</getter>
</property>
<property name="selected" readonly="true"
onget="return this.getAttribute('selected') == 'true';"/>
<property name="_selected">
<setter><![CDATA[
if (val) {
this.setAttribute("selected", "true");
this.setAttribute("visuallyselected", "true");
} else {
this.removeAttribute("selected");
this.removeAttribute("visuallyselected");
}
return val;
]]></setter>
</property>
<property name="linkedPanel" onget="return this.getAttribute('linkedpanel')"
onset="this.setAttribute('linkedpanel', val); return val;"/>
<field name="arrowKeysShouldWrap" readonly="true">
/Mac/.test(navigator.platform)
</field>
</implementation>
<handlers>
<handler event="mousedown" button="0">
<![CDATA[
if (this.disabled)
return;
this.parentNode.ariaFocusedItem = null;
if (this != this.parentNode.selectedItem) { // Not selected yet
let stopwatchid = this.parentNode.getAttribute("stopwatchid");
if (stopwatchid) {
TelemetryStopwatch.start(stopwatchid);
}
// 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);
var isTabFocused = false;
try {
isTabFocused = (document.commandDispatcher.focusedElement == this);
} catch (e) {}
// Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
// focus the tab; we only want tabs to be focusable by the mouse if
// they are already focused. After a short timeout we'll reset
// '-moz-user-focus' so that tabs can be focused by keyboard again.
if (!isTabFocused) {
this.setAttribute("ignorefocus", "true");
setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
}
if (stopwatchid) {
TelemetryStopwatch.finish(stopwatchid);
}
}
// Otherwise this tab is already selected and we will fall
// through to mousedown behavior which sets focus on the current tab,
// Only a click on an already selected tab should focus the tab itself.
]]>
</handler>
<handler event="keydown" keycode="VK_LEFT" preventdefault="true">
<![CDATA[
var direction = window.getComputedStyle(this.parentNode).direction;
this.parentNode.advanceSelectedTab(direction == "ltr" ? -1 : 1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_RIGHT" preventdefault="true">
<![CDATA[
var direction = window.getComputedStyle(this.parentNode).direction;
this.parentNode.advanceSelectedTab(direction == "ltr" ? 1 : -1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_UP" preventdefault="true">
<![CDATA[
this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_DOWN" preventdefault="true">
<![CDATA[
this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
]]>
</handler>
<handler event="keydown" keycode="VK_HOME" preventdefault="true">
<![CDATA[
this.parentNode._selectNewTab(this.parentNode.children[0]);
]]>
</handler>
<handler event="keydown" keycode="VK_END" preventdefault="true">
<![CDATA[
var tabs = this.parentNode.children;
this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
]]>
</handler>
</handlers>
</binding>
</bindings>

View File

@@ -411,6 +411,7 @@ tabs {
}
tab {
-moz-binding: url("chrome://global/content/bindings/tabbox.xml#tab");
-moz-box-align: center;
-moz-box-pack: center;
}