Bug 1911626: Add closing tabs remotely to sidebar right click menu r=lina,fluent-reviewers,desktop-theme-reviewers,fxview-reviewers,sidebar-reviewers,dao,sclements

Differential Revision: https://phabricator.services.mozilla.com/D220653
This commit is contained in:
Sammy Khamis 2024-09-10 20:25:48 +00:00
parent dec419b4de
commit a4a4236336
6 changed files with 146 additions and 38 deletions

View File

@ -300,6 +300,11 @@
<menuitem data-l10n-id="sidebar-context-menu-open-in-private-window"
id="sidebar-synced-tabs-context-open-in-private-window"/>
<menuseparator/>
<menuitem data-l10n-id="sidebar-context-menu-close-remote-tab"
id="sidebar-context-menu-close-remote-tab"
data-l10n-args='{"deviceName": ""}'
disabled="true"/>
<menuseparator/>
<menuitem data-l10n-id="sidebar-context-menu-bookmark-tab"
id="sidebar-synced-tabs-context-bookmark-tab"/>
<menuitem data-l10n-id="sidebar-context-menu-copy-link"

View File

@ -37,6 +37,11 @@
:host([compact]) & {
grid-template-columns: min-content auto;
}
&[disabled="true"] {
pointer-events: none;
color: var(--panel-disabled-color);
}
}
.fxview-tab-row-main,

View File

