зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1254544
- Show clipboard commands in the synced tabs filter context menu. r=markh
MozReview-Commit-ID: 9XTYrU0xp9E --HG-- extra : transplant_source : %08n%86%F3%C7%B7j%E2%C5%A7W%D5%91%A2Zs%A1%D9%DD%F1
This commit is contained in:
Родитель
023c3d96a4
Коммит
85bc9bbd3f
|
@ -472,6 +472,33 @@
|
||||||
accesskey="&syncSyncNowItem.accesskey;"
|
accesskey="&syncSyncNowItem.accesskey;"
|
||||||
id="syncedTabsRefresh"/>
|
id="syncedTabsRefresh"/>
|
||||||
</menupopup>
|
</menupopup>
|
||||||
|
<menupopup id="SyncedTabsSidebarTabsFilterContext"
|
||||||
|
class="textbox-contextmenu">
|
||||||
|
<menuitem label="&undoCmd.label;"
|
||||||
|
accesskey="&undoCmd.accesskey;"
|
||||||
|
cmd="cmd_undo"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="&cutCmd.label;"
|
||||||
|
accesskey="&cutCmd.accesskey;"
|
||||||
|
cmd="cmd_cut"/>
|
||||||
|
<menuitem label="©Cmd.label;"
|
||||||
|
accesskey="©Cmd.accesskey;"
|
||||||
|
cmd="cmd_copy"/>
|
||||||
|
<menuitem label="&pasteCmd.label;"
|
||||||
|
accesskey="&pasteCmd.accesskey;"
|
||||||
|
cmd="cmd_paste"/>
|
||||||
|
<menuitem label="&deleteCmd.label;"
|
||||||
|
accesskey="&deleteCmd.accesskey;"
|
||||||
|
cmd="cmd_delete"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="&selectAllCmd.label;"
|
||||||
|
accesskey="&selectAllCmd.accesskey;"
|
||||||
|
cmd="cmd_selectAll"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem label="&syncSyncNowItem.label;"
|
||||||
|
accesskey="&syncSyncNowItem.accesskey;"
|
||||||
|
id="syncedTabsRefreshFilter"/>
|
||||||
|
</menupopup>
|
||||||
</popupset>
|
</popupset>
|
||||||
|
|
||||||
#ifdef CAN_DRAW_IN_TITLEBAR
|
#ifdef CAN_DRAW_IN_TITLEBAR
|
||||||
|
|
|
@ -21,6 +21,10 @@ function getContextMenu(window) {
|
||||||
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarContext");
|
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarContext");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTabsFilterContextMenu(window) {
|
||||||
|
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarTabsFilterContext");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TabListView
|
* TabListView
|
||||||
*
|
*
|
||||||
|
@ -330,21 +334,71 @@ TabListView.prototype = {
|
||||||
|
|
||||||
// Set up the custom context menu
|
// Set up the custom context menu
|
||||||
_setupContextMenu() {
|
_setupContextMenu() {
|
||||||
this._handleContentContextMenu = event =>
|
Services.els.addSystemEventListener(this._window, "contextmenu", this, false);
|
||||||
this.handleContentContextMenu(event);
|
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
|
||||||
this._handleContentContextMenuCommand = event =>
|
let menu = getMenu(this._window);
|
||||||
this.handleContentContextMenuCommand(event);
|
menu.addEventListener("popupshowing", this, true);
|
||||||
|
menu.addEventListener("command", this, true);
|
||||||
Services.els.addSystemEventListener(this._window, "contextmenu", this._handleContentContextMenu, false);
|
}
|
||||||
let menu = getContextMenu(this._window);
|
|
||||||
menu.addEventListener("command", this._handleContentContextMenuCommand, true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_teardownContextMenu() {
|
_teardownContextMenu() {
|
||||||
// Tear down context menu
|
// Tear down context menu
|
||||||
Services.els.removeSystemEventListener(this._window, "contextmenu", this._handleContentContextMenu, false);
|
Services.els.removeSystemEventListener(this._window, "contextmenu", this, false);
|
||||||
let menu = getContextMenu(this._window);
|
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
|
||||||
menu.removeEventListener("command", this._handleContentContextMenuCommand, true);
|
let menu = getMenu(this._window);
|
||||||
|
menu.removeEventListener("popupshowing", this, true);
|
||||||
|
menu.removeEventListener("command", this, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEvent(event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case "contextmenu":
|
||||||
|
this.handleContextMenu(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "popupshowing": {
|
||||||
|
if (event.target.getAttribute("id") == "SyncedTabsSidebarTabsFilterContext") {
|
||||||
|
this.handleTabsFilterContextMenuShown(event);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "command": {
|
||||||
|
let menu = event.target.closest("menupopup");
|
||||||
|
switch (menu.getAttribute("id")) {
|
||||||
|
case "SyncedTabsSidebarContext":
|
||||||
|
this.handleContentContextMenuCommand(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "SyncedTabsSidebarTabsFilterContext":
|
||||||
|
this.handleTabsFilterContextMenuCommand(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTabsFilterContextMenuShown(event) {
|
||||||
|
let document = event.target.ownerDocument;
|
||||||
|
let focusedElement = document.commandDispatcher.focusedElement;
|
||||||
|
if (focusedElement != this.tabsFilter) {
|
||||||
|
this.tabsFilter.focus();
|
||||||
|
}
|
||||||
|
for (let item of event.target.children) {
|
||||||
|
if (!item.hasAttribute("cmd")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let command = item.getAttribute("cmd");
|
||||||
|
let controller = document.commandDispatcher.getControllerForCommand(command);
|
||||||
|
if (controller.isCommandEnabled(command)) {
|
||||||
|
item.removeAttribute("disabled");
|
||||||
|
} else {
|
||||||
|
item.setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContentContextMenuCommand(event) {
|
handleContentContextMenuCommand(event) {
|
||||||
|
@ -357,19 +411,33 @@ TabListView.prototype = {
|
||||||
this.onBookmarkTab();
|
this.onBookmarkTab();
|
||||||
break;
|
break;
|
||||||
case "syncedTabsRefresh":
|
case "syncedTabsRefresh":
|
||||||
|
case "syncedTabsRefreshFilter":
|
||||||
this.props.onSyncRefresh();
|
this.props.onSyncRefresh();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContentContextMenu(event) {
|
handleTabsFilterContextMenuCommand(event) {
|
||||||
|
let command = event.target.getAttribute("cmd");
|
||||||
|
let dispatcher = getChromeWindow(this._window).document.commandDispatcher;
|
||||||
|
let controller = dispatcher.focusedElement.controllers.getControllerForCommand(command);
|
||||||
|
controller.doCommand(command);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleContextMenu(event) {
|
||||||
|
let menu;
|
||||||
|
|
||||||
|
if (event.target == this.tabsFilter) {
|
||||||
|
menu = getTabsFilterContextMenu(this._window);
|
||||||
|
} else {
|
||||||
let itemNode = this._findParentItemNode(event.target);
|
let itemNode = this._findParentItemNode(event.target);
|
||||||
if (itemNode) {
|
if (itemNode) {
|
||||||
this._selectRow(itemNode);
|
this._selectRow(itemNode);
|
||||||
}
|
}
|
||||||
|
menu = getContextMenu(this._window);
|
||||||
let menu = getContextMenu(this._window);
|
|
||||||
this.adjustContextMenu(menu);
|
this.adjustContextMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
menu.openPopupAtScreen(event.screenX, event.screenY, true, event);
|
menu.openPopupAtScreen(event.screenX, event.screenY, true, event);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,78 @@ add_task(function* testSyncedTabsSidebarStatus() {
|
||||||
|
|
||||||
add_task(testClean);
|
add_task(testClean);
|
||||||
|
|
||||||
|
add_task(function* testSyncedTabsSidebarContextMenu() {
|
||||||
|
yield SidebarUI.show('viewTabsSidebar');
|
||||||
|
let syncedTabsDeckComponent = window.SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
|
||||||
|
let SyncedTabs = window.SidebarUI.browser.contentWindow.SyncedTabs;
|
||||||
|
|
||||||
|
Assert.ok(syncedTabsDeckComponent, "component exists");
|
||||||
|
|
||||||
|
originalSyncedTabsInternal = SyncedTabs._internal;
|
||||||
|
SyncedTabs._internal = {
|
||||||
|
isConfiguredToSyncTabs: true,
|
||||||
|
hasSyncedThisSession: true,
|
||||||
|
getTabClients() { return Promise.resolve([])},
|
||||||
|
syncTabs() {return Promise.resolve();},
|
||||||
|
};
|
||||||
|
|
||||||
|
sinon.stub(syncedTabsDeckComponent, "_accountStatus", ()=> Promise.resolve(true));
|
||||||
|
sinon.stub(SyncedTabs._internal, "getTabClients", ()=> Promise.resolve(Cu.cloneInto(FIXTURE, {})));
|
||||||
|
|
||||||
|
yield syncedTabsDeckComponent.updatePanel();
|
||||||
|
// This is a hacky way of waiting for the view to render. The view renders
|
||||||
|
// after the following promise (a different instance of which is triggered
|
||||||
|
// in updatePanel) resolves, so we wait for it here as well
|
||||||
|
yield syncedTabsDeckComponent.tabListComponent._store.getData();
|
||||||
|
|
||||||
|
info("Right-clicking the search box should show text-related actions");
|
||||||
|
let filterMenuItems = [
|
||||||
|
"menuitem[cmd=cmd_undo]",
|
||||||
|
"menuseparator",
|
||||||
|
// We don't check whether the commands are enabled due to platform
|
||||||
|
// differences. On OS X and Windows, "cut" and "copy" are always enabled
|
||||||
|
// for HTML inputs; on Linux, they're only enabled if text is selected.
|
||||||
|
"menuitem[cmd=cmd_cut]",
|
||||||
|
"menuitem[cmd=cmd_copy]",
|
||||||
|
"menuitem[cmd=cmd_paste]",
|
||||||
|
"menuitem[cmd=cmd_delete]",
|
||||||
|
"menuseparator",
|
||||||
|
"menuitem[cmd=cmd_selectAll]",
|
||||||
|
"menuseparator",
|
||||||
|
"menuitem#syncedTabsRefreshFilter",
|
||||||
|
];
|
||||||
|
yield* testContextMenu(syncedTabsDeckComponent,
|
||||||
|
"#SyncedTabsSidebarTabsFilterContext",
|
||||||
|
".tabsFilter",
|
||||||
|
filterMenuItems);
|
||||||
|
|
||||||
|
info("Right-clicking a tab should show additional actions");
|
||||||
|
let tabMenuItems = [
|
||||||
|
["menuitem#syncedTabsOpenSelected", { hidden: false }],
|
||||||
|
["menuitem#syncedTabsBookmarkSelected", { hidden: false }],
|
||||||
|
["menuseparator", { hidden: false }],
|
||||||
|
["menuitem#syncedTabsRefresh", { hidden: false }],
|
||||||
|
];
|
||||||
|
yield* testContextMenu(syncedTabsDeckComponent,
|
||||||
|
"#SyncedTabsSidebarContext",
|
||||||
|
"#tab-7cqCr77ptzX3-0",
|
||||||
|
tabMenuItems);
|
||||||
|
|
||||||
|
info("Right-clicking a client shouldn't show any actions");
|
||||||
|
let sidebarMenuItems = [
|
||||||
|
["menuitem#syncedTabsOpenSelected", { hidden: true }],
|
||||||
|
["menuitem#syncedTabsBookmarkSelected", { hidden: true }],
|
||||||
|
["menuseparator", { hidden: true }],
|
||||||
|
["menuitem#syncedTabsRefresh", { hidden: false }],
|
||||||
|
];
|
||||||
|
yield* testContextMenu(syncedTabsDeckComponent,
|
||||||
|
"#SyncedTabsSidebarContext",
|
||||||
|
"#item-OL3EJCsdb2JD",
|
||||||
|
sidebarMenuItems);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(testClean);
|
||||||
|
|
||||||
function checkItem(node, item) {
|
function checkItem(node, item) {
|
||||||
Assert.ok(node.classList.contains("item"),
|
Assert.ok(node.classList.contains("item"),
|
||||||
"Node should have .item class");
|
"Node should have .item class");
|
||||||
|
@ -279,3 +351,47 @@ function checkItem(node, item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* testContextMenu(syncedTabsDeckComponent, contextSelector, triggerSelector, menuSelectors) {
|
||||||
|
let contextMenu = document.querySelector(contextSelector);
|
||||||
|
let triggerElement = syncedTabsDeckComponent.container.querySelector(triggerSelector);
|
||||||
|
|
||||||
|
let promisePopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
|
||||||
|
|
||||||
|
let chromeWindow = triggerElement.ownerDocument.defaultView.top;
|
||||||
|
let rect = triggerElement.getBoundingClientRect();
|
||||||
|
let contentRect = chromeWindow.SidebarUI.browser.getBoundingClientRect();
|
||||||
|
// The offsets in `rect` are relative to the content window, but
|
||||||
|
// `synthesizeMouseAtPoint` calls `nsIDOMWindowUtils.sendMouseEvent`,
|
||||||
|
// which interprets the offsets relative to the containing *chrome* window.
|
||||||
|
// This means we need to account for the width and height of any elements
|
||||||
|
// outside the `browser` element, like `sidebarheader`.
|
||||||
|
let offsetX = contentRect.x + rect.x + (rect.width / 2);
|
||||||
|
let offsetY = contentRect.y + rect.y + (rect.height / 4);
|
||||||
|
|
||||||
|
yield EventUtils.synthesizeMouseAtPoint(offsetX, offsetY, {
|
||||||
|
type: "contextmenu",
|
||||||
|
button: 2,
|
||||||
|
}, chromeWindow);
|
||||||
|
yield promisePopupShown;
|
||||||
|
checkChildren(contextMenu, menuSelectors);
|
||||||
|
|
||||||
|
let promisePopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
|
||||||
|
contextMenu.hidePopup();
|
||||||
|
yield promisePopupHidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkChildren(node, selectors) {
|
||||||
|
is(node.children.length, selectors.length, "Menu item count doesn't match");
|
||||||
|
for (let index = 0; index < node.children.length; index++) {
|
||||||
|
let child = node.children[index];
|
||||||
|
let [selector, props] = [].concat(selectors[index]);
|
||||||
|
ok(selector, `Node at ${index} should have selector`);
|
||||||
|
ok(child.matches(selector), `Node ${
|
||||||
|
index} should match ${selector}`);
|
||||||
|
if (props) {
|
||||||
|
Object.keys(props).forEach(prop => {
|
||||||
|
is(child[prop], props[prop], `${prop} value at ${index} should match`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче