Bug 1272256 - Adding in longpress new tab button container menu r?Gijs
MozReview-Commit-ID: 5KECDW34G8M
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xul
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul
@@ -85,16 +85,25 @@
{
// xul:toolbarbutton ("Close current tab")
role: ROLE_PUSHBUTTON,
children: []
}
);
} else {
SimpleTest.ok(true, "Testing Firefox tabbrowser UI.");
+ let newTabChildren = [];
+ if (SpecialPowers.getBoolPref("privacy.userContext.enabled")) {
+ newTabChildren = [
+ {
+ role: ROLE_MENUPOPUP,
+ children: []
+ }
+ ];
+ }
// NB: The (3) buttons are not visible, unless manually hovered,
// probably due to size reduction in this test.
tabsAccTree.children.splice(0, 0,
{
// xul:tab ("about:")
role: ROLE_PAGETAB,
children: [
@@ -114,17 +123,17 @@
role: ROLE_PUSHBUTTON,
children: []
}
]
},
{
// xul:toolbarbutton ("Open a new tab")
role: ROLE_PUSHBUTTON,
- children: []
+ children: newTabChildren
}
// "List all tabs" dropdown
// XXX: This child(?) is not present in this test.
// I'm not sure why (though probably expected).
);
}
testAccessibleTree(tabBrowser().tabContainer, tabsAccTree);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -138,16 +138,26 @@ tabbrowser {
#TabsToolbar[customizing="true"] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
visibility: collapse;
}
#tabbrowser-tabs:not([overflow="true"])[using-closing-tabs-spacer] ~ #alltabs-button {
visibility: hidden; /* temporary space to keep a tab's close button under the cursor */
}
+.tabs-newtab-button > .toolbarbutton-menu-dropmarker,
+#new-tab-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+.tabs-newtab-button > .toolbarbutton-icon,
+#new-tab-button > .toolbarbutton-icon {
+ margin-inline-end: 0;
+}
+
.tabbrowser-tab {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
}
.tabbrowser-tab:not([pinned]) {
-moz-box-flex: 100;
max-width: 210px;
min-width: 100px;
@@ -196,16 +206,17 @@ tabbrowser {
z-index: 2;
pointer-events: none; /* avoid blocking dragover events on scroll buttons */
}
.tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
transition: transform 200ms ease-out;
}
+.new-tab-popup,
#alltabs-popup {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
}
toolbar[printpreview="true"] {
-moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
}
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -258,92 +258,102 @@ function UpdateBackForwardCommands(aWebN
}
}
/**
* Click-and-Hold implementation for the Back and Forward buttons
* XXXmano: should this live in toolbarbutton.xml?
*/
function SetClickAndHoldHandlers() {
- var timer;
-
- function openMenu(aButton) {
- cancelHold(aButton);
- aButton.firstChild.hidden = false;
- aButton.open = true;
- }
-
- function mousedownHandler(aEvent) {
- if (aEvent.button != 0 ||
- aEvent.currentTarget.open ||
- aEvent.currentTarget.disabled)
- return;
-
- // Prevent the menupopup from opening immediately
- aEvent.currentTarget.firstChild.hidden = true;
-
- aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false);
- aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false);
- timer = setTimeout(openMenu, 500, aEvent.currentTarget);
- }
-
- function mouseoutHandler(aEvent) {
- let buttonRect = aEvent.currentTarget.getBoundingClientRect();
- if (aEvent.clientX >= buttonRect.left &&
- aEvent.clientX <= buttonRect.right &&
- aEvent.clientY >= buttonRect.bottom)
- openMenu(aEvent.currentTarget);
- else
- cancelHold(aEvent.currentTarget);
- }
-
- function mouseupHandler(aEvent) {
- cancelHold(aEvent.currentTarget);
- }
-
- function cancelHold(aButton) {
- clearTimeout(timer);
- aButton.removeEventListener("mouseout", mouseoutHandler, false);
- aButton.removeEventListener("mouseup", mouseupHandler, false);
- }
-
- function clickHandler(aEvent) {
- if (aEvent.button == 0 &&
- aEvent.target == aEvent.currentTarget &&
- !aEvent.currentTarget.open &&
- !aEvent.currentTarget.disabled) {
- let cmdEvent = document.createEvent("xulcommandevent");
- cmdEvent.initCommandEvent("command", true, true, window, 0,
- aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
- aEvent.metaKey, null);
- aEvent.currentTarget.dispatchEvent(cmdEvent);
- }
- }
-
- function _addClickAndHoldListenersOnElement(aElm) {
- aElm.addEventListener("mousedown", mousedownHandler, true);
- aElm.addEventListener("click", clickHandler, true);
- }
-
// Bug 414797: Clone the back/forward buttons' context menu into both buttons.
let popup = document.getElementById("backForwardMenu").cloneNode(true);
popup.removeAttribute("id");
// Prevent the back/forward buttons' context attributes from being inherited.
popup.setAttribute("context", "");
let backButton = document.getElementById("back-button");
backButton.setAttribute("type", "menu");
backButton.appendChild(popup);
- _addClickAndHoldListenersOnElement(backButton);
+ addClickAndHoldListenersOnElement(backButton);
let forwardButton = document.getElementById("forward-button");
popup = popup.cloneNode(true);
forwardButton.setAttribute("type", "menu");
forwardButton.appendChild(popup);
- _addClickAndHoldListenersOnElement(forwardButton);
+ addClickAndHoldListenersOnElement(forwardButton);
+}
+
+let holdTimersMap = new Map();
+function holdMousedownHandler(aEvent) {
+ if (aEvent.button != 0 ||
+ aEvent.currentTarget.open ||
+ aEvent.currentTarget.disabled)
+ return;
+
+ // Prevent the menupopup from opening immediately
+ aEvent.currentTarget.firstChild.hidden = true;
+
+ aEvent.currentTarget.addEventListener("mouseout", holdMouseoutHandler, false);
+ aEvent.currentTarget.addEventListener("mouseup", holdMouseupHandler, false);
+ holdTimersMap.set(aEvent.currentTarget, setTimeout(holdOpenMenu, 500, aEvent.currentTarget));
+}
+
+function holdClickHandler(aEvent) {
+ if (aEvent.button == 0 &&
+ aEvent.target == aEvent.currentTarget &&
+ !aEvent.currentTarget.open &&
+ !aEvent.currentTarget.disabled) {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ }
+ // This is here to cancel the XUL default event
+ // dom.click() triggers a command even if there is a click handler
+ // however this can now be prevented with preventDefault().
+ aEvent.preventDefault();
+}
+
+function holdOpenMenu(aButton) {
+ cancelHold(aButton);
+ aButton.firstChild.hidden = false;
+ aButton.open = true;
+}
+
+function holdMouseoutHandler(aEvent) {
+ let buttonRect = aEvent.currentTarget.getBoundingClientRect();
+ if (aEvent.clientX >= buttonRect.left &&
+ aEvent.clientX <= buttonRect.right &&
+ aEvent.clientY >= buttonRect.bottom)
+ holdOpenMenu(aEvent.currentTarget);
+ else
+ cancelHold(aEvent.currentTarget);
+}
+
+function holdMouseupHandler(aEvent) {
+ cancelHold(aEvent.currentTarget);
+}
+
+function cancelHold(aButton) {
+ clearTimeout(holdTimersMap.get(aButton));
+ aButton.removeEventListener("mouseout", holdMouseoutHandler, false);
+ aButton.removeEventListener("mouseup", holdMouseupHandler, false);
+}
+
+function removeClickAndHoldListenersOnElement(aElm) {
+ aElm.removeEventListener("mousedown", holdMousedownHandler, true);
+ aElm.removeEventListener("click", holdClickHandler, true);
+}
+
+function addClickAndHoldListenersOnElement(aElm) {
+ holdTimersMap.delete(aElm);
+
+ aElm.addEventListener("mousedown", holdMousedownHandler, true);
+ aElm.addEventListener("click", holdClickHandler, true);
}
const gSessionHistoryObserver = {
observe: function(subject, topic, data)
{
if (topic != "browser:purge-session-history")
return;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4934,17 +4934,17 @@
onmouseover="document.getBindingParent(this)._enterNewTab();"
onmouseout="document.getBindingParent(this)._leaveNewTab();"
tooltip="dynamic-shortcut-tooltip"/>
<xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
style="width: 0;"/>
</xul:arrowscrollbox>
</content>
- <implementation implements="nsIDOMEventListener">
+ <implementation implements="nsIDOMEventListener, nsIObserver">
<constructor>
<![CDATA[
this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
var tab = this.firstChild;
tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
tab.setAttribute("crop", "end");
tab.setAttribute("onerror", "this.removeAttribute('image');");
@@ -4953,19 +4953,31 @@
window.addEventListener("load", this, false);
try {
this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
} catch (ex) {
this._tabAnimationLoggingEnabled = false;
}
this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
+ this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
+ Services.prefs.addObserver("privacy.userContext.enabled", this, false);
]]>
</constructor>
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("privacy.userContext.enabled", this);
+ ]]>
+ </destructor>
+
+ <field name="newtabUndoCloseTab" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "newtab_undoCloseTab");
+ </field>
+
<field name="tabbrowser" readonly="true">
document.getElementById(this.getAttribute("tabbrowser"));
</field>
<field name="tabbox" readonly="true">
this.tabbrowser.mTabBox;
</field>
@@ -4981,16 +4993,64 @@
<field name="_firstTab">null</field>
<field name="_lastTab">null</field>
<field name="_afterSelectedTab">null</field>
<field name="_beforeHoveredTab">null</field>
<field name="_afterHoveredTab">null</field>
<field name="_hoveredTab">null</field>
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ switch (aTopic) {
+ case "nsPref:changed":
+ // This is the only pref observed.
+ let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
+
+ const newTab = document.getElementById("new-tab-button");
+ const newTab2 = document.getAnonymousElementByAttribute(this, "anonid", "tabs-newtab-button")
+
+ if (containersEnabled) {
+ for (let parent of [newTab, newTab2]) {
+ if (!parent)
+ continue;
+ let popup = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menupopup");
+ if (parent.id) {
+ popup.id = "newtab-popup";
+ } else {
+ popup.setAttribute("anonid", "newtab-popup");
+ }
+ popup.oncommand="event.stopPropagation();";
+ popup.className = "new-tab-popup";
+ popup.setAttribute("position", "after_end");
+ parent.appendChild(popup);
+
+ addClickAndHoldListenersOnElement(parent);
+ parent.setAttribute("type", "menu");
+ }
+ } else {
+ for (let parent of [newTab, newTab2]) {
+ if (!parent)
+ continue;
+ removeClickAndHoldListenersOnElement(parent);
+ parent.removeAttribute("type");
+ parent.firstChild.remove();
+ }
+ }
+
+ break;
+ }
+ ]]></body>
+ </method>
+
<property name="_isCustomizing" readonly="true">
<getter>
let root = document.documentElement;
return root.getAttribute("customizing") == "true" ||
root.getAttribute("customize-exiting") == "true";
</getter>
</property>
@@ -6668,55 +6728,64 @@
<handler event="popupshowing">
<![CDATA[
if (event.target.getAttribute('id') == "alltabs_containersMenuTab") {
createUserContextMenu(event);
return;
}
let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
- document.getElementById("alltabs-popup-separator-1").hidden = !containersEnabled;
- let containersTab = document.getElementById("alltabs_containersTab");
-
- containersTab.hidden = !containersEnabled;
- if (PrivateBrowsingUtils.isWindowPrivate(window)) {
- containersTab.setAttribute("disabled", "true");
+
+ if (event.target.getAttribute('anonid') == "newtab-popup" ||
+ event.target.id == "newtab-popup") {
+ createUserContextMenu(event);
+ } else {
+ document.getElementById("alltabs-popup-separator-1").hidden = !containersEnabled;
+ let containersTab = document.getElementById("alltabs_containersTab");
+
+ containersTab.hidden = !containersEnabled;
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ containersTab.setAttribute("disabled", "true");
+ }
+
+ document.getElementById("alltabs_undoCloseTab").disabled =
+ SessionStore.getClosedTabCount(window) == 0;
+
+ var tabcontainer = gBrowser.tabContainer;
+
+ // Listen for changes in the tab bar.
+ tabcontainer.addEventListener("TabAttrModified", this, false);
+ tabcontainer.addEventListener("TabClose", this, false);
+ tabcontainer.mTabstrip.addEventListener("scroll", this, false);
+
+ let tabs = gBrowser.visibleTabs;
+ for (var i = 0; i < tabs.length; i++) {
+ if (!tabs[i].pinned)
+ this._createTabMenuItem(tabs[i]);
+ }
+ this._updateTabsVisibilityStatus();
}
-
- document.getElementById("alltabs_undoCloseTab").disabled =
- SessionStore.getClosedTabCount(window) == 0;
-
- var tabcontainer = gBrowser.tabContainer;
-
- // Listen for changes in the tab bar.
- tabcontainer.addEventListener("TabAttrModified", this, false);
- tabcontainer.addEventListener("TabClose", this, false);
- tabcontainer.mTabstrip.addEventListener("scroll", this, false);
-
- let tabs = gBrowser.visibleTabs;
- for (var i = 0; i < tabs.length; i++) {
- if (!tabs[i].pinned)
- this._createTabMenuItem(tabs[i]);
- }
- this._updateTabsVisibilityStatus();
]]></handler>
<handler event="popuphidden">
<![CDATA[
if (event.target.getAttribute('id') == "alltabs_containersMenuTab") {
return;
}
// clear out the menu popup and remove the listeners
for (let i = this.childNodes.length - 1; i > 0; i--) {
let menuItem = this.childNodes[i];
if (menuItem.tab) {
menuItem.tab.mCorrespondingMenuitem = null;
this.removeChild(menuItem);
}
+ if (menuItem.hasAttribute("usercontextid")) {
+ this.removeChild(menuItem);
+ }
}
var tabcontainer = gBrowser.tabContainer;
tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
tabcontainer.removeEventListener("TabAttrModified", this, false);
tabcontainer.removeEventListener("TabClose", this, false);
]]></handler>
<handler event="DOMMenuItemActive">
--- a/browser/components/contextualidentity/test/browser/browser.ini
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -8,16 +8,17 @@ support-files =
serviceworker.html
worker.js
[browser_aboutURLs.js]
skip-if = (debug && (os == "win" || os == "linux")) # intermittent negative leak bug 1271182
[browser_eme.js]
[browser_favicon.js]
[browser_forgetaboutsite.js]
+[browser_newtabButton.js]
[browser_usercontext.js]
[browser_usercontextid_tabdrop.js]
skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
[browser_windowName.js]
tags = openwindow
[browser_windowOpen.js]
tags = openwindow
[browser_serviceworkers.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_newtabButton.js
@@ -0,0 +1,34 @@
+"use strict";
+
+// Testing that when the user opens the add tab menu and clicks menu items
+// the correct context id is opened
+
+add_task(function* test() {
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]});
+
+ let newTab = document.getElementById('tabbrowser-tabs');
+ let newTabButton = document.getAnonymousElementByAttribute(newTab, "anonid", "tabs-newtab-button");
+ ok(newTabButton, "New tab button exists");
+ ok(!newTabButton.hidden, "New tab button is visible");
+ let popup = document.getAnonymousElementByAttribute(newTab, "anonid", "newtab-popup");
+
+ for (let i = 1; i <= 4; i++) {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(newTabButton, {type: "mousedown"});
+
+ yield popupShownPromise;
+ let contextIdItem = popup.querySelector(`menuitem[usercontextid="${i}"]`);
+
+ ok(contextIdItem, `User context id ${i} exists`);
+
+ let waitForTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ EventUtils.synthesizeMouseAtCenter(contextIdItem, {});
+
+ let tab = yield waitForTabPromise;
+
+ is(tab.getAttribute('usercontextid'), i, `New tab has UCI equal ${i}`);
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});