@ -52,7 +52,18 @@ class SyncedTabsInSidebar extends SidebarPage {
this.triggerNode = this.findTriggerNode(e, "sidebar-tab-row");
if (!this.triggerNode) {
e.preventDefault();
return;
}
const contextMenu = this._contextMenu;
const closeTabMenuItem = contextMenu.querySelector(
"#sidebar-context-menu-close-remote-tab"
);
closeTabMenuItem.setAttribute(
"data-l10n-args",
this.triggerNode.secondaryL10nArgs
);
// Enable the feature only if the device supports it
closeTabMenuItem.disabled = !this.triggerNode.canClose;
}
handleCommandEvent(e) {
@ -63,6 +74,13 @@ class SyncedTabsInSidebar extends SidebarPage {
this.triggerNode.title
);
break;
case "sidebar-context-menu-close-remote-tab":
this.requestOrRemoveTabToClose(
this.triggerNode.url,
this.triggerNode.fxaDeviceId,
this.triggerNode.secondaryActionClass
);
break;
default:
super.handleCommandEvent(e);
break;
@ -75,11 +93,15 @@ class SyncedTabsInSidebar extends SidebarPage {
onSecondaryAction(e) {
const { url, fxaDeviceId, secondaryActionClass } = e.originalTarget;
this.requestOrRemoveTabToClose(url, fxaDeviceId, secondaryActionClass);
}
requestOrRemoveTabToClose(url, fxaDeviceId, secondaryActionClass) {
if (secondaryActionClass === "dismiss-button") {
// Set new pending close tab
this.controller.requestCloseRemoteTab(fxaDeviceId, url);
} else if (secondaryActionClass === "undo-button") {
// User wants to undo
this.controller.removePendingTabToClose(fxaDeviceId, url);
}
this.requestUpdate();
@ -224,14 +246,22 @@ class SyncedTabsInSidebar extends SidebarPage {
getTabItems(items, deviceName, canClose) {
return items
.map(item => {
// We always show the option to close remotely on right-click but
// disable it if the device doesn't support actually closing it
let secondaryL10nId = "synced-tabs-context-close-tab-title";
let secondaryL10nArgs = JSON.stringify({ deviceName });
if (!canClose) {
return item;
return {
...item,
canClose,
secondaryL10nId,
secondaryL10nArgs,
};
}
// Default show the close/dismiss button
let secondaryActionClass = "dismiss-button";
let secondaryL10nId = "synced-tabs-context-close-tab-title";
let secondaryL10nArgs = JSON.stringify({ deviceName });
item.closeRequested = false;
// If this item has been requested to be closed, show
// the undo instead
@ -239,10 +269,12 @@ class SyncedTabsInSidebar extends SidebarPage {
secondaryActionClass = "undo-button";
secondaryL10nId = "text-action-undo";
secondaryL10nArgs = null;
item.closeRequested = true;
}
return {
...item,
canClose,
secondaryActionClass,
secondaryL10nId,
secondaryL10nArgs,

View File

@ -42,9 +42,11 @@ export class SidebarTabList extends FxviewTabListBase {
return html`
<sidebar-tab-row
?active=${i == this.activeIndex}
.closedId=${ifDefined(tabItem.closedId || tabItem.closedId)}
.canClose=${ifDefined(tabItem.canClose)}
.closedId=${ifDefined(tabItem.closedId)}
compact
.currentActiveElementId=${this.currentActiveElementId}
.closeRequested=${tabItem.closeRequested}
.fxaDeviceId=${ifDefined(tabItem.fxaDeviceId)}
.favicon=${tabItem.icon}
.hasPopup=${this.hasPopup}
@ -111,7 +113,10 @@ export class SidebarTabRow extends FxviewTabRowBase {
href="chrome://browser/content/sidebar/sidebar-tab-row.css"
/>
<a
class="fxview-tab-row-main"
class=${classMap({
"fxview-tab-row-main": true,
})}
disabled=${this.closeRequested}
data-l10n-args=${ifDefined(this.primaryL10nArgs)}
data-l10n-id=${ifDefined(this.primaryL10nId)}
href=${ifDefined(this.url)}

View File

@ -34,6 +34,7 @@ const tabClients = [
availableCommands: {
"https://identity.mozilla.com/cmd/close-uri/v1": "encryption_is_cool",
},
secondaryL10nArgs: '{"deviceName": "My Desktop"}',
},
{
device: "My desktop",
@ -48,6 +49,7 @@ const tabClients = [
availableCommands: {
"https://identity.mozilla.com/cmd/close-uri/v1": "encryption_is_cool",
},
secondaryL10nArgs: '{"deviceName": "My Desktop"}',
},
],
},
@ -68,9 +70,8 @@ const tabClients = [
lastUsed: 1655291890, // Wed Jun 15 2022 11:18:10 GMT+0000
client: 2,
fxaDeviceId: "2",
availableCommands: {
"https://identity.mozilla.com/cmd/close-uri/v1": "encryption_is_cool",
},
availableCommands: {},
secondaryL10nArgs: '{"deviceName": "My iphone"}',
},
{
device: "My iphone",
@ -82,9 +83,8 @@ const tabClients = [
lastUsed: 1655727485, // Mon Jun 20 2022 12:18:05 GMT+0000
client: 2,
fxaDeviceId: "2",
availableCommands: {
"https://identity.mozilla.com/cmd/close-uri/v1": "encryption_is_cool",
},
availableCommands: {},
secondaryL10nArgs: '{"deviceName": "My iphone"}',
},
],
},
@ -126,32 +126,48 @@ add_task(async function test_tabs() {
content
);
// We need to use renderRoot since Lit components querySelector
// won't return the right things
await BrowserTestUtils.waitForCondition(
() => row.renderRoot.querySelector(".dismiss-button") !== null,
`Dismiss button should appear for tab ${j + 1}`
);
// Check the presence of the dismiss button
const dismissButton = row.renderRoot.querySelector(".dismiss-button");
Assert.ok(dismissButton, `Dismiss button is present on tab ${j + 1}.`);
// Simulate clicking the dismiss button
EventUtils.synthesizeMouseAtCenter(dismissButton, {}, content);
await TestUtils.waitForCondition(() => {
const undoButton = row.renderRoot.querySelector(".undo-button");
return undoButton && undoButton.style.display !== "none";
}, `Undo button is shown after dismissing tab ${j + 1}.`);
// Simulate clicking the undo button
const undoButton = row.renderRoot.querySelector(".undo-button");
EventUtils.synthesizeMouseAtCenter(undoButton, {}, content);
await TestUtils.waitForCondition(() => {
return (
row.renderRoot.querySelector(".dismiss-button") &&
!row.renderRoot.querySelector(".undo-button")
// We set the second client to not have CloseTab as an available command
// to ensure we properly test that path
if (client.id === 2) {
Assert.ok(
!row.renderRoot.querySelector(".dismiss-button"),
`Dismiss button should NOT appear for tab ${
j + 1
} on the client that does not have available commands.`
);
}, `Dismiss button is restored after undoing tab ${j + 1}.`);
} else {
// We need to use renderRoot since Lit components querySelector
// won't return the right things
await BrowserTestUtils.waitForCondition(
() => row.renderRoot.querySelector(".dismiss-button") !== null,
`Dismiss button should appear for tab ${j + 1}`
);
// Check the presence of the dismiss button
const dismissButton = row.renderRoot.querySelector(".dismiss-button");
Assert.ok(dismissButton, `Dismiss button is present on tab ${j + 1}.`);
// Simulate clicking the dismiss button
EventUtils.synthesizeMouseAtCenter(dismissButton, {}, content);
await TestUtils.waitForCondition(() => {
const undoButton = row.renderRoot.querySelector(".undo-button");
return undoButton && undoButton.style.display !== "none";
}, `Undo button is shown after dismissing tab ${j + 1}.`);
// Simulate clicking the undo button
const undoButton = row.renderRoot.querySelector(".undo-button");
EventUtils.synthesizeMouseAtCenter(
row.mainEl,
{ type: "mouseover" },
content
);
EventUtils.synthesizeMouseAtCenter(undoButton, {}, content);
await TestUtils.waitForCondition(() => {
return (
row.renderRoot.querySelector(".dismiss-button") &&
!row.renderRoot.querySelector(".undo-button")
);
}, `Dismiss button is restored after undoing tab ${j + 1}.`);
}
}
}
@ -184,4 +200,45 @@ add_task(async function test_syncedtabs_searchbox_focus() {
searchTextbox,
"Check search box is focused"
);
SidebarController.hide();
});
add_task(async function test_close_remote_tab_context_menu() {
const sandbox = sinon.createSandbox();
sandbox.stub(lazy.SyncedTabsErrorHandler, "getErrorType").returns(null);
sandbox.stub(lazy.TabsSetupFlowManager, "uiStateIndex").value(4);
sandbox.stub(lazy.SyncedTabs, "getTabClients").resolves(tabClients);
sandbox
.stub(lazy.SyncedTabs, "createRecentTabsList")
.resolves(tabClients.flatMap(client => client.tabs));
await SidebarController.show("viewTabsSidebar");
const { contentDocument } = SidebarController.browser;
const component = contentDocument.querySelector("sidebar-syncedtabs");
Assert.ok(component, "Synced tabs panel is shown.");
const contextMenu = SidebarController.currentContextMenu;
// Verify that the context menu is available
info("Check if the context menu is present in the DOM.");
Assert.ok(contextMenu, "Context menu is present.");
// Verify "Close Remote Tab" context menu item
info("Verify 'Close Remote Tab' context menu item.");
const rows = await TestUtils.waitForCondition(() => {
const { rowEls } = component.cards[0].querySelector("sidebar-tab-list");
return rowEls.length && rowEls;
}, "Device has the correct number of tabs.");
await openAndWaitForContextMenu(contextMenu, rows[0], () => {
const closeTabMenuItem = contextMenu.querySelector(
"#sidebar-context-menu-close-remote-tab"
);
Assert.ok(closeTabMenuItem, "'Close Remote Tab' menu item is present.");
Assert.ok(
!closeTabMenuItem.disabled,
"'Close Remote Tab' menu item is enabled."
);
});
SidebarController.hide();
sandbox.restore();
});

View File

@ -75,6 +75,10 @@ sidebar-context-menu-bookmark-tab =
.label = Bookmark Tab…
sidebar-context-menu-copy-link =
.label = Copy Link
# Variables:
# $deviceName (String) - The name of the device the user is closing a tab for
sidebar-context-menu-close-remote-tab =
.label = Close tab on { $deviceName }
## Labels for sidebar history context menu items
@ -107,7 +111,7 @@ sidebar-menu-syncedtabs-header =
# Context for hovering over the close tab button that will
# send a push to the device to close said tab
# Variables
# $deviceName - the name of the device the user is closing a tab for
# Variables:
# $deviceName (String) - the name of the device the user is closing a tab for
synced-tabs-context-close-tab-title =
.title = Close tab on { $deviceName }