Bug 1363169 - Add support for native windows share. r=gijs, r=aklotz
authorDale Harvey <dale@arandomurl.com>
Wed, 26 Sep 2018 21:09:00 +0100
changeset 489766 5f197a31d28fb2524167a8cb92c45b2d7a64ea8a
parent 489765 81cff62bc428d4fc54c81d0445b69ad554b31cda
child 489767 7c5bf03859d2fb592ff5cca51552f2d1c0000893
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersgijs, aklotz
bugs1363169
milestone64.0a1
Bug 1363169 - Add support for native windows share. r=gijs, r=aklotz MozReview-Commit-ID: 7quON7Somvr
browser/base/content/browser-pageActions.js
browser/base/content/test/urlbar/browser.ini
browser/base/content/test/urlbar/browser_page_action_menu_share_win.html
browser/base/content/test/urlbar/browser_page_action_menu_share_win.js
browser/modules/PageActions.jsm
browser/themes/windows/browser.css
browser/themes/windows/jar.mn
browser/themes/windows/share.svg
widget/nsIWindowsUIUtils.idl
widget/windows/WindowsUIUtils.cpp
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1198,16 +1198,22 @@ BrowserPageActions.addSearchEngine = {
         prompt.alert(title, text);
       },
     });
   },
 };
 
 // share URL
 BrowserPageActions.shareURL = {
+  onCommand(event, buttonNode) {
+    let browser = gBrowser.selectedBrowser;
+    let currentURI = gURLBar.makeURIReadable(browser.currentURI).displaySpec;
+    this._windowsUIUtils.shareUrl(currentURI, browser.contentTitle);
+  },
+
   onShowingInPanel(buttonNode) {
     this._cached = false;
   },
 
   onBeforePlacedInWindow(browserWindow) {
     let action = PageActions.actionForID("shareURL");
     BrowserPageActions.takeActionTitleFromPanel(action);
   },
@@ -1259,12 +1265,12 @@ BrowserPageActions.shareURL = {
       bodyNode.firstChild.remove();
     }
     bodyNode.appendChild(fragment);
     this._cached = true;
   },
 };
 
 // Attach sharingService here so tests can override the implementation
-XPCOMUtils.defineLazyServiceGetter(BrowserPageActions.shareURL,
-                                   "_sharingService",
-                                   "@mozilla.org/widget/macsharingservice;1",
-                                   "nsIMacSharingService");
+XPCOMUtils.defineLazyServiceGetters(BrowserPageActions.shareURL, {
+  _sharingService: ["@mozilla.org/widget/macsharingservice;1", "nsIMacSharingService"],
+  _windowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
+});
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -52,16 +52,20 @@ support-files =
   page_action_menu_add_search_engine_same_names.html
   page_action_menu_add_search_engine_0.xml
   page_action_menu_add_search_engine_1.xml
   page_action_menu_add_search_engine_2.xml
 [browser_page_action_menu_clipboard.js]
 subsuite = clipboard
 [browser_page_action_menu_share_mac.js]
 skip-if = os != "mac" # Mac only feature
+[browser_page_action_menu_share_win.js]
+support-files =
+  browser_page_action_menu_share_win.html
+skip-if = os != "win" # Windows only feature
 [browser_pasteAndGo.js]
 subsuite = clipboard
 [browser_populateAfterPushState.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [browser_search_favicon.js]
 [browser_tabMatchesInAwesomebar.js]
 support-files =
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_page_action_menu_share_win.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<title>Windows Sharing</title>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_page_action_menu_share_win.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* global sinon */
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
+
+const TEST_URL = getRootDirectory(gTestPath) + "browser_page_action_menu_share_win.html";
+
+// Keep track of site details we are sharing
+let sharedUrl, sharedTitle;
+
+let stub = sinon.stub(BrowserPageActions.shareURL, "_windowsUIUtils").get(() => {
+  return {
+    shareUrl(url, title) {
+      sharedUrl = url;
+      sharedTitle = title;
+    },
+  };
+});
+
+registerCleanupFunction(async function() {
+  stub.restore();
+  delete window.sinon;
+});
+
+add_task(async function shareURL() {
+
+  if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.4")) {
+    Assert.ok(true, "We only expose share on windows 10 and above");
+    return;
+  }
+
+  await BrowserTestUtils.withNewTab(TEST_URL, async () => {
+    // Open the panel.
+    await promisePageActionPanelOpen();
+
+    // Click Share URL.
+    let shareURLButton = document.getElementById("pageAction-panel-shareURL");
+    let hiddenPromise = promisePageActionPanelHidden();
+    EventUtils.synthesizeMouseAtCenter(shareURLButton, {});
+
+    await hiddenPromise;
+
+    Assert.equal(sharedUrl, TEST_URL, "Shared correct URL");
+    Assert.equal(sharedTitle, "Windows Sharing", "Shared with the correct title");
+  });
+});
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -1180,16 +1180,31 @@ if (AppConstants.platform == "macosx") {
     wantsSubview: true,
     onSubviewShowing(panelViewNode) {
         browserPageActions(panelViewNode).shareURL
           .onShowingSubview(panelViewNode);
     },
   });
 }
 
