зеркало из 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;"
|
||||
id="syncedTabsRefresh"/>
|
||||
</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>
|
||||
|
||||
#ifdef CAN_DRAW_IN_TITLEBAR
|
||||
|
|
|
@ -21,6 +21,10 @@ function getContextMenu(window) {
|
|||
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarContext");
|
||||
}
|
||||
|
||||
function getTabsFilterContextMenu(window) {
|
||||
return getChromeWindow(window).document.getElementById("SyncedTabsSidebarTabsFilterContext");
|
||||
}
|
||||
|
||||
/*
|
||||
* TabListView
|
||||
*
|
||||
|
@ -330,21 +334,71 @@ TabListView.prototype = {
|
|||
|
||||
// Set up the custom context menu
|
||||
_setupContextMenu() {
|
||||
this._handleContentContextMenu = event =>
|
||||
this.handleContentContextMenu(event);
|
||||
this._handleContentContextMenuCommand = event =>
|
||||
this.handleContentContextMenuCommand(event);
|
||||
|
||||
Services.els.addSystemEventListener(this._window, "contextmenu", this._handleContentContextMenu, false);
|
||||
let menu = getContextMenu(this._window);
|
||||
menu.addEventListener("command", this._handleContentContextMenuCommand, true);
|
||||
Services.els.addSystemEventListener(this._window, "contextmenu", this, false);
|
||||
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
|
||||
let menu = getMenu(this._window);
|
||||
menu.addEventListener("popupshowing", this, true);
|
||||
menu.addEventListener("command", this, true);
|
||||
}
|
||||
},
|
||||
|
||||
_teardownContextMenu() {
|
||||
// Tear down context menu
|
||||
Services.els.removeSystemEventListener(this._window, "contextmenu", this._handleContentContextMenu, false);
|
||||
let menu = getContextMenu(this._window);
|
||||
menu.removeEventListener("command", this._handleContentContextMenuCommand, true);
|
||||
Services.els.removeSystemEventListener(this._window, "contextmenu", this, false);
|
||||
for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
|
||||
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) {
|
||||
|
@ -357,19 +411,33 @@ TabListView.prototype = {
|
|||
this.onBookmarkTab();
|
||||
break;
|
||||
case "syncedTabsRefresh":
|
||||
case "syncedTabsRefreshFilter":
|
||||
this.props.onSyncRefresh();
|
||||
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);
|
||||
if (itemNode) {
|
||||
this._selectRow(itemNode);
|
||||
}
|
||||
|
||||
let menu = getContextMenu(this._window);
|
||||
menu = getContextMenu(this._window);
|
||||
this.adjustContextMenu(menu);
|
||||
}
|
||||
|
||||
menu.openPopupAtScreen(event.screenX, event.screenY, true, event);
|
||||
},
|
||||
|
||||
|
|
|
@ -255,6 +255,78 @@ add_task(function* testSyncedTabsSidebarStatus() {
|
|||
|
||||
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) {
|
||||
Assert.ok(node.classList.contains("item"),
|
||||
"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`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче