Bug 1953575 - add 'copy link' to sharing menu on macOS and offer on non-mac/windows as an alternative to sharing, r=mossop,fluent-reviewers,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D241487
This commit is contained in:
@@ -60,6 +60,9 @@ skip-if = ["os == 'linux' && socketprocess_networking"]
|
||||
["browser_contextmenu_save_blocked.js"]
|
||||
skip-if = ["os == 'linux' && socketprocess_networking"]
|
||||
|
||||
["browser_contextmenu_share_linux.js"]
|
||||
run-if = ["os == 'linux'"]
|
||||
|
||||
["browser_contextmenu_share_macosx.js"]
|
||||
https_first_disabled = true
|
||||
support-files = ["browser_contextmenu_shareurl.html"]
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const BASE = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"https://example.com"
|
||||
);
|
||||
const TEST_URL = BASE + "browser_contextmenu_shareurl.html";
|
||||
|
||||
/**
|
||||
* Test the "Share" item in the tab contextmenu on Linux.
|
||||
*/
|
||||
add_task(async function test_contextmenu_share_linux() {
|
||||
await BrowserTestUtils.withNewTab(TEST_URL, async () => {
|
||||
await openTabContextMenu(gBrowser.selectedTab);
|
||||
|
||||
let contextMenu = document.getElementById("tabContextMenu");
|
||||
let contextMenuClosedPromise = BrowserTestUtils.waitForPopupEvent(
|
||||
contextMenu,
|
||||
"hidden"
|
||||
);
|
||||
let itemCreated = contextMenu.querySelector(".share-tab-url-item");
|
||||
ok(itemCreated, "Got Share item on Linux");
|
||||
await SimpleTest.promiseClipboardChange(TEST_URL, () =>
|
||||
contextMenu.activateItem(itemCreated)
|
||||
);
|
||||
ok(true, "Copied to clipboard.");
|
||||
|
||||
await contextMenuClosedPromise;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper for opening the toolbar context menu.
|
||||
*/
|
||||
async function openTabContextMenu(tab) {
|
||||
info("Opening tab context menu");
|
||||
let contextMenu = document.getElementById("tabContextMenu");
|
||||
let openTabContextMenuPromise = BrowserTestUtils.waitForPopupEvent(
|
||||
contextMenu,
|
||||
"shown"
|
||||
);
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(tab, { type: "contextmenu" });
|
||||
await openTabContextMenuPromise;
|
||||
}
|
||||
@@ -69,25 +69,26 @@ add_task(async function test_contextmenu_share_macosx() {
|
||||
ok(getSharingProvidersSpy.calledOnce, "getSharingProviders called");
|
||||
|
||||
info(
|
||||
"Check we have a service and one extra menu item for the More... button"
|
||||
"Check we have copy link, a service and one extra menu item for the More... button"
|
||||
);
|
||||
let popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
|
||||
let items = popup.querySelectorAll("menuitem");
|
||||
is(items.length, 2, "There should be 2 sharing services.");
|
||||
let items = Array.from(popup.querySelectorAll("menuitem"));
|
||||
is(items.length, 3, "There should be 3 sharing services.");
|
||||
|
||||
info("Click on the sharing service");
|
||||
let menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
|
||||
contextMenu,
|
||||
"hidden"
|
||||
);
|
||||
let shareButton = items[0];
|
||||
is(
|
||||
shareButton.label,
|
||||
mockShareData[0].menuItemTitle,
|
||||
let shareButton = items.find(
|
||||
t => t.label == mockShareData[0].menuItemTitle
|
||||
);
|
||||
ok(
|
||||
shareButton,
|
||||
"Share button's label should match the service's menu item title. "
|
||||
);
|
||||
is(
|
||||
shareButton.getAttribute("share-name"),
|
||||
shareButton?.getAttribute("share-name"),
|
||||
mockShareData[0].name,
|
||||
"Share button's share-name value should match the service's name. "
|
||||
);
|
||||
@@ -103,23 +104,45 @@ add_task(async function test_contextmenu_share_macosx() {
|
||||
is(url, TEST_URL, "Shared correct URL");
|
||||
is(title, "Sharing URL", "Shared the correct title.");
|
||||
|
||||
info("Test the More... button");
|
||||
info("Test the copy link button");
|
||||
contextMenu = await openTabContextMenu(gBrowser.selectedTab);
|
||||
await openMenuPopup(contextMenu);
|
||||
// Since the tab context menu was collapsed previously, the popup needs to get the
|
||||
// providers again.
|
||||
ok(getSharingProvidersSpy.calledTwice, "getSharingProviders called again");
|
||||
popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
|
||||
items = popup.querySelectorAll("menuitem");
|
||||
is(items.length, 2, "There should be 2 sharing services.");
|
||||
|
||||
info("Click on the More Button");
|
||||
let moreButton = items[1];
|
||||
items = Array.from(popup.querySelectorAll("menuitem"));
|
||||
is(items.length, 3, "There should be 3 sharing services.");
|
||||
info("Click on the Copy Link item");
|
||||
let copyLinkItem = items.find(
|
||||
item => item.dataset.l10nId == "menu-share-copy-link"
|
||||
);
|
||||
menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
|
||||
contextMenu,
|
||||
"hidden"
|
||||
);
|
||||
popup.activateItem(moreButton);
|
||||
await SimpleTest.promiseClipboardChange(TEST_URL, () =>
|
||||
popup.activateItem(copyLinkItem)
|
||||
);
|
||||
await menuPopupClosedPromised;
|
||||
|
||||
info("Test the More... item");
|
||||
contextMenu = await openTabContextMenu(gBrowser.selectedTab);
|
||||
await openMenuPopup(contextMenu);
|
||||
// Since the tab context menu was collapsed previously, the popup needs to get the
|
||||
// providers again.
|
||||
is(getSharingProvidersSpy.callCount, 3, "getSharingProviders called again");
|
||||
popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
|
||||
items = popup.querySelectorAll("menuitem");
|
||||
is(items.length, 3, "There should be 3 sharing services.");
|
||||
|
||||
info("Click on the More item");
|
||||
let moreMenuitem = items[2];
|
||||
menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
|
||||
contextMenu,
|
||||
"hidden"
|
||||
);
|
||||
popup.activateItem(moreMenuitem);
|
||||
await menuPopupClosedPromised;
|
||||
ok(openSharingPreferencesSpy.calledOnce, "openSharingPreferences called");
|
||||
});
|
||||
|
||||
@@ -8,12 +8,11 @@ const { sinon } = ChromeUtils.importESModule(
|
||||
);
|
||||
const BASE = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
|
||||
"http://example.com"
|
||||
"https://example.com"
|
||||
);
|
||||
const TEST_URL = BASE + "browser_contextmenu_shareurl.html";
|
||||
|
||||
// Setup spies for observing function calls from MacSharingService
|
||||
// Setup spies for observing function calls from WindowsUIUtils.
|
||||
let shareUrlSpy = sinon.spy();
|
||||
|
||||
SharingUtils.testOnlyMockUIUtils({
|
||||
@@ -23,7 +22,7 @@ SharingUtils.testOnlyMockUIUtils({
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowsUIUtils]),
|
||||
});
|
||||
|
||||
registerCleanupFunction(async function () {
|
||||
registerCleanupFunction(function () {
|
||||
SharingUtils.testOnlyMockUIUtils(null);
|
||||
});
|
||||
|
||||
|
||||
@@ -983,6 +983,9 @@ panel-save-update-password = Password
|
||||
# "More" item in macOS share menu
|
||||
menu-share-more =
|
||||
.label = More…
|
||||
menu-share-copy-link =
|
||||
.label = Copy Link
|
||||
.accesskey = L
|
||||
ui-tour-info-panel-close =
|
||||
.tooltiptext = Close
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
import { BrowserUtils } from "resource://gre/modules/BrowserUtils.sys.mjs";
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const APPLE_COPY_LINK = "com.apple.share.CopyLink.invite";
|
||||
|
||||
let lazy = {};
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||
@@ -26,20 +28,16 @@ class SharingUtilsCls {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only support "share URL" on macOS and on Windows:
|
||||
if (AppConstants.platform != "macosx" && AppConstants.platform != "win") {
|
||||
return;
|
||||
}
|
||||
|
||||
let shareURL = insertAfterEl.nextElementSibling;
|
||||
if (!shareURL?.matches(".share-tab-url-item")) {
|
||||
shareURL = this.#createShareURLMenuItem(insertAfterEl);
|
||||
}
|
||||
|
||||
shareURL.browserToShare = Cu.getWeakReference(browser);
|
||||
if (AppConstants.platform == "win") {
|
||||
// We disable the item on Windows, as there's no submenu.
|
||||
// On macOS, we handle this inside the menupopup.
|
||||
if (AppConstants.platform != "macosx") {
|
||||
// On macOS, we keep the item enabled and handle enabled state
|
||||
// inside the menupopup.
|
||||
// Everywhere else, we disable the item, as there's no submenu.
|
||||
shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
|
||||
}
|
||||
}
|
||||
@@ -51,18 +49,21 @@ class SharingUtilsCls {
|
||||
let menu = insertAfterEl.parentNode;
|
||||
let shareURL = null;
|
||||
let document = insertAfterEl.ownerDocument;
|
||||
if (AppConstants.platform != "win" && AppConstants.platform != "macosx") {
|
||||
shareURL = this.#buildCopyLinkItem(document);
|
||||
} else {
|
||||
if (AppConstants.platform == "win") {
|
||||
shareURL = this.#buildShareURLItem(document);
|
||||
} else if (AppConstants.platform == "macosx") {
|
||||
shareURL = this.#buildShareURLMenu(document);
|
||||
}
|
||||
shareURL.className = "share-tab-url-item";
|
||||
|
||||
let l10nID =
|
||||
menu.id == "tabContextMenu"
|
||||
? "tab-context-share-url"
|
||||
: "menu-file-share-url";
|
||||
document.l10n.setAttributes(shareURL, l10nID);
|
||||
}
|
||||
shareURL.classList.add("share-tab-url-item");
|
||||
|
||||
menu.insertBefore(shareURL, insertAfterEl.nextSibling);
|
||||
return shareURL;
|
||||
@@ -88,6 +89,32 @@ class SharingUtilsCls {
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a menuitem that only copies the link. Useful for
|
||||
* OSes where we do not yet have full share support, like Linux.
|
||||
*
|
||||
* We currently also use this on macOS because for some reason Apple does not
|
||||
* provide the share service option for this.
|
||||
*/
|
||||
#buildCopyLinkItem(document) {
|
||||
let shareURLMenuItem = document.createXULElement("menuitem");
|
||||
document.l10n.setAttributes(shareURLMenuItem, "menu-share-copy-link");
|
||||
shareURLMenuItem.classList.add("share-copy-link");
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
shareURLMenuItem.classList.add("menuitem-iconic");
|
||||
shareURLMenuItem.setAttribute(
|
||||
"image",
|
||||
"chrome://global/skin/icons/link.svg"
|
||||
);
|
||||
} else {
|
||||
// On macOS the command handling happens by virtue of the submenu
|
||||
// command event listener.
|
||||
shareURLMenuItem.addEventListener("command", this);
|
||||
}
|
||||
return shareURLMenuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sharing data for a given DOM node.
|
||||
*/
|
||||
@@ -136,6 +163,16 @@ class SharingUtilsCls {
|
||||
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
||||
let services = lazy.MacSharingService.getSharingProviders(currentURI);
|
||||
|
||||
// Apple seems reluctant to provide copy link as a feature. Add it at the
|
||||
// start if it's not there.
|
||||
if (!services.some(s => s.name == APPLE_COPY_LINK)) {
|
||||
let item = this.#buildCopyLinkItem(document);
|
||||
if (!shouldEnable) {
|
||||
item.setAttribute("disabled", "true");
|
||||
}
|
||||
menuPopup.appendChild(item);
|
||||
}
|
||||
|
||||
services.forEach(share => {
|
||||
let item = document.createXULElement("menuitem");
|
||||
item.classList.add("menuitem-iconic");
|
||||
@@ -181,18 +218,18 @@ class SharingUtilsCls {
|
||||
let { urlToShare, titleToShare } = this.getDataToShare(target);
|
||||
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
||||
|
||||
if (AppConstants.platform == "win") {
|
||||
if (event.target.classList.contains("share-copy-link")) {
|
||||
BrowserUtils.copyLink(currentURI, titleToShare);
|
||||
} else if (AppConstants.platform == "win") {
|
||||
lazy.WindowsUIUtils.shareUrl(currentURI, titleToShare);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// On macOSX platforms
|
||||
let shareName = event.target.getAttribute("share-name");
|
||||
|
||||
if (shareName) {
|
||||
lazy.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPopupHiding(event) {
|
||||
// We don't want to rebuild the contents of the "Share" menupopup if only its submenu is
|
||||
|
||||
@@ -44,5 +44,5 @@ interface nsIWindowsUIUtils : nsISupports
|
||||
/**
|
||||
* Share URL
|
||||
*/
|
||||
void shareUrl(in AString shareTitle, in AString urlToShare);
|
||||
void shareUrl(in AString urlToShare, in AString shareTitle);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user