+if (AppConstants.isPlatformAndVersionAtLeast("win", "6.4")) {
+  gBuiltInActions.push(
+  // Share URL
+  {
+    id: "shareURL",
+    title: "shareURL-title",
+    onBeforePlacedInWindow(buttonNode) {
+      browserPageActions(buttonNode).shareURL.onBeforePlacedInWindow(buttonNode);
+    },
+    onCommand(event, buttonNode) {
+      browserPageActions(buttonNode).shareURL.onCommand(event, buttonNode);
+    },
+  });
+}
+
 /**
  * Gets a BrowserPageActions object in a browser window.
  *
  * @param  obj
  *         Either a DOM node or a browser window.
  * @return The BrowserPageActions object in the browser window related to the
  *         given object.
  */
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -592,16 +592,20 @@ html|*.urlbar-input:-moz-lwtheme::placeh
 
 .urlbar-display {
   margin-top: 0;
   margin-bottom: 0;
   margin-inline-start: 0;
   color: GrayText;
 }
 
+#pageAction-panel-shareURL {
+  list-style-image: url("chrome://browser/skin/share.svg");
+}
+
 %include ../shared/urlbarSearchSuggestionsNotification.inc.css
 
 #search-container {
   min-width: calc(54px + 11ch);
 }
 
 /* identity box */
 
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -36,16 +36,17 @@ browser.jar:
   skin/classic/browser/places/livemark-item.png                (places/livemark-item.png)
   skin/classic/browser/preferences/alwaysAsk.png               (preferences/alwaysAsk.png)
   skin/classic/browser/preferences/application.png             (preferences/application.png)
   skin/classic/browser/preferences/saveFile.png                (preferences/saveFile.png)
   skin/classic/browser/preferences/preferences.css             (preferences/preferences.css)
 * skin/classic/browser/preferences/in-content/preferences.css  (preferences/in-content/preferences.css)
 * skin/classic/browser/preferences/in-content/dialog.css       (preferences/in-content/dialog.css)
   skin/classic/browser/preferences/applications.css            (preferences/applications.css)
+  skin/classic/browser/share.svg                               (share.svg)
   skin/classic/browser/tabbrowser/tabDragIndicator.png         (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/window-controls/close.svg                 (window-controls/close.svg)
   skin/classic/browser/window-controls/close-highcontrast.svg    (window-controls/close-highcontrast.svg)
   skin/classic/browser/window-controls/close-themes.svg          (window-controls/close-themes.svg)
   skin/classic/browser/window-controls/maximize.svg              (window-controls/maximize.svg)
   skin/classic/browser/window-controls/maximize-highcontrast.svg (window-controls/maximize-highcontrast.svg)
   skin/classic/browser/window-controls/maximize-themes.svg       (window-controls/maximize-themes.svg)
   skin/classic/browser/window-controls/minimize.svg              (window-controls/minimize.svg)
new file mode 100644
--- /dev/null
+++ b/browser/themes/windows/share.svg
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
+  <path fill="context-fill" d="M 15.707 4.293 l -4 -4 a 1 1 0 0 0 -1.414 1.414 L 12.585 4 H 11 a 7.008 7.008 0 0 0 -7 7 a 1 1 0 0 0 2 0 a 5.006 5.006 0 0 1 5 -5 h 1.585 l -2.293 2.293 a 1 1 0 1 0 1.414 1.414 l 4 -4 a 1 1 0 0 0 0.001 -1.414 Z" />
+  <path fill="context-fill" d="M 13 11 a 1 1 0 0 0 -1 1 v 1 a 1 1 0 0 1 -1 1 H 3 a 1 1 0 0 1 -1 -1 V 6 a 1 1 0 0 1 1 -1 h 1 a 1 1 0 0 0 0 -2 H 3 a 3 3 0 0 0 -3 3 v 7 a 3 3 0 0 0 3 3 h 8 a 3 3 0 0 0 3 -3 v -1 a 1 1 0 0 0 -1 -1 Z" />
+</svg>
--- a/widget/nsIWindowsUIUtils.idl
+++ b/widget/nsIWindowsUIUtils.idl
@@ -15,10 +15,14 @@ interface nsIWindowsUIUtils : nsISupport
    * non-Windows and on versions of Windows before win10
    */
   readonly attribute boolean inTabletMode;
 
   /**
    * Update the tablet mode state
    */
   void updateTabletModeState();
+
+  /**
+   * Share URL
+   */
+  void shareUrl(in AString shareTitle, in AString urlToShare);
 };
