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"]
|
["browser_contextmenu_save_blocked.js"]
|
||||||
skip-if = ["os == 'linux' && socketprocess_networking"]
|
skip-if = ["os == 'linux' && socketprocess_networking"]
|
||||||
|
|
||||||
|
["browser_contextmenu_share_linux.js"]
|
||||||
|
run-if = ["os == 'linux'"]
|
||||||
|
|
||||||
["browser_contextmenu_share_macosx.js"]
|
["browser_contextmenu_share_macosx.js"]
|
||||||
https_first_disabled = true
|
https_first_disabled = true
|
||||||
support-files = ["browser_contextmenu_shareurl.html"]
|
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");
|
ok(getSharingProvidersSpy.calledOnce, "getSharingProviders called");
|
||||||
|
|
||||||
info(
|
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 popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
|
||||||
let items = popup.querySelectorAll("menuitem");
|
let items = Array.from(popup.querySelectorAll("menuitem"));
|
||||||
is(items.length, 2, "There should be 2 sharing services.");
|
is(items.length, 3, "There should be 3 sharing services.");
|
||||||
|
|
||||||
info("Click on the sharing service");
|
info("Click on the sharing service");
|
||||||
let menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
|
let menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
|
||||||
contextMenu,
|
contextMenu,
|
||||||
"hidden"
|
"hidden"
|
||||||
);
|
);
|
||||||
let shareButton = items[0];
|
let shareButton = items.find(
|
||||||
is(
|
t => t.label == mockShareData[0].menuItemTitle
|
||||||
shareButton.label,
|
);
|
||||||
mockShareData[0].menuItemTitle,
|
ok(
|
||||||
|
shareButton,
|
||||||
"Share button's label should match the service's menu item title. "
|
"Share button's label should match the service's menu item title. "
|
||||||
);
|
);
|
||||||
is(
|
is(
|
||||||
shareButton.getAttribute("share-name"),
|
shareButton?.getAttribute("share-name"),
|
||||||
mockShareData[0].name,
|
mockShareData[0].name,
|
||||||
"Share button's share-name value should match the service's 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(url, TEST_URL, "Shared correct URL");
|
||||||
is(title, "Sharing URL", "Shared the correct title.");
|
is(title, "Sharing URL", "Shared the correct title.");
|
||||||
|
|
||||||
info("Test the More... button");
|
info("Test the copy link button");
|
||||||
contextMenu = await openTabContextMenu(gBrowser.selectedTab);
|
contextMenu = await openTabContextMenu(gBrowser.selectedTab);
|
||||||
await openMenuPopup(contextMenu);
|
await openMenuPopup(contextMenu);
|
||||||
// Since the tab context menu was collapsed previously, the popup needs to get the
|
// Since the tab context menu was collapsed previously, the popup needs to get the
|
||||||
// providers again.
|
// providers again.
|
||||||
ok(getSharingProvidersSpy.calledTwice, "getSharingProviders called again");
|
ok(getSharingProvidersSpy.calledTwice, "getSharingProviders called again");
|
||||||
popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
|
popup = contextMenu.querySelector(".share-tab-url-item").menupopup;
|
||||||
items = popup.querySelectorAll("menuitem");
|
items = Array.from(popup.querySelectorAll("menuitem"));
|
||||||
is(items.length, 2, "There should be 2 sharing services.");
|
is(items.length, 3, "There should be 3 sharing services.");
|
||||||
|
info("Click on the Copy Link item");
|
||||||
info("Click on the More Button");
|
let copyLinkItem = items.find(
|
||||||
let moreButton = items[1];
|
item => item.dataset.l10nId == "menu-share-copy-link"
|
||||||
|
);
|
||||||
menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
|
menuPopupClosedPromised = BrowserTestUtils.waitForPopupEvent(
|
||||||
contextMenu,
|
contextMenu,
|
||||||
"hidden"
|
"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;
|
await menuPopupClosedPromised;
|
||||||
ok(openSharingPreferencesSpy.calledOnce, "openSharingPreferences called");
|
ok(openSharingPreferencesSpy.calledOnce, "openSharingPreferences called");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ const { sinon } = ChromeUtils.importESModule(
|
|||||||
);
|
);
|
||||||
const BASE = getRootDirectory(gTestPath).replace(
|
const BASE = getRootDirectory(gTestPath).replace(
|
||||||
"chrome://mochitests/content",
|
"chrome://mochitests/content",
|
||||||
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
|
"https://example.com"
|
||||||
"http://example.com"
|
|
||||||
);
|
);
|
||||||
const TEST_URL = BASE + "browser_contextmenu_shareurl.html";
|
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();
|
let shareUrlSpy = sinon.spy();
|
||||||
|
|
||||||
SharingUtils.testOnlyMockUIUtils({
|
SharingUtils.testOnlyMockUIUtils({
|
||||||
@@ -23,7 +22,7 @@ SharingUtils.testOnlyMockUIUtils({
|
|||||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowsUIUtils]),
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowsUIUtils]),
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCleanupFunction(async function () {
|
registerCleanupFunction(function () {
|
||||||
SharingUtils.testOnlyMockUIUtils(null);
|
SharingUtils.testOnlyMockUIUtils(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -983,6 +983,9 @@ panel-save-update-password = Password
|
|||||||
# "More" item in macOS share menu
|
# "More" item in macOS share menu
|
||||||
menu-share-more =
|
menu-share-more =
|
||||||
.label = More…
|
.label = More…
|
||||||
|
menu-share-copy-link =
|
||||||
|
.label = Copy Link
|
||||||
|
.accesskey = L
|
||||||
ui-tour-info-panel-close =
|
ui-tour-info-panel-close =
|
||||||
.tooltiptext = 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 { BrowserUtils } from "resource://gre/modules/BrowserUtils.sys.mjs";
|
||||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||||
|
|
||||||
|
const APPLE_COPY_LINK = "com.apple.share.CopyLink.invite";
|
||||||
|
|
||||||
let lazy = {};
|
let lazy = {};
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||||
@@ -26,20 +28,16 @@ class SharingUtilsCls {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only support "share URL" on macOS and on Windows:
|
|
||||||
if (AppConstants.platform != "macosx" && AppConstants.platform != "win") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let shareURL = insertAfterEl.nextElementSibling;
|
let shareURL = insertAfterEl.nextElementSibling;
|
||||||
if (!shareURL?.matches(".share-tab-url-item")) {
|
if (!shareURL?.matches(".share-tab-url-item")) {
|
||||||
shareURL = this.#createShareURLMenuItem(insertAfterEl);
|
shareURL = this.#createShareURLMenuItem(insertAfterEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
shareURL.browserToShare = Cu.getWeakReference(browser);
|
shareURL.browserToShare = Cu.getWeakReference(browser);
|
||||||
if (AppConstants.platform == "win") {
|
if (AppConstants.platform != "macosx") {
|
||||||
// We disable the item on Windows, as there's no submenu.
|
// On macOS, we keep the item enabled and handle enabled state
|
||||||
// On macOS, we handle this inside the menupopup.
|
// inside the menupopup.
|
||||||
|
// Everywhere else, we disable the item, as there's no submenu.
|
||||||
shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
|
shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,18 +49,21 @@ class SharingUtilsCls {
|
|||||||
let menu = insertAfterEl.parentNode;
|
let menu = insertAfterEl.parentNode;
|
||||||
let shareURL = null;
|
let shareURL = null;
|
||||||
let document = insertAfterEl.ownerDocument;
|
let document = insertAfterEl.ownerDocument;
|
||||||
|
if (AppConstants.platform != "win" && AppConstants.platform != "macosx") {
|
||||||
|
shareURL = this.#buildCopyLinkItem(document);
|
||||||
|
} else {
|
||||||
if (AppConstants.platform == "win") {
|
if (AppConstants.platform == "win") {
|
||||||
shareURL = this.#buildShareURLItem(document);
|
shareURL = this.#buildShareURLItem(document);
|
||||||
} else if (AppConstants.platform == "macosx") {
|
} else if (AppConstants.platform == "macosx") {
|
||||||
shareURL = this.#buildShareURLMenu(document);
|
shareURL = this.#buildShareURLMenu(document);
|
||||||
}
|
}
|
||||||
shareURL.className = "share-tab-url-item";
|
|
||||||
|
|
||||||
let l10nID =
|
let l10nID =
|
||||||
menu.id == "tabContextMenu"
|
menu.id == "tabContextMenu"
|
||||||
? "tab-context-share-url"
|
? "tab-context-share-url"
|
||||||
: "menu-file-share-url";
|
: "menu-file-share-url";
|
||||||
document.l10n.setAttributes(shareURL, l10nID);
|
document.l10n.setAttributes(shareURL, l10nID);
|
||||||
|
}
|
||||||
|
shareURL.classList.add("share-tab-url-item");
|
||||||
|
|
||||||
menu.insertBefore(shareURL, insertAfterEl.nextSibling);
|
menu.insertBefore(shareURL, insertAfterEl.nextSibling);
|
||||||
return shareURL;
|
return shareURL;
|
||||||
@@ -88,6 +89,32 @@ class SharingUtilsCls {
|
|||||||
return menu;
|
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.
|
* Get the sharing data for a given DOM node.
|
||||||
*/
|
*/
|
||||||
@@ -136,6 +163,16 @@ class SharingUtilsCls {
|
|||||||
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
||||||
let services = lazy.MacSharingService.getSharingProviders(currentURI);
|
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 => {
|
services.forEach(share => {
|
||||||
let item = document.createXULElement("menuitem");
|
let item = document.createXULElement("menuitem");
|
||||||
item.classList.add("menuitem-iconic");
|
item.classList.add("menuitem-iconic");
|
||||||
@@ -181,18 +218,18 @@ class SharingUtilsCls {
|
|||||||
let { urlToShare, titleToShare } = this.getDataToShare(target);
|
let { urlToShare, titleToShare } = this.getDataToShare(target);
|
||||||
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
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);
|
lazy.WindowsUIUtils.shareUrl(currentURI, titleToShare);
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
// On macOSX platforms
|
// On macOSX platforms
|
||||||
let shareName = event.target.getAttribute("share-name");
|
let shareName = event.target.getAttribute("share-name");
|
||||||
|
|
||||||
if (shareName) {
|
if (shareName) {
|
||||||
lazy.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
|
lazy.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPopupHiding(event) {
|
onPopupHiding(event) {
|
||||||
// We don't want to rebuild the contents of the "Share" menupopup if only its submenu is
|
// 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
|
* 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