Bug 1471877 - Add link to share menu to configuration. r=gijs, r=mstange draft
authorDale Harvey <dale@arandomurl.com>
Sat, 30 Jun 2018 18:35:46 +0100
changeset 819677 043ec44701ea8ddc4edf85ba41ad0703b2dc5f90
parent 819676 2e8624a457a61ad282e163741c7b1fad91df4434
push id116610
push userbmo:dharvey@mozilla.com
push dateWed, 18 Jul 2018 10:38:31 +0000
reviewersgijs, mstange
bugs1471877
milestone63.0a1
Bug 1471877 - Add link to share menu to configuration. r=gijs, r=mstange MozReview-Commit-ID: CCTO9SJJMY6
browser/base/content/browser-pageActions.js
browser/base/content/browser.xul
browser/base/content/test/urlbar/browser_page_action_menu_share_mac.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/themes/shared/urlbar-searchbar.inc.css
widget/cocoa/nsMacSharingService.mm
widget/nsIMacSharingService.idl
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1225,36 +1225,44 @@ BrowserPageActions.shareURL = {
     }
 
     let sharingService = this._sharingService;
     let url = gBrowser.selectedBrowser.currentURI;
     let currentURI = gURLBar.makeURIReadable(url).displaySpec;
     let shareProviders = sharingService.getSharingProviders(currentURI);
     let fragment = document.createDocumentFragment();
 
+    let onCommand = event => {
+      let shareName = event.target.getAttribute("share-name");
+      if (shareName) {
+        sharingService.shareUrl(shareName,
+                                currentURI,
+                                gBrowser.selectedBrowser.contentTitle);
+      } else if (event.target.classList.contains("share-more-button")) {
+        sharingService.openSharingPreferences();
+      }
+      PanelMultiView.hidePopup(BrowserPageActions.panelNode);
+    };
+
     shareProviders.forEach(function(share) {
       let item = document.createElement("toolbarbutton");
       item.setAttribute("label", share.menuItemTitle);
       item.setAttribute("share-name", share.name);
       item.setAttribute("image", share.image);
       item.classList.add("subviewbutton", "subviewbutton-iconic");
-
-      item.addEventListener("command", event => {
-        let shareName = event.target.getAttribute("share-name");
-        if (shareName) {
-          sharingService.shareUrl(shareName,
-                                  currentURI,
-                                  gBrowser.selectedBrowser.contentTitle);
-        }
-        PanelMultiView.hidePopup(BrowserPageActions.panelNode);
-      });
-
+      item.addEventListener("command", onCommand);
       fragment.appendChild(item);
     });
 
+    let item = document.createElement("toolbarbutton");
+    item.setAttribute("label", BrowserPageActions.panelNode.getAttribute("shareMore-label"));
+    item.classList.add("subviewbutton", "subviewbutton-iconic", "share-more-button");
+    item.addEventListener("command", onCommand);
+    fragment.appendChild(item);
+
     while (bodyNode.firstChild) {
       bodyNode.firstChild.remove();
     }
     bodyNode.appendChild(fragment);
     this._cached = true;
   }
 };
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -469,17 +469,18 @@
            photon="true"
            position="bottomcenter topright"
            tabspecific="true"
            noautofocus="true"
            copyURL-title="&pageAction.copyLink.label;"
            emailLink-title="&emailPageCmd.label;"
            sendToDevice-title="&pageAction.sendTabToDevice.label;"
            sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;"
-           shareURL-title="&pageAction.shareUrl.label;">
+           shareURL-title="&pageAction.shareUrl.label;"
+           shareMore-label="&pageAction.shareMore.label;">
       <panelmultiview id="pageActionPanelMultiView"
                       mainViewId="pageActionPanelMainView"
                       viewCacheId="appMenu-viewCache">
         <panelview id="pageActionPanelMainView"
                    context="pageActionContextMenu"
                    class="PanelUI-subView">
           <vbox class="panel-subview-body"/>
         </panelview>
--- a/browser/base/content/test/urlbar/browser_page_action_menu_share_mac.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu_share_mac.js
@@ -5,19 +5,18 @@
 "use strict";
 
 /* global sinon */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
 
 const URL = "http://example.org/";
 
 // Keep track of title of service we chose to share with
-let serviceName;
-let sharedUrl;
-let sharedTitle;
+let serviceName, sharedUrl, sharedTitle;
+let sharingPreferencesCalled = false;
 
 let mockShareData = [{
   name: "NSA",
   menuItemTitle: "National Security Agency",
   image: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEA" +
     "LAAAAAABAAEAAAICTAEAOw=="
 }];
 