-
--- a/widget/windows/WindowsUIUtils.cpp
+++ b/widget/windows/WindowsUIUtils.cpp
@@ -30,16 +30,17 @@
 #pragma comment(lib, "runtimeobject.lib")
 
 using namespace mozilla;
 using namespace ABI::Windows::UI;
 using namespace ABI::Windows::UI::ViewManagement;
 using namespace Microsoft::WRL;
 using namespace Microsoft::WRL::Wrappers;
 using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::ApplicationModel::DataTransfer;
 
 /* All of this is win10 stuff and we're compiling against win81 headers
  * for now, so we may need to do some legwork: */
 #if WINVER_MAXVER < 0x0A00
 namespace ABI {
   namespace Windows {
     namespace UI {
       namespace ViewManagement {
@@ -85,16 +86,31 @@ typedef interface IUIViewSettingsInterop
 MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6")
 IUIViewSettingsInterop : public IInspectable
 {
 public:
   virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND hwnd, REFIID riid, void **ppv) = 0;
 };
 #endif
 
+#ifndef __IDataTransferManagerInterop_INTERFACE_DEFINED__
+#define __IDataTransferManagerInterop_INTERFACE_DEFINED__
+
+typedef interface IDataTransferManagerInterop IDataTransferManagerInterop;
+
+MIDL_INTERFACE("3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8")
+IDataTransferManagerInterop : public IUnknown
+{
+public:
+    virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND appWindow, REFIID riid, void **dataTransferManager) = 0;
+    virtual HRESULT STDMETHODCALLTYPE ShowShareUIForWindow(HWND appWindow) = 0;
+};
+
+#endif
+
 #endif
 
 WindowsUIUtils::WindowsUIUtils() :
   mInTabletMode(eTabletModeUnknown)
 {
 }
 
 WindowsUIUtils::~WindowsUIUtils()
@@ -173,8 +189,129 @@ WindowsUIUtils::UpdateTabletModeState()
         }
       }
     }
   }
 #endif
 
   return NS_OK;
 }
+
+struct HStringDeleter
+{
+  typedef HSTRING pointer;
+  void operator()(pointer aString)
+  {
+      WindowsDeleteString(aString);
+  }
+};
+
+typedef mozilla::UniquePtr<HSTRING, HStringDeleter> HStringUniquePtr;
+
+NS_IMETHODIMP
+WindowsUIUtils::ShareUrl(const nsAString& aUrlToShare,
+                         const nsAString& aShareTitle)
+{
+#ifndef __MINGW32__
+  if (!IsWin10OrLater()) {
+    return NS_OK;
+  }
+
+  HSTRING rawTitle;
+  HRESULT hr = WindowsCreateString(PromiseFlatString(aShareTitle).get(), aShareTitle.Length(), &rawTitle);
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+  HStringUniquePtr title(rawTitle);
+
+  HSTRING rawUrl;
+  hr = WindowsCreateString(PromiseFlatString(aUrlToShare).get(), aUrlToShare.Length(), &rawUrl);
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+  HStringUniquePtr url(rawUrl);
+
+  ComPtr<IUriRuntimeClassFactory> uriFactory;
+  hr = GetActivationFactory(HStringReference(RuntimeClass_Windows_Foundation_Uri).Get(), &uriFactory);
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+
+  ComPtr<IUriRuntimeClass> uri;
+  hr = uriFactory->CreateUri(url.get(), &uri);
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+
+  HWND hwnd = GetForegroundWindow();
+  if (!hwnd) {
+    return NS_OK;
+  }
+
+  ComPtr<IDataTransferManagerInterop> dtmInterop;
+  hr = RoGetActivationFactory(HStringReference(
+    RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager)
+                         .Get(), IID_PPV_ARGS(&dtmInterop));
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+
+  ComPtr<IDataTransferManager> dtm;
+  hr = dtmInterop->GetForWindow(hwnd, IID_PPV_ARGS(&dtm));
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+
+  auto callback = Callback < ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs* >> (
+          [uri = std::move(uri), title = std::move(title)](IDataTransferManager*, IDataRequestedEventArgs* pArgs) -> HRESULT
+    {
+      ComPtr<IDataRequest> spDataRequest;
+      HRESULT hr = pArgs->get_Request(&spDataRequest);
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      ComPtr<IDataPackage> spDataPackage;
+      hr = spDataRequest->get_Data(&spDataPackage);
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      ComPtr<IDataPackage2> spDataPackage2;
+      hr = spDataPackage->QueryInterface(IID_PPV_ARGS(&spDataPackage2));
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      ComPtr<IDataPackagePropertySet> spDataPackageProperties;
+      hr = spDataPackage->get_Properties(&spDataPackageProperties);
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      hr = spDataPackageProperties->put_Title(title.get());
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      hr = spDataPackage2->SetWebLink(uri.Get());
+      if (FAILED(hr)) {
+        return hr;
+      }
+
+      return S_OK;
+    });
+
+  EventRegistrationToken dataRequestedToken;
+  hr = dtm->add_DataRequested(callback.Get(), &dataRequestedToken);
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+
+  hr = dtmInterop->ShowShareUIForWindow(hwnd);
+  if (FAILED(hr)) {
+    return NS_OK;
+  }
+
+#endif
+
+  return NS_OK;
+}