1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include <windows.h>
7 #include <winsdkver.h>
8 #include <wrl.h>
9 
10 #include "nsServiceManagerUtils.h"
11 
12 #include "WindowsUIUtils.h"
13 
14 #include "nsIObserverService.h"
15 #include "nsIAppShellService.h"
16 #include "nsAppShellCID.h"
17 #include "mozilla/ResultVariant.h"
18 #include "mozilla/Services.h"
19 #include "mozilla/WidgetUtils.h"
20 #include "mozilla/WindowsVersion.h"
21 #include "mozilla/media/MediaUtils.h"
22 #include "nsString.h"
23 #include "nsIWidget.h"
24 #include "nsIWindowMediator.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsWindowGfx.h"
27 #include "Units.h"
28 
29 /* mingw currently doesn't support windows.ui.viewmanagement.h, so we disable it
30  * until it's fixed. */
31 #ifndef __MINGW32__
32 
33 #  include <windows.ui.viewmanagement.h>
34 
35 #  pragma comment(lib, "runtimeobject.lib")
36 
37 using namespace ABI::Windows::UI;
38 using namespace ABI::Windows::UI::ViewManagement;
39 using namespace Microsoft::WRL;
40 using namespace Microsoft::WRL::Wrappers;
41 using namespace ABI::Windows::Foundation;
42 using namespace ABI::Windows::ApplicationModel::DataTransfer;
43 
44 /* All of this is win10 stuff and we're compiling against win81 headers
45  * for now, so we may need to do some legwork: */
46 #  if WINVER_MAXVER < 0x0A00
47 namespace ABI {
48 namespace Windows {
49 namespace UI {
50 namespace ViewManagement {
51 enum UserInteractionMode {
52   UserInteractionMode_Mouse = 0,
53   UserInteractionMode_Touch = 1
54 };
55 }
56 }  // namespace UI
57 }  // namespace Windows
58 }  // namespace ABI
59 
60 #  endif
61 
62 #  ifndef RuntimeClass_Windows_UI_ViewManagement_UIViewSettings
63 #    define RuntimeClass_Windows_UI_ViewManagement_UIViewSettings \
64       L"Windows.UI.ViewManagement.UIViewSettings"
65 #  endif
66 
67 #  if WINVER_MAXVER < 0x0A00
68 namespace ABI {
69 namespace Windows {
70 namespace UI {
71 namespace ViewManagement {
72 interface IUIViewSettings;
73 MIDL_INTERFACE("C63657F6-8850-470D-88F8-455E16EA2C26")
74 IUIViewSettings : public IInspectable {
75  public:
76   virtual HRESULT STDMETHODCALLTYPE get_UserInteractionMode(
77       UserInteractionMode * value) = 0;
78 };
79 
80 extern const __declspec(selectany) IID& IID_IUIViewSettings =
81     __uuidof(IUIViewSettings);
82 }  // namespace ViewManagement
83 }  // namespace UI
84 }  // namespace Windows
85 }  // namespace ABI
86 #  endif
87 
88 #  ifndef IUIViewSettingsInterop
89 
90 typedef interface IUIViewSettingsInterop IUIViewSettingsInterop;
91 
92 MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6")
93 IUIViewSettingsInterop : public IInspectable {
94  public:
95   virtual HRESULT STDMETHODCALLTYPE GetForWindow(HWND hwnd, REFIID riid,
96                                                  void** ppv) = 0;
97 };
98 #  endif
99 
100 #  ifndef __IDataTransferManagerInterop_INTERFACE_DEFINED__
101 #    define __IDataTransferManagerInterop_INTERFACE_DEFINED__
102 
103 typedef interface IDataTransferManagerInterop IDataTransferManagerInterop;
104 
105 MIDL_INTERFACE("3A3DCD6C-3EAB-43DC-BCDE-45671CE800C8")
106 IDataTransferManagerInterop : public IUnknown {
107  public:
108   virtual HRESULT STDMETHODCALLTYPE GetForWindow(
109       HWND appWindow, REFIID riid, void** dataTransferManager) = 0;
110   virtual HRESULT STDMETHODCALLTYPE ShowShareUIForWindow(HWND appWindow) = 0;
111 };
112 
113 #  endif
114 
115 #  if !defined( \
116       ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__)
117 #    define ____x_ABI_CWindows_CApplicationModel_CDataTransfer_CIDataPackage4_INTERFACE_DEFINED__
118 
119 MIDL_INTERFACE("13a24ec8-9382-536f-852a-3045e1b29a3b")
120 IDataPackage4 : public IInspectable {
121  public:
122   virtual HRESULT STDMETHODCALLTYPE add_ShareCanceled(
123       __FITypedEventHandler_2_Windows__CApplicationModel__CDataTransfer__CDataPackage_IInspectable *
124           handler,
125       EventRegistrationToken * token) = 0;
126   virtual HRESULT STDMETHODCALLTYPE remove_ShareCanceled(
127       EventRegistrationToken token) = 0;
128 };
129 
130 #  endif
131 
132 #endif
133 
134 using namespace mozilla;
135 
136 WindowsUIUtils::WindowsUIUtils() : mInTabletMode(eTabletModeUnknown) {}
137 
138 WindowsUIUtils::~WindowsUIUtils() {}
139 
140 /*
141  * Implement the nsISupports methods...
142  */
143 NS_IMPL_ISUPPORTS(WindowsUIUtils, nsIWindowsUIUtils)
144 
145 NS_IMETHODIMP
146 WindowsUIUtils::GetSystemSmallIconSize(int32_t* aSize) {
147   NS_ENSURE_ARG(aSize);
148 
149   mozilla::LayoutDeviceIntSize size =
150       nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon);
151   *aSize = std::max(size.width, size.height);
152   return NS_OK;
153 }
154 
155 NS_IMETHODIMP
156 WindowsUIUtils::GetSystemLargeIconSize(int32_t* aSize) {
157   NS_ENSURE_ARG(aSize);
158 
159   mozilla::LayoutDeviceIntSize size =
160       nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon);
161   *aSize = std::max(size.width, size.height);
162   return NS_OK;
163 }
164 
165 NS_IMETHODIMP
166 WindowsUIUtils::SetWindowIcon(mozIDOMWindowProxy* aWindow,
167                               imgIContainer* aSmallIcon,
168                               imgIContainer* aBigIcon) {
169   NS_ENSURE_ARG(aWindow);
170 
171   nsCOMPtr<nsIWidget> widget =
172       nsGlobalWindowOuter::Cast(aWindow)->GetMainWidget();
173   nsWindow* window = static_cast<nsWindow*>(widget.get());
174 
175   nsresult rv;
176 
177   if (aSmallIcon) {
178     HICON hIcon = nullptr;
179     rv = nsWindowGfx::CreateIcon(
180         aSmallIcon, false, mozilla::LayoutDeviceIntPoint(),
181         nsWindowGfx::GetIconMetrics(nsWindowGfx::kSmallIcon), &hIcon);
182     NS_ENSURE_SUCCESS(rv, rv);
183 
184     window->SetSmallIcon(hIcon);
185   }
186 
187   if (aBigIcon) {
188     HICON hIcon = nullptr;
189     rv = nsWindowGfx::CreateIcon(
190         aBigIcon, false, mozilla::LayoutDeviceIntPoint(),
191         nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &hIcon);
192     NS_ENSURE_SUCCESS(rv, rv);
193 
194     window->SetBigIcon(hIcon);
195   }
196 
197   return NS_OK;
198 }
199 
200 NS_IMETHODIMP
201 WindowsUIUtils::GetInTabletMode(bool* aResult) {
202   if (mInTabletMode == eTabletModeUnknown) {
203     UpdateTabletModeState();
204   }
205   *aResult = mInTabletMode == eTabletModeOn;
206   return NS_OK;
207 }
208 
209 NS_IMETHODIMP
210 WindowsUIUtils::UpdateTabletModeState() {
211 #ifndef __MINGW32__
212   if (!IsWin10OrLater()) {
213     return NS_OK;
214   }
215 
216   nsresult rv;
217   nsCOMPtr<nsIWindowMediator> winMediator(
218       do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
219   if (NS_FAILED(rv)) {
220     return rv;
221   }
222 
223   nsCOMPtr<nsIWidget> widget;
224   nsCOMPtr<mozIDOMWindowProxy> navWin;
225 
226   rv = winMediator->GetMostRecentWindow(u"navigator:browser",
227                                         getter_AddRefs(navWin));
228   if (NS_FAILED(rv) || !navWin) {
229     // Fall back to the hidden window
230     nsCOMPtr<nsIAppShellService> appShell(
231         do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
232 
233     rv = appShell->GetHiddenDOMWindow(getter_AddRefs(navWin));
234     if (NS_FAILED(rv) || !navWin) {
235       return rv;
236     }
237   }
238 
239   nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
240   widget = widget::WidgetUtils::DOMWindowToWidget(win);
241 
242   if (!widget) return NS_ERROR_FAILURE;
243 
244   HWND winPtr = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
245   ComPtr<IUIViewSettingsInterop> uiViewSettingsInterop;
246 
247   HRESULT hr = GetActivationFactory(
248       HStringReference(RuntimeClass_Windows_UI_ViewManagement_UIViewSettings)
249           .Get(),
250       &uiViewSettingsInterop);
251   if (SUCCEEDED(hr)) {
252     ComPtr<IUIViewSettings> uiViewSettings;
253     hr = uiViewSettingsInterop->GetForWindow(winPtr,
254                                              IID_PPV_ARGS(&uiViewSettings));
255     if (SUCCEEDED(hr)) {
256       UserInteractionMode mode;
257       hr = uiViewSettings->get_UserInteractionMode(&mode);
258       if (SUCCEEDED(hr)) {
259         TabletModeState oldTabletModeState = mInTabletMode;
260         mInTabletMode = (mode == UserInteractionMode_Touch) ? eTabletModeOn
261                                                             : eTabletModeOff;
262         if (mInTabletMode != oldTabletModeState) {
263           nsCOMPtr<nsIObserverService> observerService =
264               mozilla::services::GetObserverService();
265           observerService->NotifyObservers(
266               nullptr, "tablet-mode-change",
267               ((mInTabletMode == eTabletModeOn) ? u"tablet-mode"
268                                                 : u"normal-mode"));
269         }
270       }
271     }
272   }
273 #endif
274 
275   return NS_OK;
276 }
277 
278 #ifndef __MINGW32__
279 struct HStringDeleter {
280   typedef HSTRING pointer;
281   void operator()(pointer aString) { WindowsDeleteString(aString); }
282 };
283 
284 typedef UniquePtr<HSTRING, HStringDeleter> HStringUniquePtr;
285 
286 Result<HStringUniquePtr, HRESULT> ConvertToWindowsString(
287     const nsAString& aStr) {
288   HSTRING rawStr;
289   HRESULT hr = WindowsCreateString(PromiseFlatString(aStr).get(), aStr.Length(),
290                                    &rawStr);
291   if (FAILED(hr)) {
292     return Err(hr);
293   }
294   return HStringUniquePtr(rawStr);
295 }
296 
297 Result<Ok, nsresult> RequestShare(
298     const std::function<HRESULT(IDataRequestedEventArgs* pArgs)>& aCallback) {
299   if (!IsWin10OrLater()) {
300     return Err(NS_ERROR_FAILURE);
301   }
302 
303   HWND hwnd = GetForegroundWindow();
304   if (!hwnd) {
305     return Err(NS_ERROR_FAILURE);
306   }
307 
308   ComPtr<IDataTransferManagerInterop> dtmInterop;
309   ComPtr<IDataTransferManager> dtm;
310 
311   HRESULT hr = RoGetActivationFactory(
312       HStringReference(
313           RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager)
314           .Get(),
315       IID_PPV_ARGS(&dtmInterop));
316   if (FAILED(hr) ||
317       FAILED(dtmInterop->GetForWindow(hwnd, IID_PPV_ARGS(&dtm)))) {
318     return Err(NS_ERROR_FAILURE);
319   }
320 
321   auto callback = Callback<
322       ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>(
323       [aCallback](IDataTransferManager*,
324                   IDataRequestedEventArgs* pArgs) -> HRESULT {
325         return aCallback(pArgs);
326       });
327 
328   EventRegistrationToken dataRequestedToken;
329   if (FAILED(dtm->add_DataRequested(callback.Get(), &dataRequestedToken)) ||
330       FAILED(dtmInterop->ShowShareUIForWindow(hwnd))) {
331     return Err(NS_ERROR_FAILURE);
332   }
333 
334   return Ok();
335 }
336 #endif
337 
338 RefPtr<SharePromise> WindowsUIUtils::Share(nsAutoString aTitle,
339                                            nsAutoString aText,
340                                            nsAutoString aUrl) {
341   auto promiseHolder = MakeRefPtr<
342       mozilla::media::Refcountable<MozPromiseHolder<SharePromise>>>();
343   RefPtr<SharePromise> promise = promiseHolder->Ensure(__func__);
344 
345 #ifndef __MINGW32__
346   auto result = RequestShare([promiseHolder, title = std::move(aTitle),
347                               text = std::move(aText), url = std::move(aUrl)](
348                                  IDataRequestedEventArgs* pArgs) {
349     ComPtr<IDataRequest> spDataRequest;
350     ComPtr<IDataPackage> spDataPackage;
351     ComPtr<IDataPackage2> spDataPackage2;
352     ComPtr<IDataPackage3> spDataPackage3;
353     ComPtr<IDataPackagePropertySet> spDataPackageProperties;
354 
355     if (FAILED(pArgs->get_Request(&spDataRequest)) ||
356         FAILED(spDataRequest->get_Data(&spDataPackage)) ||
357         FAILED(spDataPackage.As(&spDataPackage2)) ||
358         FAILED(spDataPackage.As(&spDataPackage3)) ||
359         FAILED(spDataPackage->get_Properties(&spDataPackageProperties))) {
360       promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
361       return E_FAIL;
362     }
363 
364     /*
365      * Windows always requires a title, and an empty string does not work.
366      * Thus we trick the API by passing a whitespace when we have no title.
367      * https://docs.microsoft.com/en-us/windows/uwp/app-to-app/share-data
368      */
369     auto wTitle = ConvertToWindowsString((title.IsVoid() || title.Length() == 0)
370                                              ? nsAutoString(u" "_ns)
371                                              : title);
372     if (wTitle.isErr() ||
373         FAILED(spDataPackageProperties->put_Title(wTitle.unwrap().get()))) {
374       promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
375       return E_FAIL;
376     }
377 
378     // Assign even if empty, as Windows requires some data to share
379     auto wText = ConvertToWindowsString(text);
380     if (wText.isErr() || FAILED(spDataPackage->SetText(wText.unwrap().get()))) {
381       promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
382       return E_FAIL;
383     }
384 
385     if (!url.IsVoid()) {
386       auto wUrl = ConvertToWindowsString(url);
387       if (wUrl.isErr()) {
388         promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
389         return wUrl.unwrapErr();
390       }
391 
392       ComPtr<IUriRuntimeClassFactory> uriFactory;
393       ComPtr<IUriRuntimeClass> uri;
394 
395       auto hr = GetActivationFactory(
396           HStringReference(RuntimeClass_Windows_Foundation_Uri).Get(),
397           &uriFactory);
398 
399       if (FAILED(hr) ||
400           FAILED(uriFactory->CreateUri(wUrl.unwrap().get(), &uri)) ||
401           FAILED(spDataPackage2->SetWebLink(uri.Get()))) {
402         promiseHolder->RejectIfExists(NS_ERROR_FAILURE, __func__);
403         return E_FAIL;
404       }
405     }
406 
407     auto completedCallback =
408         Callback<ITypedEventHandler<DataPackage*, ShareCompletedEventArgs*>>(
409             [promiseHolder](IDataPackage*,
410                             IShareCompletedEventArgs*) -> HRESULT {
411               promiseHolder->ResolveIfExists(true, __func__);
412               return S_OK;
413             });
414 
415     EventRegistrationToken dataRequestedToken;
416     if (FAILED(spDataPackage3->add_ShareCompleted(completedCallback.Get(),
417                                                   &dataRequestedToken))) {
418       promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
419       return E_FAIL;
420     }
421 
422     ComPtr<IDataPackage4> spDataPackage4;
423     if (SUCCEEDED(spDataPackage.As(&spDataPackage4))) {
424       // Use SharedCanceled API only on supported versions of Windows
425       // So that the older ones can still use ShareUrl()
426 
427       auto canceledCallback =
428           Callback<ITypedEventHandler<DataPackage*, IInspectable*>>(
429               [promiseHolder](IDataPackage*, IInspectable*) -> HRESULT {
430                 promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
431                 return S_OK;
432               });
433 
434       if (FAILED(spDataPackage4->add_ShareCanceled(canceledCallback.Get(),
435                                                    &dataRequestedToken))) {
436         promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
437         return E_FAIL;
438       }
439     }
440 
441     return S_OK;
442   });
443   if (result.isErr()) {
444     promiseHolder->Reject(result.unwrapErr(), __func__);
445   }
446 #else
447   promiseHolder->Reject(NS_ERROR_FAILURE, __func__);
448 #endif
449 
450   return promise;
451 }
452 
453 NS_IMETHODIMP
454 WindowsUIUtils::ShareUrl(const nsAString& aUrlToShare,
455                          const nsAString& aShareTitle) {
456   nsAutoString text;
457   text.SetIsVoid(true);
458   WindowsUIUtils::Share(nsAutoString(aShareTitle), text,
459                         nsAutoString(aUrlToShare));
460   return NS_OK;
461 }
462