Bug 1272256 - Adding a longpress menu to the new tab button for containers. r=dao

MozReview-Commit-ID: 3GM15cnuQGA
This commit is contained in:
Jonathan Kingston 2016-09-12 18:25:59 +01:00
Родитель f2ddeeaf28
Коммит ac78554a1e
8 изменённых файлов: 311 добавлений и 88 удалений

Просмотреть файл

@ -90,6 +90,15 @@
); );
} else { } else {
SimpleTest.ok(true, "Testing Firefox tabbrowser UI."); 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, // NB: The (3) buttons are not visible, unless manually hovered,
// probably due to size reduction in this test. // probably due to size reduction in this test.
@ -119,7 +128,7 @@
{ {
// xul:toolbarbutton ("Open a new tab") // xul:toolbarbutton ("Open a new tab")
role: ROLE_PUSHBUTTON, role: ROLE_PUSHBUTTON,
children: [] children: newTabChildren
} }
// "List all tabs" dropdown // "List all tabs" dropdown
// XXX: This child(?) is not present in this test. // XXX: This child(?) is not present in this test.

Просмотреть файл

@ -119,6 +119,16 @@ tabbrowser {
visibility: hidden; /* temporary space to keep a tab's close button under the cursor */ 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;
}
/* override drop marker image padding */
.tabs-newtab-button > .toolbarbutton-icon {
margin-inline-end: 0;
}
.tabbrowser-tab { .tabbrowser-tab {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab"); -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
} }
@ -177,6 +187,7 @@ tabbrowser {
transition: transform 200ms ease-out; transition: transform 200ms ease-out;
} }
.new-tab-popup,
#alltabs-popup { #alltabs-popup {
-moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup"); -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
} }

Просмотреть файл

@ -268,66 +268,6 @@ function UpdateBackForwardCommands(aWebNavigation) {
* XXXmano: should this live in toolbarbutton.xml? * XXXmano: should this live in toolbarbutton.xml?
*/ */
function SetClickAndHoldHandlers() { 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. // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
let popup = document.getElementById("backForwardMenu").cloneNode(true); let popup = document.getElementById("backForwardMenu").cloneNode(true);
popup.removeAttribute("id"); popup.removeAttribute("id");
@ -337,15 +277,107 @@ function SetClickAndHoldHandlers() {
let backButton = document.getElementById("back-button"); let backButton = document.getElementById("back-button");
backButton.setAttribute("type", "menu"); backButton.setAttribute("type", "menu");
backButton.appendChild(popup); backButton.appendChild(popup);
_addClickAndHoldListenersOnElement(backButton); gClickAndHoldListenersOnElement.add(backButton);
let forwardButton = document.getElementById("forward-button"); let forwardButton = document.getElementById("forward-button");
popup = popup.cloneNode(true); popup = popup.cloneNode(true);
forwardButton.setAttribute("type", "menu"); forwardButton.setAttribute("type", "menu");
forwardButton.appendChild(popup); forwardButton.appendChild(popup);
_addClickAndHoldListenersOnElement(forwardButton); gClickAndHoldListenersOnElement.add(forwardButton);
} }
const gClickAndHoldListenersOnElement = {
_timers: new Map(),
_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", this, false);
aEvent.currentTarget.addEventListener("mouseup", this, false);
this._timers.set(aEvent.currentTarget, setTimeout((b) => this._openMenu(b), 500, aEvent.currentTarget));
},
_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);
// 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();
}
},
_openMenu(aButton) {
this._cancelHold(aButton);
aButton.firstChild.hidden = false;
aButton.open = true;
},
_mouseoutHandler(aEvent) {
let buttonRect = aEvent.currentTarget.getBoundingClientRect();
if (aEvent.clientX >= buttonRect.left &&
aEvent.clientX <= buttonRect.right &&
aEvent.clientY >= buttonRect.bottom)
this._openMenu(aEvent.currentTarget);
else
this._cancelHold(aEvent.currentTarget);
},
_mouseupHandler(aEvent) {
this._cancelHold(aEvent.currentTarget);
},
_cancelHold(aButton) {
clearTimeout(this._timers.get(aButton));
aButton.removeEventListener("mouseout", this, false);
aButton.removeEventListener("mouseup", this, false);
},
handleEvent(e) {
switch (e.type) {
case "mouseout":
this._mouseoutHandler(e);
break;
case "mousedown":
this._mousedownHandler(e);
break;
case "click":
this._clickHandler(e);
break;
case "mouseup":
this._mouseupHandler(e);
break;
}
},
remove(aButton) {
aButton.removeEventListener("mousedown", this, true);
aButton.removeEventListener("click", this, true);
},
add(aElm) {
this._timers.delete(aElm);
aElm.addEventListener("mousedown", this, true);
aElm.addEventListener("click", this, true);
}
};
const gSessionHistoryObserver = { const gSessionHistoryObserver = {
observe: function(subject, topic, data) observe: function(subject, topic, data)
{ {

Просмотреть файл

@ -5236,7 +5236,7 @@
</xul:arrowscrollbox> </xul:arrowscrollbox>
</content> </content>
<implementation implements="nsIDOMEventListener"> <implementation implements="nsIDOMEventListener, nsIObserver">
<constructor> <constructor>
<![CDATA[ <![CDATA[
this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth"); this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
@ -5255,9 +5255,17 @@
this._tabAnimationLoggingEnabled = false; this._tabAnimationLoggingEnabled = false;
} }
this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled"); 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> </constructor>
<destructor>
<![CDATA[
Services.prefs.removeObserver("privacy.userContext.enabled", this);
]]>
</destructor>
<field name="tabbrowser" readonly="true"> <field name="tabbrowser" readonly="true">
document.getElementById(this.getAttribute("tabbrowser")); document.getElementById(this.getAttribute("tabbrowser"));
</field> </field>
@ -5283,6 +5291,53 @@
<field name="_afterHoveredTab">null</field> <field name="_afterHoveredTab">null</field>
<field name="_hoveredTab">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.className = "new-tab-popup";
popup.setAttribute("position", "after_end");
parent.appendChild(popup);
gClickAndHoldListenersOnElement.add(parent);
parent.setAttribute("type", "menu");
}
} else {
for (let parent of [newTab, newTab2]) {
if (!parent)
continue;
gClickAndHoldListenersOnElement.remove(parent);
parent.removeAttribute("type");
parent.firstChild.remove();
}
}
break;
}
]]></body>
</method>
<property name="_isCustomizing" readonly="true"> <property name="_isCustomizing" readonly="true">
<getter> <getter>
let root = document.documentElement; let root = document.documentElement;
@ -6963,41 +7018,47 @@
<handlers> <handlers>
<handler event="popupshowing"> <handler event="popupshowing">
<![CDATA[ <![CDATA[
if (event.target.getAttribute('id') == "alltabs_containersMenuTab") { if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
createUserContextMenu(event); createUserContextMenu(event);
return; return;
} }
let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled"); 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 (event.target.getAttribute("anonid") == "newtab-popup" ||
if (PrivateBrowsingUtils.isWindowPrivate(window)) { event.target.id == "newtab-popup") {
containersTab.setAttribute("disabled", "true"); 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>
<handler event="popuphidden"> <handler event="popuphidden">
<![CDATA[ <![CDATA[
if (event.target.getAttribute('id') == "alltabs_containersMenuTab") { if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
return; return;
} }
@ -7008,6 +7069,9 @@
menuItem.tab.mCorrespondingMenuitem = null; menuItem.tab.mCorrespondingMenuitem = null;
this.removeChild(menuItem); this.removeChild(menuItem);
} }
if (menuItem.hasAttribute("usercontextid")) {
this.removeChild(menuItem);
}
} }
var tabcontainer = gBrowser.tabContainer; var tabcontainer = gBrowser.tabContainer;
tabcontainer.mTabstrip.removeEventListener("scroll", this, false); tabcontainer.mTabstrip.removeEventListener("scroll", this, false);

Просмотреть файл

@ -534,3 +534,4 @@ skip-if = !e10s
tags = mcb tags = mcb
[browser_newwindow_focus.js] [browser_newwindow_focus.js]
skip-if = (os == "linux" && !e10s) # Bug 1263254 - Perma fails on Linux without e10s for some reason. skip-if = (os == "linux" && !e10s) # Bug 1263254 - Perma fails on Linux without e10s for some reason.
[browser_bug1299667.js]

Просмотреть файл

@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { addObserver, removeObserver } = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
function receive(topic) {
return new Promise((resolve, reject) => {
let timeout = setTimeout(() => {
reject(new Error("Timeout"));
}, 90000);
const observer = {
observe: subject => {
removeObserver(observer, topic);
clearTimeout(timeout);
resolve(subject);
}
};
addObserver(observer, topic, false);
});
}
add_task(function* () {
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
content.history.pushState({}, "2", "2.html");
});
yield receive("sessionstore-state-write-complete");
// Wait for the session data to be flushed before continuing the test
yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
var backButton = document.getElementById("back-button");
var contextMenu = document.getElementById("backForwardMenu");
info("waiting for the history menu to open");
let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
EventUtils.synthesizeMouseAtCenter(backButton, {type: "contextmenu", button: 2});
let event = yield popupShownPromise;
ok(true, "history menu opened");
// Wait for the session data to be flushed before continuing the test
yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
is(event.target.children.length, 2, "Two history items");
let node = event.target.firstChild;
is(node.getAttribute("uri"), "http://example.com/2.html", "first item uri");
is(node.getAttribute("index"), "1", "first item index");
is(node.getAttribute("historyindex"), "0", "first item historyindex");
node = event.target.lastChild;
is(node.getAttribute("uri"), "http://example.com/", "second item uri");
is(node.getAttribute("index"), "0", "second item index");
is(node.getAttribute("historyindex"), "-1", "second item historyindex");
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
event.target.hidePopup();
yield popupHiddenPromise;
info("Hidden popup");
let onClose = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose");
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
yield onClose;
info("Tab closed");
});

Просмотреть файл

@ -15,6 +15,7 @@ support-files =
[browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js] [browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js]
[browser_forgetAPI_EME_forgetThisSite.js] [browser_forgetAPI_EME_forgetThisSite.js]
[browser_forgetAPI_quota_clearStoragesForPrincipal.js] [browser_forgetAPI_quota_clearStoragesForPrincipal.js]
[browser_newtabButton.js]
[browser_usercontext.js] [browser_usercontext.js]
[browser_usercontextid_tabdrop.js] [browser_usercontextid_tabdrop.js]
skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276 skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276

Просмотреть файл

@ -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[data-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);
}
});