Bug 1953575 - move gShareUtils to its own module, r=firefox-desktop-core-reviewers ,tabbrowser-reviewers,dao
Differential Revision: https://phabricator.services.mozilla.com/D241485
This commit is contained in:
@@ -77,6 +77,7 @@ ChromeUtils.defineESModuleGetters(this, {
|
|||||||
SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs",
|
SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs",
|
||||||
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
|
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
|
||||||
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
|
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
|
||||||
|
SharingUtils: "resource:///modules/SharingUtils.sys.mjs",
|
||||||
ShoppingSidebarParent: "resource:///actors/ShoppingSidebarParent.sys.mjs",
|
ShoppingSidebarParent: "resource:///actors/ShoppingSidebarParent.sys.mjs",
|
||||||
ShoppingSidebarManager: "resource:///actors/ShoppingSidebarParent.sys.mjs",
|
ShoppingSidebarManager: "resource:///actors/ShoppingSidebarParent.sys.mjs",
|
||||||
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
|
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
|
||||||
@@ -2053,7 +2054,7 @@ let gFileMenu = {
|
|||||||
this.updateImportCommandEnabledState();
|
this.updateImportCommandEnabledState();
|
||||||
this.updateTabCloseCountState();
|
this.updateTabCloseCountState();
|
||||||
if (AppConstants.platform == "macosx") {
|
if (AppConstants.platform == "macosx") {
|
||||||
gShareUtils.updateShareURLMenuItem(
|
SharingUtils.updateShareURLMenuItem(
|
||||||
gBrowser.selectedBrowser,
|
gBrowser.selectedBrowser,
|
||||||
document.getElementById("menu_savePage")
|
document.getElementById("menu_savePage")
|
||||||
);
|
);
|
||||||
@@ -2062,215 +2063,6 @@ let gFileMenu = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let gShareUtils = {
|
|
||||||
/**
|
|
||||||
* Updates a sharing item in a given menu, creating it if necessary.
|
|
||||||
*/
|
|
||||||
updateShareURLMenuItem(browser, insertAfterEl) {
|
|
||||||
if (!Services.prefs.getBoolPref("browser.menu.share_url.allow", true)) {
|
|
||||||
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.
|
|
||||||
shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and returns the "Share" menu item.
|
|
||||||
*/
|
|
||||||
_createShareURLMenuItem(insertAfterEl) {
|
|
||||||
let menu = insertAfterEl.parentNode;
|
|
||||||
let shareURL = null;
|
|
||||||
if (AppConstants.platform == "win") {
|
|
||||||
shareURL = this._buildShareURLItem(menu.id);
|
|
||||||
} else if (AppConstants.platform == "macosx") {
|
|
||||||
shareURL = this._buildShareURLMenu(menu.id);
|
|
||||||
}
|
|
||||||
shareURL.className = "share-tab-url-item";
|
|
||||||
|
|
||||||
let l10nID =
|
|
||||||
menu.id == "tabContextMenu"
|
|
||||||
? "tab-context-share-url"
|
|
||||||
: "menu-file-share-url";
|
|
||||||
document.l10n.setAttributes(shareURL, l10nID);
|
|
||||||
|
|
||||||
menu.insertBefore(shareURL, insertAfterEl.nextSibling);
|
|
||||||
return shareURL;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a menu item specifically for accessing Windows sharing services.
|
|
||||||
*/
|
|
||||||
_buildShareURLItem() {
|
|
||||||
let shareURLMenuItem = document.createXULElement("menuitem");
|
|
||||||
shareURLMenuItem.addEventListener("command", this);
|
|
||||||
return shareURLMenuItem;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a menu specifically for accessing macOSx sharing services .
|
|
||||||
*/
|
|
||||||
_buildShareURLMenu() {
|
|
||||||
let menu = document.createXULElement("menu");
|
|
||||||
let menuPopup = document.createXULElement("menupopup");
|
|
||||||
menuPopup.addEventListener("popupshowing", this);
|
|
||||||
menu.appendChild(menuPopup);
|
|
||||||
return menu;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the sharing data for a given DOM node.
|
|
||||||
*/
|
|
||||||
getDataToShare(node) {
|
|
||||||
let browser = node.browserToShare?.get();
|
|
||||||
let urlToShare = null;
|
|
||||||
let titleToShare = null;
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
let maybeToShare = BrowserUtils.getShareableURL(browser.currentURI);
|
|
||||||
if (maybeToShare) {
|
|
||||||
urlToShare = maybeToShare;
|
|
||||||
titleToShare = browser.contentTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { urlToShare, titleToShare };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the "Share" menupopup on macOSx.
|
|
||||||
*/
|
|
||||||
initializeShareURLPopup(menuPopup) {
|
|
||||||
if (AppConstants.platform != "macosx") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty menupopup
|
|
||||||
while (menuPopup.firstChild) {
|
|
||||||
menuPopup.firstChild.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
let { urlToShare } = this.getDataToShare(menuPopup.parentNode);
|
|
||||||
|
|
||||||
// If we can't share the current URL, we display the items disabled,
|
|
||||||
// but enable the "more..." item at the bottom, to allow the user to
|
|
||||||
// change sharing preferences in the system dialog.
|
|
||||||
let shouldEnable = !!urlToShare;
|
|
||||||
if (!urlToShare) {
|
|
||||||
// Fake it so we can ask the sharing service for services:
|
|
||||||
urlToShare = makeURI("https://mozilla.org/");
|
|
||||||
}
|
|
||||||
|
|
||||||
let sharingService = gBrowser.MacSharingService;
|
|
||||||
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
|
||||||
let services = sharingService.getSharingProviders(currentURI);
|
|
||||||
|
|
||||||
services.forEach(share => {
|
|
||||||
let item = document.createXULElement("menuitem");
|
|
||||||
item.classList.add("menuitem-iconic");
|
|
||||||
item.setAttribute("label", share.menuItemTitle);
|
|
||||||
item.setAttribute("share-name", share.name);
|
|
||||||
item.setAttribute("image", share.image);
|
|
||||||
if (!shouldEnable) {
|
|
||||||
item.setAttribute("disabled", "true");
|
|
||||||
}
|
|
||||||
menuPopup.appendChild(item);
|
|
||||||
});
|
|
||||||
menuPopup.appendChild(document.createXULElement("menuseparator"));
|
|
||||||
let moreItem = document.createXULElement("menuitem");
|
|
||||||
document.l10n.setAttributes(moreItem, "menu-share-more");
|
|
||||||
moreItem.classList.add("menuitem-iconic", "share-more-button");
|
|
||||||
menuPopup.appendChild(moreItem);
|
|
||||||
|
|
||||||
menuPopup.addEventListener("command", this);
|
|
||||||
menuPopup.parentNode
|
|
||||||
.closest("menupopup")
|
|
||||||
.addEventListener("popuphiding", this);
|
|
||||||
menuPopup.setAttribute("data-initialized", true);
|
|
||||||
},
|
|
||||||
|
|
||||||
onShareURLCommand(event) {
|
|
||||||
// Only call sharing services for the "Share" menu item. These services
|
|
||||||
// are accessed from a submenu popup for MacOS or the "Share" menu item
|
|
||||||
// for Windows. Use .closest() as a hack to find either the item itself
|
|
||||||
// or a parent with the right class.
|
|
||||||
let target = event.target.closest(".share-tab-url-item");
|
|
||||||
if (!target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// urlToShare/titleToShare may be null, in which case only the "more"
|
|
||||||
// item is enabled, so handle that case first:
|
|
||||||
if (event.target.classList.contains("share-more-button")) {
|
|
||||||
gBrowser.MacSharingService.openSharingPreferences();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { urlToShare, titleToShare } = this.getDataToShare(target);
|
|
||||||
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
|
||||||
|
|
||||||
if (AppConstants.platform == "win") {
|
|
||||||
WindowsUIUtils.shareUrl(currentURI, titleToShare);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On macOSX platforms
|
|
||||||
let shareName = event.target.getAttribute("share-name");
|
|
||||||
|
|
||||||
if (shareName) {
|
|
||||||
gBrowser.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onPopupHiding(event) {
|
|
||||||
// We don't want to rebuild the contents of the "Share" menupopup if only its submenu is
|
|
||||||
// hidden. So bail if this isn't the top menupopup in the DOM tree:
|
|
||||||
if (event.target.parentNode.closest("menupopup")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Otherwise, clear its "data-initialized" attribute.
|
|
||||||
let menupopup = event.target.querySelector(
|
|
||||||
".share-tab-url-item"
|
|
||||||
)?.menupopup;
|
|
||||||
menupopup?.removeAttribute("data-initialized");
|
|
||||||
|
|
||||||
event.target.removeEventListener("popuphiding", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
onPopupShowing(event) {
|
|
||||||
if (!event.target.hasAttribute("data-initialized")) {
|
|
||||||
this.initializeShareURLPopup(event.target);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent(aEvent) {
|
|
||||||
switch (aEvent.type) {
|
|
||||||
case "command":
|
|
||||||
this.onShareURLCommand(aEvent);
|
|
||||||
break;
|
|
||||||
case "popuphiding":
|
|
||||||
this.onPopupHiding(aEvent);
|
|
||||||
break;
|
|
||||||
case "popupshowing":
|
|
||||||
this.onPopupShowing(aEvent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a new tab with the userContextId specified as an attribute of
|
* Opens a new tab with the userContextId specified as an attribute of
|
||||||
* sourceEvent. This attribute is propagated to the top level originAttributes
|
* sourceEvent. This attribute is propagated to the top level originAttributes
|
||||||
|
|||||||
@@ -51,7 +51,6 @@
|
|||||||
"OpenBrowserWindow",
|
"OpenBrowserWindow",
|
||||||
"updateEditUIVisibility",
|
"updateEditUIVisibility",
|
||||||
"gFileMenu",
|
"gFileMenu",
|
||||||
"gShareUtils",
|
|
||||||
"openNewUserContextTab",
|
"openNewUserContextTab",
|
||||||
"XULBrowserWindow",
|
"XULBrowserWindow",
|
||||||
"LinkTargetDisplay",
|
"LinkTargetDisplay",
|
||||||
@@ -149,6 +148,7 @@
|
|||||||
"SearchUIUtils",
|
"SearchUIUtils",
|
||||||
"SessionStartup",
|
"SessionStartup",
|
||||||
"SessionStore",
|
"SessionStore",
|
||||||
|
"SharingUtils",
|
||||||
"ShoppingSidebarParent",
|
"ShoppingSidebarParent",
|
||||||
"ShoppingSidebarManager",
|
"ShoppingSidebarManager",
|
||||||
"ShortcutUtils",
|
"ShortcutUtils",
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ let shareUrlSpy = sinon.spy();
|
|||||||
let openSharingPreferencesSpy = sinon.spy();
|
let openSharingPreferencesSpy = sinon.spy();
|
||||||
let getSharingProvidersSpy = sinon.spy();
|
let getSharingProvidersSpy = sinon.spy();
|
||||||
|
|
||||||
let stub = sinon.stub(gBrowser, "MacSharingService").get(() => {
|
let { MockRegistrar } = ChromeUtils.importESModule(
|
||||||
return {
|
"resource://testing-common/MockRegistrar.sys.mjs"
|
||||||
|
);
|
||||||
|
let mockMacSharingService = MockRegistrar.register(
|
||||||
|
"@mozilla.org/widget/macsharingservice;1",
|
||||||
|
{
|
||||||
getSharingProviders(url) {
|
getSharingProviders(url) {
|
||||||
getSharingProvidersSpy(url);
|
getSharingProvidersSpy(url);
|
||||||
return mockShareData;
|
return mockShareData;
|
||||||
@@ -40,11 +44,12 @@ let stub = sinon.stub(gBrowser, "MacSharingService").get(() => {
|
|||||||
openSharingPreferences() {
|
openSharingPreferences() {
|
||||||
openSharingPreferencesSpy();
|
openSharingPreferencesSpy();
|
||||||
},
|
},
|
||||||
};
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIMacSharingService]),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
registerCleanupFunction(async function () {
|
registerCleanupFunction(function () {
|
||||||
stub.restore();
|
MockRegistrar.unregister(mockMacSharingService);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,16 +16,15 @@ const TEST_URL = BASE + "browser_contextmenu_shareurl.html";
|
|||||||
// Setup spies for observing function calls from MacSharingService
|
// Setup spies for observing function calls from MacSharingService
|
||||||
let shareUrlSpy = sinon.spy();
|
let shareUrlSpy = sinon.spy();
|
||||||
|
|
||||||
let stub = sinon.stub(gBrowser.ownerGlobal, "WindowsUIUtils").get(() => {
|
SharingUtils.testOnlyMockUIUtils({
|
||||||
return {
|
|
||||||
shareUrl(url, title) {
|
shareUrl(url, title) {
|
||||||
shareUrlSpy(url, title);
|
shareUrlSpy(url, title);
|
||||||
},
|
},
|
||||||
};
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowsUIUtils]),
|
||||||
});
|
});
|
||||||
|
|
||||||
registerCleanupFunction(async function () {
|
registerCleanupFunction(async function () {
|
||||||
stub.restore();
|
SharingUtils.testOnlyMockUIUtils(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ let shareUrlSpy = sinon.spy();
|
|||||||
let openSharingPreferencesSpy = sinon.spy();
|
let openSharingPreferencesSpy = sinon.spy();
|
||||||
let getSharingProvidersSpy = sinon.spy();
|
let getSharingProvidersSpy = sinon.spy();
|
||||||
|
|
||||||
let stub = sinon.stub(gBrowser, "MacSharingService").get(() => {
|
let { MockRegistrar } = ChromeUtils.importESModule(
|
||||||
return {
|
"resource://testing-common/MockRegistrar.sys.mjs"
|
||||||
|
);
|
||||||
|
let mockMacSharingService = MockRegistrar.register(
|
||||||
|
"@mozilla.org/widget/macsharingservice;1",
|
||||||
|
{
|
||||||
getSharingProviders(url) {
|
getSharingProviders(url) {
|
||||||
getSharingProvidersSpy(url);
|
getSharingProvidersSpy(url);
|
||||||
return mockShareData;
|
return mockShareData;
|
||||||
@@ -40,11 +44,12 @@ let stub = sinon.stub(gBrowser, "MacSharingService").get(() => {
|
|||||||
openSharingPreferences() {
|
openSharingPreferences() {
|
||||||
openSharingPreferencesSpy();
|
openSharingPreferencesSpy();
|
||||||
},
|
},
|
||||||
};
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIMacSharingService]),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
registerCleanupFunction(async function () {
|
registerCleanupFunction(function () {
|
||||||
stub.restore();
|
MockRegistrar.unregister(mockMacSharingService);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,13 +73,15 @@ add_task(async function test_file_menu_share() {
|
|||||||
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 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 2 sharing services.");
|
||||||
|
|
||||||
info("Click on the sharing service");
|
info("Click on the sharing service");
|
||||||
let shareButton = items[0];
|
let shareButton = items.find(
|
||||||
|
t => t.label == mockShareData[0].menuItemTitle
|
||||||
|
);
|
||||||
is(
|
is(
|
||||||
shareButton.label,
|
shareButton.label,
|
||||||
mockShareData[0].menuItemTitle,
|
mockShareData[0].menuItemTitle,
|
||||||
@@ -98,6 +105,25 @@ add_task(async function test_file_menu_share() {
|
|||||||
await simulateMenuClosed(popup);
|
await simulateMenuClosed(popup);
|
||||||
await simulateMenuClosed(menu);
|
await simulateMenuClosed(menu);
|
||||||
|
|
||||||
|
info("Test the copy link button");
|
||||||
|
await simulateMenuOpen(menu);
|
||||||
|
popup = menu.querySelector(".share-tab-url-item").menupopup;
|
||||||
|
await simulateMenuOpen(popup);
|
||||||
|
// Since the menu was collapsed previously, the popup needs to get the
|
||||||
|
// providers again.
|
||||||
|
ok(getSharingProvidersSpy.calledTwice, "getSharingProviders called again");
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
await SimpleTest.promiseClipboardChange(TEST_URL, () =>
|
||||||
|
copyLinkItem.doCommand()
|
||||||
|
);
|
||||||
|
await simulateMenuClosed(popup);
|
||||||
|
await simulateMenuClosed(menu);
|
||||||
|
|
||||||
info("Test the More... button");
|
info("Test the More... button");
|
||||||
|
|
||||||
await simulateMenuOpen(menu);
|
await simulateMenuOpen(menu);
|
||||||
@@ -105,12 +131,12 @@ add_task(async function test_file_menu_share() {
|
|||||||
await simulateMenuOpen(popup);
|
await simulateMenuOpen(popup);
|
||||||
// Since the menu was collapsed previously, the popup needs to get the
|
// Since the menu was collapsed previously, the popup needs to get the
|
||||||
// providers again.
|
// providers again.
|
||||||
ok(getSharingProvidersSpy.calledTwice, "getSharingProviders called again");
|
is(getSharingProvidersSpy.callCount, 3, "getSharingProviders called again");
|
||||||
items = popup.querySelectorAll("menuitem");
|
items = 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 More Button");
|
info("Click on the More Button");
|
||||||
let moreButton = items[1];
|
let moreButton = items[2];
|
||||||
moreButton.doCommand();
|
moreButton.doCommand();
|
||||||
ok(openSharingPreferencesSpy.calledOnce, "openSharingPreferences called");
|
ok(openSharingPreferencesSpy.calledOnce, "openSharingPreferences called");
|
||||||
// Tidy up:
|
// Tidy up:
|
||||||
|
|||||||
@@ -113,12 +113,6 @@
|
|||||||
UrlbarProviderOpenTabs:
|
UrlbarProviderOpenTabs:
|
||||||
"resource:///modules/UrlbarProviderOpenTabs.sys.mjs",
|
"resource:///modules/UrlbarProviderOpenTabs.sys.mjs",
|
||||||
});
|
});
|
||||||
XPCOMUtils.defineLazyServiceGetters(this, {
|
|
||||||
MacSharingService: [
|
|
||||||
"@mozilla.org/widget/macsharingservice;1",
|
|
||||||
"nsIMacSharingService",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
ChromeUtils.defineLazyGetter(this, "tabLocalization", () => {
|
ChromeUtils.defineLazyGetter(this, "tabLocalization", () => {
|
||||||
return new Localization(
|
return new Localization(
|
||||||
["browser/tabbrowser.ftl", "branding/brand.ftl"],
|
["browser/tabbrowser.ftl", "branding/brand.ftl"],
|
||||||
@@ -8867,7 +8861,7 @@ var TabContextMenu = {
|
|||||||
PrivateBrowsingUtils.isWindowPrivate(window);
|
PrivateBrowsingUtils.isWindowPrivate(window);
|
||||||
reopenInContainer.disabled = this.contextTab.hidden;
|
reopenInContainer.disabled = this.contextTab.hidden;
|
||||||
|
|
||||||
gShareUtils.updateShareURLMenuItem(
|
SharingUtils.updateShareURLMenuItem(
|
||||||
this.contextTab.linkedBrowser,
|
this.contextTab.linkedBrowser,
|
||||||
document.getElementById("context_sendTabToDevice")
|
document.getElementById("context_sendTabToDevice")
|
||||||
);
|
);
|
||||||
|
|||||||
250
browser/modules/SharingUtils.sys.mjs
Normal file
250
browser/modules/SharingUtils.sys.mjs
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
let lazy = {};
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyServiceGetters(lazy, {
|
||||||
|
MacSharingService: [
|
||||||
|
"@mozilla.org/widget/macsharingservice;1",
|
||||||
|
"nsIMacSharingService",
|
||||||
|
],
|
||||||
|
WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
|
||||||
|
});
|
||||||
|
|
||||||
|
class SharingUtilsCls {
|
||||||
|
/**
|
||||||
|
* Updates a sharing item in a given menu, creating it if necessary.
|
||||||
|
*/
|
||||||
|
updateShareURLMenuItem(browser, insertAfterEl) {
|
||||||
|
if (!Services.prefs.getBoolPref("browser.menu.share_url.allow", true)) {
|
||||||
|
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.
|
||||||
|
shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns the "Share" menu item.
|
||||||
|
*/
|
||||||
|
#createShareURLMenuItem(insertAfterEl) {
|
||||||
|
let menu = insertAfterEl.parentNode;
|
||||||
|
let shareURL = null;
|
||||||
|
let document = insertAfterEl.ownerDocument;
|
||||||
|
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);
|
||||||
|
|
||||||
|
menu.insertBefore(shareURL, insertAfterEl.nextSibling);
|
||||||
|
return shareURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a menu item specifically for accessing Windows sharing services.
|
||||||
|
*/
|
||||||
|
#buildShareURLItem(document) {
|
||||||
|
let shareURLMenuItem = document.createXULElement("menuitem");
|
||||||
|
shareURLMenuItem.addEventListener("command", this);
|
||||||
|
return shareURLMenuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a menu specifically for accessing macOSx sharing services .
|
||||||
|
*/
|
||||||
|
#buildShareURLMenu(document) {
|
||||||
|
let menu = document.createXULElement("menu");
|
||||||
|
let menuPopup = document.createXULElement("menupopup");
|
||||||
|
menuPopup.addEventListener("popupshowing", this);
|
||||||
|
menu.appendChild(menuPopup);
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sharing data for a given DOM node.
|
||||||
|
*/
|
||||||
|
getDataToShare(node) {
|
||||||
|
let browser = node.browserToShare?.get();
|
||||||
|
let urlToShare = null;
|
||||||
|
let titleToShare = null;
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
let maybeToShare = BrowserUtils.getShareableURL(browser.currentURI);
|
||||||
|
if (maybeToShare) {
|
||||||
|
urlToShare = maybeToShare;
|
||||||
|
titleToShare = browser.contentTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { urlToShare, titleToShare };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the "Share" menupopup on macOSx.
|
||||||
|
*/
|
||||||
|
initializeShareURLPopup(menuPopup) {
|
||||||
|
if (AppConstants.platform != "macosx") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty menupopup
|
||||||
|
while (menuPopup.firstChild) {
|
||||||
|
menuPopup.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
let document = menuPopup.ownerDocument;
|
||||||
|
let { gURLBar } = menuPopup.ownerGlobal;
|
||||||
|
|
||||||
|
let { urlToShare } = this.getDataToShare(menuPopup.parentNode);
|
||||||
|
|
||||||
|
// If we can't share the current URL, we display the items disabled,
|
||||||
|
// but enable the "more..." item at the bottom, to allow the user to
|
||||||
|
// change sharing preferences in the system dialog.
|
||||||
|
let shouldEnable = !!urlToShare;
|
||||||
|
if (!urlToShare) {
|
||||||
|
// Fake it so we can ask the sharing service for services:
|
||||||
|
urlToShare = Services.io.newURI("https://mozilla.org/");
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
||||||
|
let services = lazy.MacSharingService.getSharingProviders(currentURI);
|
||||||
|
|
||||||
|
services.forEach(share => {
|
||||||
|
let item = document.createXULElement("menuitem");
|
||||||
|
item.classList.add("menuitem-iconic");
|
||||||
|
item.setAttribute("label", share.menuItemTitle);
|
||||||
|
item.setAttribute("share-name", share.name);
|
||||||
|
item.setAttribute("image", share.image);
|
||||||
|
if (!shouldEnable) {
|
||||||
|
item.setAttribute("disabled", "true");
|
||||||
|
}
|
||||||
|
menuPopup.appendChild(item);
|
||||||
|
});
|
||||||
|
menuPopup.appendChild(document.createXULElement("menuseparator"));
|
||||||
|
let moreItem = document.createXULElement("menuitem");
|
||||||
|
document.l10n.setAttributes(moreItem, "menu-share-more");
|
||||||
|
moreItem.classList.add("menuitem-iconic", "share-more-button");
|
||||||
|
menuPopup.appendChild(moreItem);
|
||||||
|
|
||||||
|
menuPopup.addEventListener("command", this);
|
||||||
|
menuPopup.parentNode
|
||||||
|
.closest("menupopup")
|
||||||
|
.addEventListener("popuphiding", this);
|
||||||
|
menuPopup.setAttribute("data-initialized", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onShareURLCommand(event) {
|
||||||
|
// Only call sharing services for the "Share" menu item. These services
|
||||||
|
// are accessed from a submenu popup for MacOS or the "Share" menu item
|
||||||
|
// for Windows. Use .closest() as a hack to find either the item itself
|
||||||
|
// or a parent with the right class.
|
||||||
|
let target = event.target.closest(".share-tab-url-item");
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let { gURLBar } = target.ownerGlobal;
|
||||||
|
|
||||||
|
// urlToShare/titleToShare may be null, in which case only the "more"
|
||||||
|
// item is enabled, so handle that case first:
|
||||||
|
if (event.target.classList.contains("share-more-button")) {
|
||||||
|
lazy.MacSharingService.openSharingPreferences();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { urlToShare, titleToShare } = this.getDataToShare(target);
|
||||||
|
let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
|
||||||
|
|
||||||
|
if (AppConstants.platform == "win") {
|
||||||
|
lazy.WindowsUIUtils.shareUrl(currentURI, titleToShare);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// hidden. So bail if this isn't the top menupopup in the DOM tree:
|
||||||
|
if (event.target.parentNode.closest("menupopup")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise, clear its "data-initialized" attribute.
|
||||||
|
let menupopup = event.target.querySelector(
|
||||||
|
".share-tab-url-item"
|
||||||
|
)?.menupopup;
|
||||||
|
menupopup?.removeAttribute("data-initialized");
|
||||||
|
|
||||||
|
event.target.removeEventListener("popuphiding", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPopupShowing(event) {
|
||||||
|
if (!event.target.hasAttribute("data-initialized")) {
|
||||||
|
this.initializeShareURLPopup(event.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(aEvent) {
|
||||||
|
switch (aEvent.type) {
|
||||||
|
case "command":
|
||||||
|
this.onShareURLCommand(aEvent);
|
||||||
|
break;
|
||||||
|
case "popuphiding":
|
||||||
|
this.onPopupHiding(aEvent);
|
||||||
|
break;
|
||||||
|
case "popupshowing":
|
||||||
|
this.onPopupShowing(aEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testOnlyMockUIUtils(mock) {
|
||||||
|
if (!Cu.isInAutomation) {
|
||||||
|
throw new Error("Can only mock utils in automation.");
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line mozilla/valid-lazy
|
||||||
|
Object.defineProperty(lazy, "WindowsUIUtils", {
|
||||||
|
get() {
|
||||||
|
if (mock) {
|
||||||
|
return mock;
|
||||||
|
}
|
||||||
|
return Cc["@mozilla.org/windows-ui-utils;1"].getService(
|
||||||
|
Ci.nsIWindowsUIUtils
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let SharingUtils = new SharingUtilsCls();
|
||||||
@@ -137,6 +137,7 @@ EXTRA_JS_MODULES += [
|
|||||||
"ProcessHangMonitor.sys.mjs",
|
"ProcessHangMonitor.sys.mjs",
|
||||||
"Sanitizer.sys.mjs",
|
"Sanitizer.sys.mjs",
|
||||||
"SelectionChangedMenulist.sys.mjs",
|
"SelectionChangedMenulist.sys.mjs",
|
||||||
|
"SharingUtils.sys.mjs",
|
||||||
"SiteDataManager.sys.mjs",
|
"SiteDataManager.sys.mjs",
|
||||||
"SitePermissions.sys.mjs",
|
"SitePermissions.sys.mjs",
|
||||||
"TransientPrefs.sys.mjs",
|
"TransientPrefs.sys.mjs",
|
||||||
|
|||||||
Reference in New Issue
Block a user