@@ -25,16 +24,19 @@ let stub = sinon.stub(BrowserPageActions
   return {
     getSharingProviders(url) {
       return mockShareData;
     },
     shareUrl(name, url, title) {
       serviceName = name;
       sharedUrl = url;
       sharedTitle = title;
+    },
+    openSharingPreferences() {
+      sharingPreferencesCalled = true;
     }
   };
 });
 
 registerCleanupFunction(async function() {
   stub.restore();
   delete window.sinon;
   await EventUtils.synthesizeNativeMouseMove(document.documentElement, 0, 0);
@@ -49,17 +51,18 @@ add_task(async function shareURL() {
     // Click Share URL.
     let shareURLButton = document.getElementById("pageAction-panel-shareURL");
     let viewPromise = promisePageActionViewShown();
     EventUtils.synthesizeMouseAtCenter(shareURLButton, {});
 
     let view = await viewPromise;
     let body = document.getElementById(view.id + "-body");
 
-    Assert.equal(body.childNodes.length, 1, "Has correct share receivers");
+    // We should see 1 receiver and one extra node for the "More..." button
+    Assert.equal(body.childNodes.length, 2, "Has correct share receivers");
     let shareButton = body.childNodes[0];
     Assert.equal(shareButton.label, mockShareData[0].menuItemTitle);
     let hiddenPromise = promisePageActionPanelHidden();
     // Click on share, panel should hide and sharingService should be
     // given the title of service to share with
     EventUtils.synthesizeMouseAtCenter(shareButton, {});
     await hiddenPromise;
 
@@ -102,17 +105,18 @@ add_task(async function shareURLAddressB
     // Press the Share button
     let shareButton = document.getElementById("pageAction-urlbar-shareURL");
     let viewPromise = promisePageActionPanelShown();
     EventUtils.synthesizeMouseAtCenter(shareButton, {});
     await viewPromise;
 
     // Ensure we have share providers
     let panel = document.getElementById("pageAction-urlbar-shareURL-subview-body");
-    Assert.equal(panel.childNodes.length, 1, "Has correct share receivers");
+    // We should see 1 receiver and one extra node for the "More..." button
+    Assert.equal(panel.childNodes.length, 2, "Has correct share receivers");
 
     // Remove the Share URL button from the Address bar so we dont interfere
     // with future tests
     contextMenuPromise = promisePanelShown("pageActionContextMenu");
     EventUtils.synthesizeMouseAtCenter(shareButton, {
       type: "contextmenu",
       button: 2,
     });
@@ -120,8 +124,35 @@ add_task(async function shareURLAddressB
 
     contextMenuPromise = promisePanelHidden("pageActionContextMenu");
     ctxMenuButton = document.querySelector("#pageActionContextMenu " +
                                            ".pageActionContextMenuItem");
     EventUtils.synthesizeMouseAtCenter(ctxMenuButton, {});
     await contextMenuPromise;
   });
 });
+
+add_task(async function openSharingPreferences() {
+  await BrowserTestUtils.withNewTab(URL, async () => {
+    // Open the panel.
+    await promisePageActionPanelOpen();
+
+    // Click Share URL.
+    let shareURLButton = document.getElementById("pageAction-panel-shareURL");
+    let viewPromise = promisePageActionViewShown();
+    EventUtils.synthesizeMouseAtCenter(shareURLButton, {});
+
+    let view = await viewPromise;
+    let body = document.getElementById(view.id + "-body");
+
+    // We should see 1 receiver and one extra node for the "More..." button
+    Assert.equal(body.childNodes.length, 2, "Has correct share receivers");
+    let moreButton = body.childNodes[1];
+    let hiddenPromise = promisePageActionPanelHidden();
+    // Click on the "more" button,  panel should hide and we should call
+    // the sharingService function to open preferences
+    EventUtils.synthesizeMouseAtCenter(moreButton, {});
+    await hiddenPromise;
+
+    Assert.equal(sharingPreferencesCalled, true,
+                 "We called openSharingPreferences");
+  });
+});
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -1026,16 +1026,17 @@ you can use these alternative items. Oth
 <!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
 <!ENTITY pageAction.disallowInUrlbar.label "Don’t Show in Address Bar">
 <!ENTITY pageAction.manageExtension.label "Manage Extension…">
 
 <!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
 <!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
 
 <!ENTITY pageAction.shareUrl.label "Share">
+<!ENTITY pageAction.shareMore.label "More…">
 
 <!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
 
 <!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to
      display a tooltip for accessibility indicator in toolbar/tabbar. It is also
      used as a textual label for the indicator used by assistive technology
      users. -->
 <!ENTITY accessibilityIndicator.tooltip "Accessibility Features Enabled">
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -293,17 +293,17 @@
 #urlbar[switchingtabs] > .urlbar-textbox-container > .urlbar-history-dropmarker {
   transition: none;
 }
 
 #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container:not(:hover) > #urlbar:not([focused]) > .urlbar-textbox-container > .urlbar-history-dropmarker {
   opacity: 0;
 }
 
-#pageActionButton {
+#pageActionButton, .share-more-button {
   list-style-image: url("chrome://browser/skin/page-action.svg");
 }
 
 @keyframes bookmark-animation {
   from {
     transform: translateX(0);
   }
   to {
--- a/widget/cocoa/nsMacSharingService.mm
+++ b/widget/cocoa/nsMacSharingService.mm
@@ -17,16 +17,26 @@ NS_IMPL_ISUPPORTS(nsMacSharingService, n
 NSArray *filteredProviderNames = @[
   @"com.apple.share.System.add-to-safari-reading-list",
   @"com.apple.share.Mail.compose"
 ];
 
 NSString* const remindersServiceName =
   @"com.apple.reminders.RemindersShareExtension";
 
+// These are some undocumented constants also used by Safari
+// to let us open the preferences window
+NSString* const extensionPrefPanePath =
+  @"/System/Library/PreferencePanes/Extensions.prefPane";
+const UInt32 openSharingSubpaneDescriptorType = 'ptru';
+NSString* const openSharingSubpaneActionKey = @"action";
+NSString* const openSharingSubpaneActionValue = @"revealExtensionPoint";
+NSString* const openSharingSubpaneProtocolKey = @"protocol";
+NSString* const openSharingSubpaneProtocolValue = @"com.apple.share-services";
+
 // Expose the id so we can pass reference through to JS and back
 @interface NSSharingService (ExposeName)
 - (id)name;
 @end
 
 // Clean up the activity once the share is complete
 @interface SharingServiceDelegate : NSObject <NSSharingServiceDelegate>
 {
@@ -124,16 +134,50 @@ nsMacSharingService::GetSharingProviders
 
   aResult.setObject(*array);
 
   return NS_OK;
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP
+nsMacSharingService::OpenSharingPreferences()
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  NSURL* prefPaneURL =
+    [NSURL fileURLWithPath:extensionPrefPanePath isDirectory:YES];
+  NSDictionary* args = @{
+    openSharingSubpaneActionKey: openSharingSubpaneActionValue,
+    openSharingSubpaneProtocolKey: openSharingSubpaneProtocolValue
+  };
+  NSData* data =
+    [NSPropertyListSerialization
+     dataWithPropertyList:args
+     format:NSPropertyListXMLFormat_v1_0
+     options:0
+     error:nil];
+  NSAppleEventDescriptor* descriptor =
+    [[NSAppleEventDescriptor alloc]
+     initWithDescriptorType:openSharingSubpaneDescriptorType
+     data:data];
+
+  [[NSWorkspace sharedWorkspace] openURLs:@[ prefPaneURL ]
+   withAppBundleIdentifier:nil
+   options:NSWorkspaceLaunchAsync
+   additionalEventParamDescriptor:descriptor
+   launchIdentifiers:NULL];
+
+  [descriptor release];
+
+  return NS_OK;
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
 nsMacSharingService::ShareUrl(const nsAString& aServiceName,
                               const nsAString& aPageUrl,
                               const nsAString& aPageTitle)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   NSString* serviceName = nsCocoaUtils::ToNSString(aServiceName);
   NSURL* pageUrl = [NSURL URLWithString:nsCocoaUtils::ToNSString(aPageUrl)];
--- a/widget/nsIMacSharingService.idl
+++ b/widget/nsIMacSharingService.idl
@@ -17,9 +17,14 @@ interface nsIMacSharingService : nsISupp
   [implicit_jscontext] jsval getSharingProviders(in AString pageUrl);
 
   /**
    * Launch service with shareTitle with given url
    */
   void shareUrl(in AString serviceName,
                 in AString pageUrl,
                 in AString pageTitle);
+
+  /**
+   * Open the MacOS preferences window to the sharing panel
+   */
+  void openSharingPreferences();
 };