diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
commit | 8dd16259287f58f9273002717ec4d27e97127719 (patch) | |
tree | 3863e62a53829a84037444beab3abd4ed9dfc7d0 /widget/windows | |
parent | Releasing progress-linux version 126.0.1-1~progress7.99u1. (diff) | |
download | firefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz firefox-8dd16259287f58f9273002717ec4d27e97127719.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/windows')
23 files changed, 1066 insertions, 537 deletions
diff --git a/widget/windows/CompositorWidgetParent.cpp b/widget/windows/CompositorWidgetParent.cpp index b25d30d9d5..d38125f4ca 100644 --- a/widget/windows/CompositorWidgetParent.cpp +++ b/widget/windows/CompositorWidgetParent.cpp @@ -149,16 +149,12 @@ mozilla::ipc::IPCResult CompositorWidgetParent::RecvNotifyVisibilityUpdated( return IPC_OK(); } -nsSizeMode CompositorWidgetParent::CompositorWidgetParent::GetWindowSizeMode() - const { - nsSizeMode sizeMode = mSizeMode; - return sizeMode; +nsSizeMode CompositorWidgetParent::GetWindowSizeMode() const { + return mSizeMode; } -bool CompositorWidgetParent::CompositorWidgetParent::GetWindowIsFullyOccluded() - const { - bool isFullyOccluded = mIsFullyOccluded; - return isFullyOccluded; +bool CompositorWidgetParent::GetWindowIsFullyOccluded() const { + return mIsFullyOccluded; } mozilla::ipc::IPCResult CompositorWidgetParent::RecvClearTransparentWindow() { diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index 6d8429f1cc..bb9b19e3a1 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1808,6 +1808,12 @@ const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() { V(10, 18, 15, 4256), V(10, 18, 15, 4293), "FEATURE_FAILURE_BUG_1833809", "Intel driver 10.18.15.*"); + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Windows, DeviceFamily::IntelGen12, + nsIGfxInfo::FEATURE_REUSE_DECODER_DEVICE, + nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED, + V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1896823"); + //////////////////////////////////// // FEATURE_OVERLAY_VP_AUTO_HDR diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp index e25c8c038f..69a46d5aad 100644 --- a/widget/windows/JumpListBuilder.cpp +++ b/widget/windows/JumpListBuilder.cpp @@ -18,6 +18,17 @@ #include "nsServiceManagerUtils.h" #include "WinUtils.h" +#ifdef __MINGW32__ +// The PKEY_Link_Arguments property key does not exist in the MINGW32 +// build configuration, so we define it ourselves here. +# define INITGUID // This alters the behavior of DEFINE_PROPERTYKEY so that + // we define PKEY_Link_Arguments rather than declare it. +# include <propkeydef.h> // For DEFINE_PROPERTYKEY() definition +DEFINE_PROPERTYKEY(PKEY_Link_Arguments, 0x436F2667, 0x14E2, 0x4FEB, 0xB3, 0x0A, + 0x14, 0x6C, 0x53, 0xB5, 0xB6, 0x74, 100); +# undef INITGUID +#endif + using mozilla::dom::Promise; using mozilla::dom::WindowsJumpListShortcutDescription; @@ -291,15 +302,35 @@ JumpListBuilder::CheckForRemovals(JSContext* aCx, Promise** aPromise) { } NS_IMETHODIMP -JumpListBuilder::PopulateJumpList( - const nsTArray<JS::Value>& aTaskDescriptions, const nsAString& aCustomTitle, - const nsTArray<JS::Value>& aCustomDescriptions, JSContext* aCx, - Promise** aPromise) { +JumpListBuilder::PopulateJumpList(JS::Handle<JS::Value> aTaskDescriptions, + const nsAString& aCustomTitle, + JS::Handle<JS::Value> aCustomDescriptions, + JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPromise); MOZ_ASSERT(mIOThread); - if (aCustomDescriptions.Length() && aCustomTitle.IsEmpty()) { + if (!aTaskDescriptions.isObject() || !aCustomDescriptions.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted<JSObject*> taskDescriptionsObj(aCx, &aTaskDescriptions.toObject()); + JS::Rooted<JSObject*> customDescriptionsObj(aCx, + &aCustomDescriptions.toObject()); + + uint32_t taskDescriptionsLength = 0; + uint32_t customDescriptionsLength = 0; + if (NS_WARN_IF(!JS::GetArrayLength(aCx, taskDescriptionsObj, + &taskDescriptionsLength))) { + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(!JS::GetArrayLength(aCx, customDescriptionsObj, + &customDescriptionsLength))) { + return NS_ERROR_INVALID_ARG; + } + + if (customDescriptionsLength && aCustomTitle.IsEmpty()) { return NS_ERROR_INVALID_ARG; } @@ -309,9 +340,11 @@ JumpListBuilder::PopulateJumpList( mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); nsTArray<WindowsJumpListShortcutDescription> taskDescs; - for (auto& jsval : aTaskDescriptions) { + for (uint32_t arrayIndex = 0; arrayIndex < taskDescriptionsLength; + arrayIndex++) { JS::Rooted<JS::Value> rootedVal(aCx); - if (NS_WARN_IF(!dom::ToJSValue(aCx, jsval, &rootedVal))) { + if (NS_WARN_IF( + !JS_GetElement(aCx, taskDescriptionsObj, arrayIndex, &rootedVal))) { return NS_ERROR_INVALID_ARG; } @@ -324,9 +357,11 @@ JumpListBuilder::PopulateJumpList( } nsTArray<WindowsJumpListShortcutDescription> customDescs; - for (auto& jsval : aCustomDescriptions) { + for (uint32_t arrayIndex = 0; arrayIndex < customDescriptionsLength; + arrayIndex++) { JS::Rooted<JS::Value> rootedVal(aCx); - if (NS_WARN_IF(!dom::ToJSValue(aCx, jsval, &rootedVal))) { + if (NS_WARN_IF(!JS_GetElement(aCx, customDescriptionsObj, arrayIndex, + &rootedVal))) { return NS_ERROR_INVALID_ARG; } @@ -592,7 +627,14 @@ void JumpListBuilder::DoPopulateJumpList( reinterpret_cast<const wchar_t*>(aCustomTitle.BeginReading()), pCustomArray); - if (FAILED(hr)) { + // E_ACCESSDENIED might be returned if Windows is configured not to show + // recently opened items in the start menu or jump lists. In that case, we + // still want to populate the tasks, so we ignore the error and commit + // the list. + // + // See + // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icustomdestinationlist-appendcategory + if (FAILED(hr) && hr != E_ACCESSDENIED) { rv = NS_ERROR_UNEXPECTED; return; } @@ -659,26 +701,47 @@ void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs( if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW, static_cast<void**>(getter_AddRefs(pLink))))) { + NS_WARNING("Could not get a IShellLink from the IObjectArray"); continue; } - wchar_t buf[MAX_PATH]; - HRESULT hres = pLink->GetArguments(buf, MAX_PATH); - if (SUCCEEDED(hres)) { - LPWSTR* arglist; - int32_t numArgs; - - arglist = ::CommandLineToArgvW(buf, &numArgs); - if (arglist && numArgs > 0) { - nsString spec(arglist[0]); - aURISpecs.AppendElement(std::move(spec)); - ::LocalFree(arglist); - } + RefPtr<IPropertyStore> pPropStore = nullptr; + if (NS_WARN_IF(FAILED(pLink->QueryInterface( + IID_IPropertyStore, + static_cast<void**>(getter_AddRefs(pPropStore)))))) { + NS_WARNING("Could not get IPropertyStore from IShellLink"); + continue; + } + + PROPVARIANT pv; + PropVariantInit(&pv); + auto cleanupPropVariant = MakeScopeExit([&] { PropVariantClear(&pv); }); + if (NS_WARN_IF(FAILED(pPropStore->GetValue(PKEY_Link_Arguments, &pv)))) { + NS_WARNING("Could not get PKEY_Link_Arguments from IPropertyStore"); + continue; + } + + if (pv.vt != VT_LPWSTR) { + NS_WARNING("Unexpected type returned for PKEY_Link_Arguments"); + continue; + } + + LPCWSTR args(char16ptr_t(pv.pwszVal)); + LPWSTR* arglist; + int32_t numArgs; + + arglist = ::CommandLineToArgvW(args, &numArgs); + if (arglist && numArgs > 0) { + nsString spec(arglist[0]); + aURISpecs.AppendElement(std::move(spec)); + ::LocalFree(arglist); } int iconIdx = 0; - hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx); + wchar_t buf[MAX_PATH + 1]; + HRESULT hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx); if (SUCCEEDED(hres)) { + buf[MAX_PATH] = '\0'; nsDependentString spec(buf); DeleteIconFromDisk(spec); } diff --git a/widget/windows/RemoteBackbuffer.cpp b/widget/windows/RemoteBackbuffer.cpp index 56a8bae0fe..2d53570c3e 100644 --- a/widget/windows/RemoteBackbuffer.cpp +++ b/widget/windows/RemoteBackbuffer.cpp @@ -89,6 +89,8 @@ class SharedImage { } bool Initialize(int32_t aWidth, int32_t aHeight) { + MOZ_ASSERT(aWidth); + MOZ_ASSERT(aHeight); MOZ_ASSERT(aWidth > 0); MOZ_ASSERT(aHeight > 0); @@ -183,9 +185,9 @@ class SharedImage { } } - int32_t GetWidth() { return mWidth; } + int32_t GetWidth() const { return mWidth; } - int32_t GetHeight() { return mHeight; } + int32_t GetHeight() const { return mHeight; } SharedImage(const SharedImage&) = delete; SharedImage(SharedImage&&) = delete; @@ -515,7 +517,7 @@ void Provider::HandleBorrowRequest(BorrowResponseData* aResponseData, aResponseData->result = ResponseResult::Error; - RECT clientRect = {}; + RECT clientRect{}; if (!::GetClientRect(mWindowHandle, &clientRect)) { return; } @@ -523,12 +525,12 @@ void Provider::HandleBorrowRequest(BorrowResponseData* aResponseData, MOZ_ASSERT(clientRect.left == 0); MOZ_ASSERT(clientRect.top == 0); - int32_t width = clientRect.right ? clientRect.right : 1; - int32_t height = clientRect.bottom ? clientRect.bottom : 1; + const int32_t width = std::max(int32_t(clientRect.right), 1); + const int32_t height = std::max(int32_t(clientRect.bottom), 1); bool needNewBackbuffer = !aAllowSameBuffer || !mBackbuffer || - (mBackbuffer->GetWidth() != width) || - (mBackbuffer->GetHeight() != height); + mBackbuffer->GetWidth() != width || + mBackbuffer->GetHeight() != height; if (!needNewBackbuffer) { aResponseData->result = ResponseResult::BorrowSameBuffer; diff --git a/widget/windows/ToastNotification.cpp b/widget/windows/ToastNotification.cpp index afdffc19ac..83692b26a1 100644 --- a/widget/windows/ToastNotification.cpp +++ b/widget/windows/ToastNotification.cpp @@ -776,6 +776,9 @@ ToastNotification::CloseAlert(const nsAString& aAlertName, bool aContextClosed) { RefPtr<ToastNotificationHandler> handler; if (NS_WARN_IF(!mActiveHandlers.Get(aAlertName, getter_AddRefs(handler)))) { + // This can happen when the handler is gone but the closure signal is not + // yet reached to the content process, and then the process tries closing + // the same signal. return NS_OK; } diff --git a/widget/windows/WinCompositorWidget.h b/widget/windows/WinCompositorWidget.h index fef967380c..df5d443067 100644 --- a/widget/windows/WinCompositorWidget.h +++ b/widget/windows/WinCompositorWidget.h @@ -18,8 +18,7 @@ class nsWindow; -namespace mozilla { -namespace widget { +namespace mozilla::widget { class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate { public: @@ -74,7 +73,7 @@ class WinCompositorWidget : public CompositorWidget { void UpdateCompositorWndSizeIfNecessary(); void RequestFxrOutput(); - bool HasFxrOutputHandler() const { return mFxrHandler != nullptr; } + bool HasFxrOutputHandler() const { return !!mFxrHandler; } FxROutputHandler* GetFxrOutputHandler() const { return mFxrHandler.get(); } virtual nsSizeMode GetWindowSizeMode() const = 0; @@ -97,7 +96,6 @@ class WinCompositorWidget : public CompositorWidget { UniquePtr<FxROutputHandler> mFxrHandler; }; -} // namespace widget -} // namespace mozilla +} // namespace mozilla::widget #endif // widget_windows_WinCompositorWidget_h diff --git a/widget/windows/filedialog/PWinFileDialog.ipdl b/widget/windows/filedialog/PWinFileDialog.ipdl index 812db7e103..b61bfc0432 100644 --- a/widget/windows/filedialog/PWinFileDialog.ipdl +++ b/widget/windows/filedialog/PWinFileDialog.ipdl @@ -12,6 +12,16 @@ namespace mozilla { namespace widget { namespace filedialog { +union FileResult { + Results?; + RemoteError; +}; + +union FolderResult { + nsString?; + RemoteError; +}; + [ChildProc=Utility] protocol PWinFileDialog { @@ -19,12 +29,13 @@ child: // Exactly one Show function should be called per instance. Further calls will // result in IPC failure. // - // Each will return `Nothing` iff the operation was canceled by the user. + // Each will return `Ok(Nothing)` if the operation was canceled by the user, + // or `Err(...)` if the operation actually failed. async ShowFileDialog(WindowsHandle parentHwnd, FileDialogType type, Command[] commands) - returns (Results? results); + returns (FileResult result); async ShowFolderDialog(WindowsHandle parentHwnd, Command[] commands) - returns (nsString? path); + returns (FolderResult result); }; } // namespace filedialog diff --git a/widget/windows/filedialog/WinFileDialogChild.cpp b/widget/windows/filedialog/WinFileDialogChild.cpp index a41018ff0e..ddb972534a 100644 --- a/widget/windows/filedialog/WinFileDialogChild.cpp +++ b/widget/windows/filedialog/WinFileDialogChild.cpp @@ -46,34 +46,23 @@ WinFileDialogChild::IPCResult WinFileDialogChild::MakeIpcFailure( return IPC_FAIL(this, what); } -#define MOZ_IPC_ENSURE_HRESULT_OK(hr, what) \ - do { \ - MOZ_LOG(sLogFileDialog, LogLevel::Verbose, \ - ("checking HRESULT for %s", what)); \ - HRESULT const _hr_ = (hr); \ - if (FAILED(_hr_)) { \ - MOZ_LOG(sLogFileDialog, LogLevel::Error, \ - ("HRESULT %8lX while %s", (hr), (what))); \ - return MakeIpcFailure(_hr_, (what)); \ - } \ - } while (0) - WinFileDialogChild::IPCResult WinFileDialogChild::RecvShowFileDialog( uintptr_t parentHwnd, FileDialogType type, nsTArray<Command> commands, FileResolver&& resolver) { MOZ_ABORT_IF_ALREADY_USED(); - SpawnFilePicker(HWND(parentHwnd), type, std::move(commands)) - ->Then( - GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__, - [resolver = std::move(resolver)](Maybe<Results> const& res) { - resolver(res); - }, - [self = RefPtr(this)](HRESULT hr) { - // this doesn't need to be returned anywhere; it'll crash the - // process as a side effect of construction - self->MakeIpcFailure(hr, "SpawnFilePicker"); - }); + auto promise = SpawnFilePicker(HWND(parentHwnd), type, std::move(commands)); + using RRV = std::decay_t<decltype(*promise)>::ResolveOrRejectValue; + + promise->Then(GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__, + [resolver = std::move(resolver)](RRV&& val) -> void { + if (val.IsResolve()) { + resolver(val.ResolveValue()); + } else { + auto err = val.RejectValue(); + resolver(RemoteError(err.where.Serialize(), err.why)); + } + }); return IPC_OK(); } @@ -83,23 +72,22 @@ WinFileDialogChild::IPCResult WinFileDialogChild::RecvShowFolderDialog( FolderResolver&& resolver) { MOZ_ABORT_IF_ALREADY_USED(); - SpawnFolderPicker(HWND(parentHwnd), std::move(commands)) - ->Then( - GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__, - [resolver = std::move(resolver)](Maybe<nsString> const& res) { - resolver(res); - }, - [self = RefPtr(this), resolver](HRESULT hr) { - // this doesn't need to be returned anywhere; it'll crash the - // process as a side effect of construction - self->MakeIpcFailure(hr, "SpawnFolderPicker"); - }); + auto promise = SpawnFolderPicker(HWND(parentHwnd), std::move(commands)); + using RRV = std::decay_t<decltype(*promise)>::ResolveOrRejectValue; + + promise->Then(GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__, + [resolver = std::move(resolver)](RRV&& val) -> void { + if (val.IsResolve()) { + resolver(val.ResolveValue()); + } else { + auto err = val.RejectValue(); + resolver(RemoteError(err.where.Serialize(), err.why)); + } + }); return IPC_OK(); } -#undef MOZ_IPC_ENSURE_HRESULT_OK - void WinFileDialogChild::ProcessingError(Result aCode, const char* aReason) { detail::LogProcessingError(sLogFileDialog, this, aCode, aReason); } diff --git a/widget/windows/filedialog/WinFileDialogCommands.cpp b/widget/windows/filedialog/WinFileDialogCommands.cpp index 838856893d..75abbad343 100644 --- a/widget/windows/filedialog/WinFileDialogCommands.cpp +++ b/widget/windows/filedialog/WinFileDialogCommands.cpp @@ -22,6 +22,20 @@ namespace mozilla::widget::filedialog { +const char* Error::KindName(Error::Kind kind) { + switch (kind) { + case LocalError: + return "LocalError"; + case RemoteError: + return "RemoteError"; + case IPCError: + return "IPCError"; + default: + MOZ_ASSERT(false); + return "<bad value>"; + } +} + // Visitor to apply commands to the dialog. struct Applicator { IFileDialog* dialog = nullptr; @@ -98,13 +112,15 @@ static HRESULT GetShellItemPath(IShellItem* aItem, nsString& aResultString) { } } // namespace -#define MOZ_ENSURE_HRESULT_OK(call_) \ - do { \ - HRESULT const _tmp_hr_ = (call_); \ - if (FAILED(_tmp_hr_)) return Err(_tmp_hr_); \ +#define MOZ_ENSURE_HRESULT_OK(where, call_) \ + do { \ + HRESULT const _tmp_hr_ = (call_); \ + if (FAILED(_tmp_hr_)) { \ + return mozilla::Err(MOZ_FD_LOCAL_ERROR(where, _tmp_hr_)); \ + } \ } while (0) -mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog( +mozilla::Result<RefPtr<IFileDialog>, Error> MakeFileDialog( FileDialogType type) { RefPtr<IFileDialog> dialog; @@ -112,43 +128,45 @@ mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog( : CLSID_FileSaveDialog; HRESULT const hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, IID_IFileDialog, getter_AddRefs(dialog)); - MOZ_ENSURE_HRESULT_OK(hr); + // more properly: "CoCreateInstance(CLSID_...)", but this suffices + MOZ_ENSURE_HRESULT_OK("MakeFileDialog", hr); return std::move(dialog); } -HRESULT ApplyCommands(::IFileDialog* dialog, - nsTArray<Command> const& commands) { +mozilla::Result<Ok, Error> ApplyCommands(::IFileDialog* dialog, + nsTArray<Command> const& commands) { Applicator applicator{.dialog = dialog}; for (auto const& cmd : commands) { HRESULT const hr = applicator.Visit(cmd); - if (FAILED(hr)) { - return hr; - } + MOZ_ENSURE_HRESULT_OK("ApplyCommands", hr); } - return S_OK; + return Ok{}; } -mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog* dialog) { +mozilla::Result<Results, Error> GetFileResults(::IFileDialog* dialog) { FILEOPENDIALOGOPTIONS fos; - MOZ_ENSURE_HRESULT_OK(dialog->GetOptions(&fos)); + MOZ_ENSURE_HRESULT_OK("IFileDialog::GetOptions", dialog->GetOptions(&fos)); using widget::WinUtils; // Extract which filter type the user selected UINT index; - MOZ_ENSURE_HRESULT_OK(dialog->GetFileTypeIndex(&index)); + MOZ_ENSURE_HRESULT_OK("IFileDialog::GetFileTypeIndex", + dialog->GetFileTypeIndex(&index)); // single selection if ((fos & FOS_ALLOWMULTISELECT) == 0) { RefPtr<IShellItem> item; - MOZ_ENSURE_HRESULT_OK(dialog->GetResult(getter_AddRefs(item))); + MOZ_ENSURE_HRESULT_OK("IFileDialog::GetResult", + dialog->GetResult(getter_AddRefs(item))); if (!item) { - return Err(E_FAIL); + return Err(MOZ_FD_LOCAL_ERROR("IFileDialog::GetResult: item", E_POINTER)); } nsAutoString path; - MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, path)); + MOZ_ENSURE_HRESULT_OK("GetFileResults: GetShellItemPath (1)", + GetShellItemPath(item, path)); return Results({path}, index); } @@ -158,25 +176,29 @@ mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog* dialog) { dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg)); if (!openDlg) { MOZ_ASSERT(false, "a file-save dialog was given FOS_ALLOWMULTISELECT?"); - return Err(E_UNEXPECTED); + return Err(MOZ_FD_LOCAL_ERROR("Save + FOS_ALLOWMULTISELECT", E_UNEXPECTED)); } RefPtr<IShellItemArray> items; - MOZ_ENSURE_HRESULT_OK(openDlg->GetResults(getter_AddRefs(items))); + MOZ_ENSURE_HRESULT_OK("IFileOpenDialog::GetResults", + openDlg->GetResults(getter_AddRefs(items))); if (!items) { - return Err(E_FAIL); + return Err( + MOZ_FD_LOCAL_ERROR("IFileOpenDialog::GetResults: items", E_POINTER)); } nsTArray<nsString> paths; DWORD count = 0; - MOZ_ENSURE_HRESULT_OK(items->GetCount(&count)); + MOZ_ENSURE_HRESULT_OK("IShellItemArray::GetCount", items->GetCount(&count)); for (DWORD idx = 0; idx < count; idx++) { RefPtr<IShellItem> item; - MOZ_ENSURE_HRESULT_OK(items->GetItemAt(idx, getter_AddRefs(item))); + MOZ_ENSURE_HRESULT_OK("IShellItemArray::GetItemAt", + items->GetItemAt(idx, getter_AddRefs(item))); nsAutoString str; - MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, str)); + MOZ_ENSURE_HRESULT_OK("GetFileResults: GetShellItemPath (2)", + GetShellItemPath(item, str)); paths.EmplaceBack(str); } @@ -184,15 +206,17 @@ mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog* dialog) { return Results(std::move(paths), std::move(index)); } -mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog* dialog) { +mozilla::Result<nsString, Error> GetFolderResults(::IFileDialog* dialog) { RefPtr<IShellItem> item; - MOZ_ENSURE_HRESULT_OK(dialog->GetResult(getter_AddRefs(item))); + MOZ_ENSURE_HRESULT_OK("IFileDialog::GetResult", + dialog->GetResult(getter_AddRefs(item))); + if (!item) { // shouldn't happen -- probably a precondition failure on our part, but // might be due to misbehaving shell extensions? MOZ_ASSERT(false, "unexpected lack of item: was `Show`'s return value checked?"); - return Err(E_FAIL); + return Err(MOZ_FD_LOCAL_ERROR("IFileDialog::GetResult: item", E_POINTER)); } // If the user chose a Win7 Library, resolve to the library's @@ -200,6 +224,7 @@ mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog* dialog) { RefPtr<IShellLibrary> shellLib; RefPtr<IShellItem> folderPath; MOZ_ENSURE_HRESULT_OK( + "CoCreateInstance(CLSID_ShellLibrary)", CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLibrary, getter_AddRefs(shellLib))); @@ -211,7 +236,7 @@ mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog* dialog) { // get the folder's file system path nsAutoString str; - MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, str)); + MOZ_ENSURE_HRESULT_OK("GetShellItemPath", GetShellItemPath(item, str)); return str; } @@ -312,12 +337,22 @@ void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller, template <typename Res, typename Action, size_t N> RefPtr<Promise<Res>> SpawnFileDialogThread(const char (&where)[N], Action action) { + { + using ActionRetT = std::invoke_result_t<Action>; + using Info = detail::DestructureResult<ActionRetT>; + + MOZ_ASSERT_SAME_TYPE( + typename Info::ErrorT, Error, + "supplied Action must return Result<T, filedialog::Err>"); + } + RefPtr<nsIThread> thread; { nsresult rv = NS_NewNamedThread("File Dialog", getter_AddRefs(thread), nullptr, {.isUiThread = true}); if (NS_FAILED(rv)) { - return Promise<Res>::CreateAndReject((HRESULT)rv, where); + return Promise<Res>::CreateAndReject( + MOZ_FD_LOCAL_ERROR("NS_NewNamedThread", (HRESULT)rv), where); } } // `thread` is single-purpose, and should not perform any additional work @@ -393,7 +428,7 @@ RefPtr<Promise<Res>> SpawnFileDialogThread(const char (&where)[N], } // Actually invoke the action and report the result. - Result<Res, HRESULT> val = action(); + Result<Res, Error> val = action(); if (val.isErr()) { promise->Reject(val.unwrapErr(), where); } else { @@ -407,16 +442,16 @@ RefPtr<Promise<Res>> SpawnFileDialogThread(const char (&where)[N], // For F returning `Result<T, E>`, yields the type `T`. template <typename F, typename... Args> using inner_result_of = - typename std::remove_reference_t<decltype(std::declval<F>()( - std::declval<Args>()...))>::ok_type; + typename detail::DestructureResult<std::invoke_result_t<F, Args...>>::OkT; template <typename ExtractorF, typename RetT = inner_result_of<ExtractorF, IFileDialog*>> auto SpawnPickerT(HWND parent, FileDialogType type, ExtractorF&& extractor, nsTArray<Command> commands) -> RefPtr<Promise<Maybe<RetT>>> { + using ActionRetT = Result<Maybe<RetT>, Error>; + return detail::SpawnFileDialogThread<Maybe<RetT>>( - __PRETTY_FUNCTION__, - [=, commands = std::move(commands)]() -> Result<Maybe<RetT>, HRESULT> { + __PRETTY_FUNCTION__, [=, commands = std::move(commands)]() -> ActionRetT { // On Win10, the picker doesn't support per-monitor DPI, so we create it // with our context set temporarily to system-dpi-aware. WinUtils::AutoSystemDpiAware dpiAwareness; @@ -424,15 +459,13 @@ auto SpawnPickerT(HWND parent, FileDialogType type, ExtractorF&& extractor, RefPtr<IFileDialog> dialog; MOZ_TRY_VAR(dialog, MakeFileDialog(type)); - if (HRESULT const rv = ApplyCommands(dialog, commands); FAILED(rv)) { - return mozilla::Err(rv); - } + MOZ_TRY(ApplyCommands(dialog, commands)); if (HRESULT const rv = dialog->Show(parent); FAILED(rv)) { if (rv == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { - return Result<Maybe<RetT>, HRESULT>(Nothing()); + return ActionRetT{Nothing()}; } - return mozilla::Err(rv); + return mozilla::Err(MOZ_FD_LOCAL_ERROR("IFileDialog::Show", rv)); } RetT res; diff --git a/widget/windows/filedialog/WinFileDialogCommands.h b/widget/windows/filedialog/WinFileDialogCommands.h index ca4561a8f2..800ce832d5 100644 --- a/widget/windows/filedialog/WinFileDialogCommands.h +++ b/widget/windows/filedialog/WinFileDialogCommands.h @@ -19,27 +19,200 @@ struct IFileOpenDialog; namespace mozilla::widget::filedialog { +namespace detail { + +template <typename T, typename E, bool B> +struct PromiseInfo { + using ResolveT = T; + using RejectT = E; + constexpr static const bool IsExclusive = B; + using Promise = MozPromise<T, E, B>; +}; + +template <typename P> +auto DestructurePromiseImpl(P&&) { + // Debugging hint: A type in the instantiation chain (here named `P`) was + // expected to be a `RefPtr<MozPromise<...>>`, but was some other type. + static_assert(false, "expected P = RefPtr<MozPromise< ... >>"); +} + +template <typename T, typename E, bool B> +auto DestructurePromiseImpl(RefPtr<MozPromise<T, E, B>>&&) + -> PromiseInfo<T, E, B>; + +template <typename P> +using DestructurePromise = + std::decay_t<decltype(DestructurePromiseImpl(std::declval<P>()))>; + +template <typename T, typename E> +struct ResultInfo { + using OkT = T; + using ErrorT = E; +}; + +template <typename R> +auto DestructureResultImpl(R&&) { + // Debugging hint: A type in the instantiation chain (here named `R`) was + // expected to be a `mozilla::Result<...>`, but was some other type. + static_assert(false, "expected R = mozilla::Result< ... >"); +} + +template <typename T, typename E> +auto DestructureResultImpl(mozilla::Result<T, E>&&) -> ResultInfo<T, E>; + +template <typename R> +using DestructureResult = + std::decay_t<decltype(DestructureResultImpl(std::declval<R>()))>; + +#define MOZ_ASSERT_SAME_TYPE(T1, T2, ...) \ + static_assert(std::is_same_v<T1, T2>, ##__VA_ARGS__) +} // namespace detail + extern LazyLogModule sLogFileDialog; +// Simple struct for reporting errors. +struct Error { + enum Kind { + // This error's source was within the local (current) process. + LocalError, + // This error's source was within the remote host process, and it was + // reported via noncatastrophic channels. + RemoteError, + // This error was reported via the IPC subsystem. (This includes unexpected + // remote host-process crashes.) + IPCError, + }; + + // "Enum" denoting error-location. Members are in `VALID_STRINGS`, and have no + // name other than their string. + // + // (Note: under C++20, this could reasonably be replaced with an `nsString` + // alongside a check that all constructors are either a) consteval or b) from + // IPC.) + class Location { + uint32_t value; + constexpr explicit Location(uint32_t value) : value(value) {} + + // Valid locations for errors. (Indices do not need to remain stable between + // releases; but -- where meaningful -- string values themselves should, for + // ease of telemetry-aggregation.) + constexpr static std::string_view const VALID_STRINGS[] = { + "ApplyCommands", + "CoCreateInstance(CLSID_ShellLibrary)", + "GetFileResults: GetShellItemPath (1)", + "GetFileResults: GetShellItemPath (2)", + "GetShellItemPath", + "IFileDialog::GetFileTypeIndex", + "IFileDialog::GetOptions", + "IFileDialog::GetResult", + "IFileDialog::GetResult: item", + "IFileDialog::Show", + "IFileOpenDialog::GetResults", + "IFileOpenDialog::GetResults: items", + "IPC", + "IShellItemArray::GetCount", + "IShellItemArray::GetItemAt", + "MakeFileDialog", + "NS_NewNamedThread", + "Save + FOS_ALLOWMULTISELECT", + "ShowFilePicker", + "ShowFolderPicker", + "ShowRemote: UtilityProcessManager::GetSingleton", + "ShowRemote: invocation of CreateWinFileDialogActor", + "UtilityProcessManager::CreateWinFileDialogActor", + "internal IPC failure?", + }; + constexpr static size_t VALID_STRINGS_COUNT = + std::extent_v<decltype(VALID_STRINGS)>; + + // Prevent duplicates from occurring in VALID_STRINGS by forcing it to be + // sorted. (Note that std::is_sorted is not constexpr until C++20.) + static_assert( + []() { + for (size_t i = 0; i + 1 < VALID_STRINGS_COUNT; ++i) { + if (!(VALID_STRINGS[i] < VALID_STRINGS[i + 1])) { + return false; + } + } + return true; + }(), + "VALID_STRINGS should be ASCIIbetically sorted"); + + public: + constexpr uint32_t Serialize() const { return value; } + constexpr static Location Deserialize(uint32_t val) { + return Location{val}; + } + + public: + constexpr static Location npos() { return Location{~uint32_t(0)}; } + + constexpr bool IsValid() const { return value < VALID_STRINGS_COUNT; } + + constexpr std::string_view ToString() const { + return value < VALID_STRINGS_COUNT ? VALID_STRINGS[value] + : "<bad filedialog::Error::Location?>"; + } + constexpr static Location FromString(std::string_view str) { + for (uint32_t i = 0; i < VALID_STRINGS_COUNT; ++i) { + if (str == VALID_STRINGS[i]) return Location{i}; + } + return npos(); + } + + constexpr char const* c_str() const { return ToString().data(); } + }; + + // Where and how (run-time) this error occurred. + Kind kind; + // Where (compile-time) this error occurred. + Location where; + // Why (run-time) this error occurred. Probably an HRESULT. + uint32_t why; + + // `impl Debug for Kind` + static const char* KindName(Kind); +}; + +// Create a filedialog::Error, confirming at compile-time that the supplied +// where-string is valid. +#define MOZ_FD_ERROR(kind_, where_, why_) \ + ([](HRESULT why_arg_) -> ::mozilla::widget::filedialog::Error { \ + using Error = ::mozilla::widget::filedialog::Error; \ + constexpr static const Error::Location loc = \ + Error::Location::FromString(where_); \ + static_assert( \ + loc.IsValid(), \ + "filedialog::Error: location not found in Error::VALID_STRINGS"); \ + return Error{ \ + .kind = Error::kind_, .where = loc, .why = (uint32_t)why_arg_}; \ + }(why_)) + +// Create a filedialog::Error of kind LocalError (the usual case). +#define MOZ_FD_LOCAL_ERROR(where_, why_) MOZ_FD_ERROR(LocalError, where_, why_) + +template <typename R> +using Promise = MozPromise<R, Error, true>; + enum class FileDialogType : uint8_t { Open, Save }; // Create a file-dialog of the relevant type. Requires MSCOM to be initialized. -mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog(FileDialogType); +mozilla::Result<RefPtr<IFileDialog>, Error> MakeFileDialog(FileDialogType); // Apply the selected commands to the IFileDialog, in preparation for showing // it. (The actual showing step is left to the caller.) -[[nodiscard]] HRESULT ApplyCommands(::IFileDialog*, - nsTArray<Command> const& commands); +mozilla::Result<Ok, Error> ApplyCommands(::IFileDialog*, + nsTArray<Command> const& commands); // Extract one or more results from the file-picker dialog. // // Requires that Show() has been called and has returned S_OK. -mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog*); +mozilla::Result<Results, Error> GetFileResults(::IFileDialog*); // Extract the chosen folder from the folder-picker dialog. // // Requires that Show() has been called and has returned S_OK. -mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog*); +mozilla::Result<nsString, Error> GetFolderResults(::IFileDialog*); namespace detail { // Log the error. If it's a notable error, kill the child process. @@ -48,9 +221,6 @@ void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller, } // namespace detail -template <typename R> -using Promise = MozPromise<R, HRESULT, true>; - // Show a file-picker on another thread in the current process. RefPtr<Promise<Maybe<Results>>> SpawnFilePicker(HWND parent, FileDialogType type, diff --git a/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh b/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh index dd85942f24..352b46df17 100644 --- a/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh +++ b/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh @@ -44,6 +44,19 @@ struct Results { uint32_t selectedFileTypeIndex; }; +// Homolog of filedialog::Err. (Always Err::Kind::RemoteError, by definition.) +struct RemoteError { + // An enum (`filedialog::Error::Location`) describing the compile-time location + // where the error was detected. + // + // (This value is validated at use-sites; if the child process sends a bad + // value, nothing of import will happen.) + uint32_t where; + // An error code describing the error itself more precisely. Its semantics + // depend on the context provided by `where`, but it's probably an HRESULT. + uint32_t why; +}; + } // namespace filedialog } // namespace widget } // namespace mozilla diff --git a/widget/windows/filedialog/WinFileDialogParent.cpp b/widget/windows/filedialog/WinFileDialogParent.cpp index 2c256a1506..329c72cc94 100644 --- a/widget/windows/filedialog/WinFileDialogParent.cpp +++ b/widget/windows/filedialog/WinFileDialogParent.cpp @@ -54,6 +54,86 @@ PWinFileDialogParent::nsresult WinFileDialogParent::BindToUtilityProcess( return NS_OK; } +// Convert the raw IPC promise-type to a filedialog::Promise. +template <typename T, typename Ex, size_t N> +static auto ConvertToFDPromise( + const char (&aMethod)[N], // __func__ + Ex&& extractor, + RefPtr<MozPromise<T, mozilla::ipc::ResponseRejectReason, true>> + aSrcPromise) { + // The extractor must produce a `mozilla::Result<..., Error>` from `T`. + using SrcResultInfo = detail::DestructureResult<std::invoke_result_t<Ex, T>>; + using ResolveT = typename SrcResultInfo::OkT; + static_assert(std::is_same_v<typename SrcResultInfo::ErrorT, Error>, + "expected T to be a Result<..., Error>"); + + using SrcPromiseT = MozPromise<T, mozilla::ipc::ResponseRejectReason, true>; + using DstPromiseT = MozPromise<ResolveT, Error, true>; + + RefPtr<DstPromiseT> ret = aSrcPromise->Then( + mozilla::GetCurrentSerialEventTarget(), aMethod, + + [extractor, aMethod](T&& val) { + mozilla::Result<ResolveT, Error> result = extractor(std::move(val)); + if (result.isOk()) { + return DstPromiseT::CreateAndResolve(result.unwrap(), aMethod); + } + return DstPromiseT::CreateAndReject(result.unwrapErr(), aMethod); + }, + [aMethod](typename mozilla::ipc::ResponseRejectReason&& val) { + return DstPromiseT::CreateAndReject( + MOZ_FD_ERROR(IPCError, "IPC", (uint32_t)val), aMethod); + }); + + return ret; +} + +template <typename Input, typename Output> +struct Extractor { + template <typename Input::Type tag_, Output const& (Input::*getter_)() const> + static auto get() { + return [](Input&& res) -> Result<Output, Error> { + if (res.type() == tag_) { + return (res.*getter_)(); + } + if (res.type() == Input::TRemoteError) { + RemoteError err = res.get_RemoteError(); + return Err(Error{.kind = Error::RemoteError, + .where = Error::Location::Deserialize(err.where()), + .why = err.why()}); + } + MOZ_ASSERT_UNREACHABLE("internal IPC failure?"); + return Err(MOZ_FD_ERROR(IPCError, "internal IPC failure?", E_FAIL)); + }; + } +}; + +[[nodiscard]] RefPtr<WinFileDialogParent::ShowFileDialogPromise> +WinFileDialogParent::ShowFileDialogImpl(HWND parent, const FileDialogType& type, + mozilla::Span<Command const> commands) { + auto inner_promise = PWinFileDialogParent::SendShowFileDialog( + reinterpret_cast<WindowsHandle>(parent), type, std::move(commands)); + + return ConvertToFDPromise( + __func__, + Extractor<FileResult, Maybe<Results>>::get< + FileResult::TMaybeResults, &FileResult::get_MaybeResults>(), + std::move(inner_promise)); +} + +[[nodiscard]] RefPtr<WinFileDialogParent::ShowFolderDialogPromise> +WinFileDialogParent::ShowFolderDialogImpl( + HWND parent, mozilla::Span<Command const> commands) { + auto inner_promise = PWinFileDialogParent::SendShowFolderDialog( + reinterpret_cast<WindowsHandle>(parent), std::move(commands)); + + return ConvertToFDPromise( + __func__, + Extractor<FolderResult, Maybe<nsString>>::get< + FolderResult::TMaybensString, &FolderResult::get_MaybensString>(), + std::move(inner_promise)); +} + void WinFileDialogParent::ProcessingError(Result aCode, const char* aReason) { detail::LogProcessingError(sLogFileDialog, this, aCode, aReason); } diff --git a/widget/windows/filedialog/WinFileDialogParent.h b/widget/windows/filedialog/WinFileDialogParent.h index a2c1197c55..a1eb547444 100644 --- a/widget/windows/filedialog/WinFileDialogParent.h +++ b/widget/windows/filedialog/WinFileDialogParent.h @@ -21,11 +21,14 @@ namespace mozilla::widget::filedialog { -class WinFileDialogParent : public PWinFileDialogParent { +class WinFileDialogParent final : private PWinFileDialogParent { public: using UtilityActorName = ::mozilla::UtilityActorName; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinFileDialogParent, override); + using ShowFileDialogPromise = Promise<Maybe<Results>>; + using ShowFolderDialogPromise = Promise<Maybe<nsString>>; + public: WinFileDialogParent(); nsresult BindToUtilityProcess( @@ -35,6 +38,16 @@ class WinFileDialogParent : public PWinFileDialogParent { return UtilityActorName::WindowsFileDialog; } + bool CanSend() const { return PWinFileDialogParent::CanSend(); } + void Close() { return PWinFileDialogParent::Close(); } + + [[nodiscard]] RefPtr<ShowFileDialogPromise> ShowFileDialogImpl( + HWND parent, const FileDialogType& type, + mozilla::Span<Command const> commands); + + [[nodiscard]] RefPtr<ShowFolderDialogPromise> ShowFolderDialogImpl( + HWND parent, mozilla::Span<Command const> commands); + private: ~WinFileDialogParent(); diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp index 88a2a2ad09..60e2784f37 100644 --- a/widget/windows/nsDataObj.cpp +++ b/widget/windows/nsDataObj.cpp @@ -346,10 +346,10 @@ HRESULT nsDataObj::CreateStream(IStream** outStream) { pStream->AddRef(); - // query the requestingPrincipal from the transferable and add it to the new - // channel + // query the dataPrincipal from the transferable and add it to the new + // channel. nsCOMPtr<nsIPrincipal> requestingPrincipal = - mTransferable->GetRequestingPrincipal(); + mTransferable->GetDataPrincipal(); MOZ_ASSERT(requestingPrincipal, "can not create channel without a principal"); // Note that the cookieJarSettings could be null if the data object is for the diff --git a/widget/windows/nsFilePicker.cpp b/widget/windows/nsFilePicker.cpp index 2a45937988..89b10ce4ee 100644 --- a/widget/windows/nsFilePicker.cpp +++ b/widget/windows/nsFilePicker.cpp @@ -44,15 +44,17 @@ #include "mozilla/widget/filedialog/WinFileDialogCommands.h" #include "mozilla/widget/filedialog/WinFileDialogParent.h" +using mozilla::LogLevel; using mozilla::UniquePtr; using namespace mozilla::widget; +template <typename Res> +using FDPromise = filedialog::Promise<Res>; + UniquePtr<char16_t[], nsFilePicker::FreeDeleter> nsFilePicker::sLastUsedUnicodeDirectory; -using mozilla::LogLevel; - #define MAX_EXTENSION_LENGTH 10 /////////////////////////////////////////////////////////////////////////////// @@ -106,27 +108,29 @@ NS_IMETHODIMP nsFilePicker::Init( } namespace mozilla::detail { +using Error = mozilla::widget::filedialog::Error; + // Boilerplate for remotely showing a file dialog. template <typename ActionType, typename ReturnType = typename decltype(std::declval<ActionType>()( nullptr))::element_type::ResolveValueType> -static auto ShowRemote(ActionType&& action) - -> RefPtr<MozPromise<ReturnType, HRESULT, true>> { - using RetPromise = MozPromise<ReturnType, HRESULT, true>; +static auto ShowRemote(ActionType&& action) -> RefPtr<FDPromise<ReturnType>> { + using RetPromise = FDPromise<ReturnType>; - constexpr static const auto fail = []() { - return RetPromise::CreateAndReject(E_FAIL, __PRETTY_FUNCTION__); - }; +// "function-local" #define +#define FAIL(where_, why_) \ + return RetPromise::CreateAndReject(MOZ_FD_LOCAL_ERROR(where_, why_), \ + __PRETTY_FUNCTION__) auto mgr = mozilla::ipc::UtilityProcessManager::GetSingleton(); if (!mgr) { MOZ_ASSERT(false); - return fail(); + FAIL("ShowRemote: UtilityProcessManager::GetSingleton", E_POINTER); } auto wfda = mgr->CreateWinFileDialogActor(); if (!wfda) { - return fail(); + FAIL("ShowRemote: invocation of CreateWinFileDialogActor", E_POINTER); } using mozilla::widget::filedialog::sLogFileDialog; @@ -135,32 +139,70 @@ static auto ShowRemote(ActionType&& action) mozilla::GetMainThreadSerialEventTarget(), "nsFilePicker ShowRemote acquire", [action = std::forward<ActionType>(action)]( - filedialog::ProcessProxy const& p) -> RefPtr<RetPromise> { + filedialog::ProcessProxy p) -> RefPtr<RetPromise> { MOZ_LOG(sLogFileDialog, LogLevel::Info, ("nsFilePicker ShowRemote first callback: p = [%p]", p.get())); // false positive: not actually redundant // NOLINTNEXTLINE(readability-redundant-smartptr-get) - return action(p.get())->Then( - mozilla::GetMainThreadSerialEventTarget(), - "nsFilePicker ShowRemote call", - [p](ReturnType ret) { - return RetPromise::CreateAndResolve(std::move(ret), - __PRETTY_FUNCTION__); - }, - [](mozilla::ipc::ResponseRejectReason error) { - MOZ_LOG(sLogFileDialog, LogLevel::Error, - ("IPC call rejected: %zu", size_t(error))); - return fail(); + auto promise = action(p.get()); + return promise->Map( + mozilla::GetMainThreadSerialEventTarget(), __func__, + [p = std::move(p)](typename RetPromise::ResolveValueType&& val) { + // explicitly retain the ProcessProxy until at least this point + return std::move(val); }); }, [](nsresult error) -> RefPtr<RetPromise> { MOZ_LOG(sLogFileDialog, LogLevel::Error, ("could not acquire WinFileDialog: %zu", size_t(error))); - return fail(); + // TODO: pipe more data up from utility-process creation + FAIL("UtilityProcessManager::CreateWinFileDialogActor", + (uint32_t)error); + }); + +#undef FAIL +} + +namespace { + +static RefPtr<FDPromise<Maybe<filedialog::Results>>> ShowFilePickerRemote( + HWND parent, filedialog::FileDialogType type, + nsTArray<filedialog::Command> const& commands) { + using mozilla::widget::filedialog::sLogFileDialog; + return mozilla::detail::ShowRemote( + [parent, type, + commands = commands.Clone()](filedialog::WinFileDialogParent* p) { + MOZ_LOG(sLogFileDialog, LogLevel::Info, + ("%s: p = [%p]", __PRETTY_FUNCTION__, p)); + return p->ShowFileDialogImpl(parent, type, commands); }); } +static RefPtr<FDPromise<Maybe<nsString>>> ShowFolderPickerRemote( + HWND parent, nsTArray<filedialog::Command> const& commands) { + using mozilla::widget::filedialog::sLogFileDialog; + return mozilla::detail::ShowRemote([parent, commands = commands.Clone()]( + filedialog::WinFileDialogParent* p) { + MOZ_LOG(sLogFileDialog, LogLevel::Info, + ("%s: p = [%p]", __PRETTY_FUNCTION__, p)); + return p->ShowFolderDialogImpl(parent, commands); + }); +} + +static RefPtr<FDPromise<Maybe<filedialog::Results>>> ShowFilePickerLocal( + HWND parent, filedialog::FileDialogType type, + nsTArray<filedialog::Command> const& commands) { + return filedialog::SpawnFilePicker(parent, type, commands.Clone()); +} + +static RefPtr<FDPromise<Maybe<nsString>>> ShowFolderPickerLocal( + HWND parent, nsTArray<filedialog::Command> const& commands) { + return filedialog::SpawnFolderPicker(parent, commands.Clone()); +} + +} // namespace + // fd_async // // Wrapper-namespace for the AsyncExecute() and AsyncAll() functions. @@ -296,31 +338,83 @@ static void RecordFailure(uint64_t (&&time)[3], HRESULT hrRemote, } } // namespace telemetry + +/* N.B.: L and R stand for Local and Remote, not just Left and Right */ +template <typename FnL, typename FnR, typename... Args> +struct AsyncExecuteInfo { + template <typename T> + using DestructurePromise = widget::filedialog::detail::DestructurePromise<T>; + + using Unit = ::mozilla::Ok; + + using RetL = std::invoke_result_t<FnL, Args...>; + using RetR = std::invoke_result_t<FnR, Args...>; + + using InfoL = DestructurePromise<RetL>; + using InfoR = DestructurePromise<RetR>; + + MOZ_ASSERT_SAME_TYPE( + typename InfoL::ResolveT, typename InfoR::ResolveT, + "local and remote promises must have identical resolve-types"); + + // At present, the local and remote promises have the same type, but this + // isn't logically necessary. (In particular, a future refactor may remove the + // redundant `.kind` from the local promises' return types.) + MOZ_ASSERT_SAME_TYPE(typename InfoL::RejectT, filedialog::Error, + "local promise must reject with a filedialog::Error"); + + MOZ_ASSERT_SAME_TYPE(typename InfoR::RejectT, filedialog::Error, + "remote promise must reject with a filedialog::Error"); + + using ResolveT = typename InfoL::ResolveT; + using PromiseT = MozPromise<ResolveT, Unit, true>; + + using RetT = RefPtr<PromiseT>; +}; + } // namespace details -// Invoke either or both of a "do locally" and "do remotely" function with the -// provided arguments, depending on the relevant preference-value and whether -// or not the remote version fails. +// Invoke either or both of a promise-returning "do locally" and "do remotely" +// function with the provided arguments, depending on the relevant preference's +// value and on whether or not the remote version fails (returns a rejection- +// promise). // -// Both functions must be asynchronous, returning a `RefPtr<MozPromise<...>>`. -// "Failure" is defined as the promise being rejected. +// Both provided functions must return a `RefPtr<filedialog::MozPromise<T>>`. As +// `AsyncExecute` reports failures itself, its rejection-type is `()`. template <typename Fn1, typename Fn2, typename... Args> -static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args) - -> std::invoke_result_t<Fn1, Args...> { +static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args) -> + typename details::AsyncExecuteInfo<Fn1, Fn2, Args...>::RetT { using namespace details; + using Info = AsyncExecuteInfo<Fn1, Fn2, Args...>; - static_assert(std::is_same_v<std::invoke_result_t<Fn1, Args...>, - std::invoke_result_t<Fn2, Args...>>); - using PromiseT = typename std::invoke_result_t<Fn1, Args...>::element_type; + using ResolveT = typename Info::ResolveT; + using PromiseT = typename Info::PromiseT; + using LPromiseT = typename Info::InfoL::Promise; + using RPromiseT = typename Info::InfoR::Promise; constexpr static char kFunctionName[] = "LocalAndOrRemote::AsyncExecute"; switch (GetStrategy()) { - case Local: - return local(args...); + case Local: { + return local(args...)->MapErr( + NS_GetCurrentThread(), __func__, [](Error const& err) { + MOZ_ASSERT(err.kind == Error::LocalError); + MOZ_LOG(filedialog::sLogFileDialog, LogLevel::Info, + ("local file-dialog failed: where=%s, why=%08" PRIX32, + err.where.c_str(), err.why)); + return Ok(); + }); + } case Remote: - return remote(args...); + return remote(args...)->MapErr( + NS_GetCurrentThread(), __func__, [](Error const& err) { + MOZ_LOG( + filedialog::sLogFileDialog, LogLevel::Info, + ("remote file-dialog failed: kind=%s, where=%s, why=%08" PRIX32, + Error::KindName(err.kind), err.where.c_str(), err.why)); + return Ok(); + }); case RemoteWithFallback: // more complicated; continue below @@ -337,34 +431,40 @@ static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args) return remote(args...)->Then( NS_GetCurrentThread(), kFunctionName, - [t0](typename PromiseT::ResolveValueType result) -> RefPtr<PromiseT> { + [t0](typename RPromiseT::ResolveValueType result) -> RefPtr<PromiseT> { // success; stop here auto const t1 = GetTime(); // record success telemetry::RecordSuccess({t0, t1}); - return PromiseT::CreateAndResolve(result, kFunctionName); + return PromiseT::CreateAndResolve(std::move(result), kFunctionName); }, // initialized lambda pack captures are C++20 (clang 9, gcc 9); // `make_tuple` is just a C++17 workaround [=, tuple = std::make_tuple(Copy(args)...)]( - typename PromiseT::RejectValueType err) mutable -> RefPtr<PromiseT> { + typename RPromiseT::RejectValueType err) mutable -> RefPtr<PromiseT> { // failure; record time auto const t1 = GetTime(); - HRESULT const hrRemote = err; + // TODO: also propagate `err.where` into telemetry + HRESULT const hrRemote = err.why; // retry locally... auto p0 = std::apply(local, std::move(tuple)); // ...then record the telemetry event return p0->Then( NS_GetCurrentThread(), kFunctionName, - [t0, t1, - hrRemote](typename PromiseT::ResolveOrRejectValue const& val) + [t0, t1, hrRemote](typename LPromiseT::ResolveOrRejectValue&& val) -> RefPtr<PromiseT> { auto const t2 = GetTime(); - HRESULT const hrLocal = val.IsReject() ? val.RejectValue() : S_OK; + HRESULT const hrLocal = + val.IsReject() ? (HRESULT)val.RejectValue().why : S_OK; telemetry::RecordFailure({t0, t1, t2}, hrRemote, hrLocal); - return PromiseT::CreateAndResolveOrReject(val, kFunctionName); + using V = typename PromiseT::ResolveOrRejectValue; + return PromiseT::CreateAndResolveOrReject( + val.IsResolve() + ? V::MakeResolve(std::move(val).ResolveValue()) + : V::MakeReject(Ok{}), + kFunctionName); }); }); } @@ -393,45 +493,6 @@ using fd_async::AsyncExecute; } // namespace mozilla::detail -/* static */ -nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerRemote( - HWND parent, filedialog::FileDialogType type, - nsTArray<filedialog::Command> const& commands) { - using mozilla::widget::filedialog::sLogFileDialog; - return mozilla::detail::ShowRemote( - [parent, type, - commands = commands.Clone()](filedialog::WinFileDialogParent* p) { - MOZ_LOG(sLogFileDialog, LogLevel::Info, - ("%s: p = [%p]", __PRETTY_FUNCTION__, p)); - return p->SendShowFileDialog((uintptr_t)parent, type, commands); - }); -} - -/* static */ -nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerRemote( - HWND parent, nsTArray<filedialog::Command> const& commands) { - using mozilla::widget::filedialog::sLogFileDialog; - return mozilla::detail::ShowRemote([parent, commands = commands.Clone()]( - filedialog::WinFileDialogParent* p) { - MOZ_LOG(sLogFileDialog, LogLevel::Info, - ("%s: p = [%p]", __PRETTY_FUNCTION__, p)); - return p->SendShowFolderDialog((uintptr_t)parent, commands); - }); -} - -/* static */ -nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerLocal( - HWND parent, filedialog::FileDialogType type, - nsTArray<filedialog::Command> const& commands) { - return filedialog::SpawnFilePicker(parent, type, commands.Clone()); -} - -/* static */ -nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerLocal( - HWND parent, nsTArray<filedialog::Command> const& commands) { - return filedialog::SpawnFolderPicker(parent, commands.Clone()); -} - /* * Folder picker invocation */ @@ -447,16 +508,8 @@ nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerLocal( * - resolves to false if the dialog was cancelled by the user; * - is rejected with the associated HRESULT if some error occurred. */ -RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFolderPicker( - const nsString& aInitialDir) { - using Promise = mozilla::MozPromise<bool, HRESULT, true>; - constexpr static auto Ok = [](bool val) { - return Promise::CreateAndResolve(val, "nsFilePicker::ShowFolderPicker"); - }; - constexpr static auto NotOk = [](HRESULT val = E_FAIL) { - return Promise::CreateAndReject(val, "nsFilePicker::ShowFolderPicker"); - }; - +RefPtr<mozilla::MozPromise<bool, nsFilePicker::Unit, true>> +nsFilePicker::ShowFolderPicker(const nsString& aInitialDir) { namespace fd = ::mozilla::widget::filedialog; nsTArray<fd::Command> commands = { fd::SetOptions(FOS_PICKFOLDERS), @@ -474,23 +527,18 @@ RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFolderPicker( ScopedRtlShimWindow shim(mParentWidget.get()); AutoWidgetPickerState awps(mParentWidget); - return mozilla::detail::AsyncExecute(&ShowFolderPickerLocal, - &ShowFolderPickerRemote, shim.get(), - commands) - ->Then( - NS_GetCurrentThread(), __PRETTY_FUNCTION__, - [self = RefPtr(this), shim = std::move(shim), - awps = std::move(awps)](Maybe<nsString> val) { - if (val) { - self->mUnicodeFile = val.extract(); - return Ok(true); - } - return Ok(false); - }, - [](HRESULT err) { - NS_WARNING("ShowFolderPicker failed"); - return NotOk(err); - }); + return mozilla::detail::AsyncExecute(&mozilla::detail::ShowFolderPickerLocal, + &mozilla::detail::ShowFolderPickerRemote, + shim.get(), commands) + ->Map(NS_GetCurrentThread(), __PRETTY_FUNCTION__, + [self = RefPtr(this), shim = std::move(shim), + awps = std::move(awps)](Maybe<nsString> val) { + if (val) { + self->mUnicodeFile = val.extract(); + return true; + } + return false; + }); } /* @@ -508,16 +556,16 @@ RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFolderPicker( * - resolves to false if the dialog was cancelled by the user; * - is rejected with the associated HRESULT if some error occurred. */ -RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFilePicker( - const nsString& aInitialDir) { +RefPtr<mozilla::MozPromise<bool, nsFilePicker::Unit, true>> +nsFilePicker::ShowFilePicker(const nsString& aInitialDir) { AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", OTHER); - using Promise = mozilla::MozPromise<bool, HRESULT, true>; + using Promise = mozilla::MozPromise<bool, Unit, true>; constexpr static auto Ok = [](bool val) { return Promise::CreateAndResolve(val, "nsFilePicker::ShowFilePicker"); }; - constexpr static auto NotOk = [](HRESULT val = E_FAIL) { - return Promise::CreateAndReject(val, "nsFilePicker::ShowFilePicker"); + constexpr static auto NotOk = []() { + return Promise::CreateAndReject(Unit(), "nsFilePicker::ShowFilePicker"); }; namespace fd = ::mozilla::widget::filedialog; @@ -551,7 +599,7 @@ RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFilePicker( case modeGetFolder: MOZ_ASSERT(false, "file-picker opened in directory-picker mode"); - return NotOk(E_FAIL); + return NotOk(); } commands.AppendElement(fd::SetOptions(fos)); @@ -605,7 +653,8 @@ RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFilePicker( auto type = mMode == modeSave ? FileDialogType::Save : FileDialogType::Open; auto promise = mozilla::detail::AsyncExecute( - &ShowFilePickerLocal, &ShowFilePickerRemote, shim.get(), type, commands); + &mozilla::detail::ShowFilePickerLocal, + &mozilla::detail::ShowFilePickerRemote, shim.get(), type, commands); return promise->Then( mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__, @@ -641,9 +690,9 @@ RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFilePicker( return Ok(true); }, - [](HRESULT err) { + [](Unit err) { NS_WARNING("ShowFilePicker failed"); - return NotOk(err); + return NotOk(); }); } @@ -752,43 +801,29 @@ nsFilePicker::CheckContentAnalysisService() { return promise; }; - // Since getting the files to analyze might be asynchronous, use a MozPromise - // to unify the logic below. - auto getFilesToAnalyzePromise = mozilla::MakeRefPtr<mozilla::MozPromise< - nsTArray<mozilla::PathString>, nsresult, true>::Private>(__func__); + nsCOMArray<nsIFile> files; if (mMode == modeGetFolder) { - nsCOMPtr<nsISupports> tmp; - nsresult rv = GetDomFileOrDirectory(getter_AddRefs(tmp)); + nsCOMPtr<nsIFile> file; + nsresult rv = GetFile(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { - getFilesToAnalyzePromise->Reject(rv, __func__); return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv, __func__); } - auto* directory = static_cast<mozilla::dom::Directory*>(tmp.get()); - mozilla::dom::OwningFileOrDirectory owningDirectory; - owningDirectory.SetAsDirectory() = directory; - nsTArray<mozilla::dom::OwningFileOrDirectory> directoryArray{ - std::move(owningDirectory)}; - - mozilla::ErrorResult error; - RefPtr<mozilla::dom::GetFilesHelper> helper = - mozilla::dom::GetFilesHelper::Create(directoryArray, true, error); - rv = error.StealNSResult(); + nsCOMPtr<nsIDirectoryEnumerator> iter; + rv = file->GetDirectoryEntries(getter_AddRefs(iter)); if (NS_WARN_IF(NS_FAILED(rv))) { - getFilesToAnalyzePromise->Reject(rv, __func__); return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv, __func__); } - auto getFilesCallback = mozilla::MakeRefPtr<GetFilesInDirectoryCallback>( - getFilesToAnalyzePromise); - helper->AddCallback(getFilesCallback); + nsCOMPtr<nsIFile> entry; + while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(entry))) && entry) { + files.AppendElement(entry); + } } else { - nsCOMArray<nsIFile> files; if (!mUnicodeFile.IsEmpty()) { nsCOMPtr<nsIFile> file; rv = GetFile(getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { - getFilesToAnalyzePromise->Reject(rv, __func__); return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv, __func__); } @@ -796,22 +831,13 @@ nsFilePicker::CheckContentAnalysisService() { } else { files.AppendElements(mFiles); } - nsTArray<mozilla::PathString> paths(files.Length()); - std::transform(files.begin(), files.end(), MakeBackInserter(paths), - [](auto* entry) { return entry->NativePath(); }); - getFilesToAnalyzePromise->Resolve(std::move(paths), __func__); } + nsTArray<mozilla::PathString> paths(files.Length()); + std::transform(files.begin(), files.end(), MakeBackInserter(paths), + [](auto* entry) { return entry->NativePath(); }); - return getFilesToAnalyzePromise->Then( - mozilla::GetMainThreadSerialEventTarget(), __func__, - [processOneItem](nsTArray<mozilla::PathString> aPaths) mutable { - return mozilla::detail::AsyncAll<mozilla::PathString>(std::move(aPaths), - processOneItem); - }, - [](nsresult aError) { - return nsFilePicker::ContentAnalysisResponse::CreateAndReject(aError, - __func__); - }); + return mozilla::detail::AsyncAll<mozilla::PathString>(std::move(paths), + processOneItem); }; /////////////////////////////////////////////////////////////////////////////// @@ -891,10 +917,8 @@ nsresult nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) { callback->Done(retValue); }, - [callback = RefPtr(aCallback)](HRESULT err) { - using mozilla::widget::filedialog::sLogFileDialog; - MOZ_LOG(sLogFileDialog, LogLevel::Error, - ("nsFilePicker: Show failed with hr=0x%08lX", err)); + [callback = RefPtr(aCallback)](Unit _) { + // logging already handled callback->Done(ResultCode::returnCancel); }); diff --git a/widget/windows/nsFilePicker.h b/widget/windows/nsFilePicker.h index 59108bc0dd..5a4272d30f 100644 --- a/widget/windows/nsFilePicker.h +++ b/widget/windows/nsFilePicker.h @@ -56,8 +56,6 @@ class nsFilePicker final : public nsBaseWinFilePicker { using Maybe = mozilla::Maybe<T>; template <typename T> using Result = mozilla::Result<T, HRESULT>; - template <typename Res> - using FPPromise = RefPtr<mozilla::MozPromise<Maybe<Res>, HRESULT, true>>; using Command = mozilla::widget::filedialog::Command; using Results = mozilla::widget::filedialog::Results; @@ -89,23 +87,12 @@ class nsFilePicker final : public nsBaseWinFilePicker { NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override; private: - RefPtr<mozilla::MozPromise<bool, HRESULT, true>> ShowFolderPicker( + using Unit = mozilla::Ok; + RefPtr<mozilla::MozPromise<bool, Unit, true>> ShowFolderPicker( const nsString& aInitialDir); - RefPtr<mozilla::MozPromise<bool, HRESULT, true>> ShowFilePicker( + RefPtr<mozilla::MozPromise<bool, Unit, true>> ShowFilePicker( const nsString& aInitialDir); - // Show the dialog out-of-process. - static FPPromise<Results> ShowFilePickerRemote( - HWND aParent, FileDialogType type, nsTArray<Command> const& commands); - static FPPromise<nsString> ShowFolderPickerRemote( - HWND aParent, nsTArray<Command> const& commands); - - // Show the dialog in-process. - static FPPromise<Results> ShowFilePickerLocal( - HWND aParent, FileDialogType type, nsTArray<Command> const& commands); - static FPPromise<nsString> ShowFolderPickerLocal( - HWND aParent, nsTArray<Command> const& commands); - void ClearFiles(); using ContentAnalysisResponse = mozilla::MozPromise<bool, nsresult, true>; RefPtr<ContentAnalysisResponse> CheckContentAnalysisService(); diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp index 7ef968baf6..b988fb4ec5 100644 --- a/widget/windows/nsNativeThemeWin.cpp +++ b/widget/windows/nsNativeThemeWin.cpp @@ -481,6 +481,7 @@ mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass( case StyleAppearance::Button: return Some(eUXButton); case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: return Some(eUXEdit); @@ -500,12 +501,7 @@ mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass( case StyleAppearance::Menulist: case StyleAppearance::MenulistButton: return Some(eUXCombobox); - case StyleAppearance::Treeheadercell: - return Some(eUXHeader); case StyleAppearance::Listbox: - case StyleAppearance::Treeview: - case StyleAppearance::Treetwistyopen: - case StyleAppearance::Treeitem: return Some(eUXListview); default: return Nothing(); @@ -594,6 +590,7 @@ nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, return NS_OK; } case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: { ElementState elementState = GetContentState(aFrame, aAppearance); @@ -716,7 +713,6 @@ nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, } return NS_OK; } - case StyleAppearance::Treeview: case StyleAppearance::Listbox: { aPart = TREEVIEW_BODY; aState = TS_NORMAL; @@ -753,17 +749,6 @@ nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame, return NS_OK; } - case StyleAppearance::Treeheadercell: { - aPart = 1; - if (!aFrame) { - aState = TS_NORMAL; - return NS_OK; - } - - aState = StandardGetState(aFrame, aAppearance, true); - - return NS_OK; - } case StyleAppearance::MenulistButton: case StyleAppearance::Menulist: { nsIContent* content = aFrame->GetContent(); @@ -956,6 +941,7 @@ RENDER_AGAIN: DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect); } else if (aAppearance == StyleAppearance::NumberInput || + aAppearance == StyleAppearance::PasswordInput || aAppearance == StyleAppearance::Textfield || aAppearance == StyleAppearance::Textarea) { DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect); @@ -1088,6 +1074,7 @@ LayoutDeviceIntMargin nsNativeThemeWin::GetWidgetBorder( } if (aFrame && (aAppearance == StyleAppearance::NumberInput || + aAppearance == StyleAppearance::PasswordInput || aAppearance == StyleAppearance::Textfield || aAppearance == StyleAppearance::Textarea)) { nsIContent* content = aFrame->GetContent(); @@ -1128,6 +1115,7 @@ bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext, * added, see bug 430212) */ if (aAppearance == StyleAppearance::NumberInput || + aAppearance == StyleAppearance::PasswordInput || aAppearance == StyleAppearance::Textfield || aAppearance == StyleAppearance::Textarea) { aResult->top = aResult->bottom = 2; @@ -1236,12 +1224,12 @@ LayoutDeviceIntSize nsNativeThemeWin::GetMinimumWidgetSize( switch (aAppearance) { case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Progresschunk: case StyleAppearance::Tabpanels: case StyleAppearance::Tabpanel: case StyleAppearance::Listbox: - case StyleAppearance::Treeview: return {}; // Don't worry about it. default: break; @@ -1386,6 +1374,7 @@ bool nsNativeThemeWin::ThemeDrawsFocusForWidget(nsIFrame* aFrame, case StyleAppearance::Textarea: case StyleAppearance::Textfield: case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: return true; default: return false; @@ -1438,6 +1427,7 @@ bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame, switch (aAppearance) { case StyleAppearance::Button: case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: case StyleAppearance::Range: @@ -1445,7 +1435,6 @@ bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame, case StyleAppearance::Menulist: case StyleAppearance::MenulistButton: case StyleAppearance::Listbox: - case StyleAppearance::Treeview: case StyleAppearance::ProgressBar: case StyleAppearance::Progresschunk: case StyleAppearance::Tab: @@ -1465,11 +1454,11 @@ LayoutDeviceIntMargin nsNativeThemeWin::ClassicGetWidgetBorder( result.top = result.left = result.bottom = result.right = 2; break; case StyleAppearance::Listbox: - case StyleAppearance::Treeview: case StyleAppearance::Menulist: case StyleAppearance::MenulistButton: case StyleAppearance::Tab: case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: result.top = result.left = result.bottom = result.right = 2; @@ -1516,8 +1505,8 @@ LayoutDeviceIntSize nsNativeThemeWin::ClassicGetMinimumWidgetSize( case StyleAppearance::MenulistButton: case StyleAppearance::Button: case StyleAppearance::Listbox: - case StyleAppearance::Treeview: case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: case StyleAppearance::Progresschunk: @@ -1574,8 +1563,8 @@ nsresult nsNativeThemeWin::ClassicGetThemePartAndState( return NS_OK; } case StyleAppearance::Listbox: - case StyleAppearance::Treeview: case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: case StyleAppearance::Menulist: @@ -1767,6 +1756,7 @@ RENDER_AGAIN: } // Draw controls with 2px 3D inset border case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: case StyleAppearance::Listbox: @@ -1787,15 +1777,6 @@ RENDER_AGAIN: break; } - case StyleAppearance::Treeview: { - // Draw inset edge - ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); - - // Fill in window color background - ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1)); - - break; - } // Draw 3D face background controls case StyleAppearance::ProgressBar: // Draw 3D border @@ -1903,6 +1884,7 @@ uint32_t nsNativeThemeWin::GetWidgetNativeDrawingFlags( switch (aAppearance) { case StyleAppearance::Button: case StyleAppearance::NumberInput: + case StyleAppearance::PasswordInput: case StyleAppearance::Textfield: case StyleAppearance::Textarea: case StyleAppearance::Menulist: diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp index ce8b0f3479..88546b01c6 100644 --- a/widget/windows/nsUXThemeData.cpp +++ b/widget/windows/nsUXThemeData.cpp @@ -72,8 +72,6 @@ const wchar_t* nsUXThemeData::GetClassName(nsUXThemeClass cls) { return L"Trackbar"; case eUXCombobox: return L"Combobox"; - case eUXHeader: - return L"Header"; case eUXListview: return L"Listview"; case eUXMenu: diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h index 24fe07d128..b59c2aae04 100644 --- a/widget/windows/nsUXThemeData.h +++ b/widget/windows/nsUXThemeData.h @@ -24,7 +24,6 @@ enum nsUXThemeClass { eUXTab, eUXTrackbar, eUXCombobox, - eUXHeader, eUXListview, eUXMenu, eUXNumClasses diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index 22d20d099e..ac393e8b09 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -166,7 +166,6 @@ #include "mozilla/StaticPrefs_ui.h" #include "mozilla/StaticPrefs_widget.h" #include "nsNativeAppSupportWin.h" -#include "mozilla/browser/NimbusFeatures.h" #include "nsIGfxInfo.h" #include "nsUXThemeConstants.h" @@ -188,7 +187,6 @@ # include "mozilla/a11y/DocAccessible.h" # include "mozilla/a11y/LazyInstantiator.h" # include "mozilla/a11y/Platform.h" -# include "mozilla/StaticPrefs_accessibility.h" # if !defined(WINABLEAPI) # include <winable.h> # endif // !defined(WINABLEAPI) @@ -348,6 +346,12 @@ static SystemTimeConverter<DWORD>& TimeConverter() { return timeConverterSingleton; } +static const wchar_t* GetMainWindowClass(); +static const wchar_t* ChooseWindowClass(mozilla::widget::WindowType); +// This method registers the given window class, and returns the class name. +static void RegisterWindowClass(const wchar_t* aClassName, UINT aExtraStyle, + LPWSTR aIconID); + // Global event hook for window cloaking. Never deregistered. // - `Nothing` if not yet set. // - `Some(nullptr)` if no attempt should be made to set it. @@ -990,13 +994,11 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, } if (aInitData->mIsPrivate) { - if (NimbusFeatures::GetBool("majorRelease2022"_ns, - "feltPrivacyWindowSeparation"_ns, true) && - // Although permanent Private Browsing mode is indeed Private Browsing, - // we choose to make it look like regular Firefox in terms of the icon - // it uses (which also means we shouldn't use the Private Browsing - // AUMID). - !StaticPrefs::browser_privatebrowsing_autostart()) { + // Although permanent Private Browsing mode is indeed Private Browsing, + // we choose to make it look like regular Firefox in terms of the icon + // it uses (which also means we shouldn't use the Private Browsing + // AUMID). + if (!StaticPrefs::browser_privatebrowsing_autostart()) { RefPtr<IPropertyStore> pPropStore; if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore, getter_AddRefs(pPropStore)))) { @@ -1189,40 +1191,28 @@ void nsWindow::Destroy() { * **************************************************************/ -/* static */ -const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName, - UINT aExtraStyle, LPWSTR aIconID) { - WNDCLASSW wc; +static void RegisterWindowClass(const wchar_t* aClassName, UINT aExtraStyle, + LPWSTR aIconID) { + WNDCLASSW wc = {}; if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) { // already registered - return aClassName; + return; } wc.style = CS_DBLCLKS | aExtraStyle; wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; wc.hInstance = nsToolkit::mDllInstance; wc.hIcon = aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr; - wc.hCursor = nullptr; - wc.hbrBackground = nullptr; - wc.lpszMenuName = nullptr; wc.lpszClassName = aClassName; - if (!::RegisterClassW(&wc)) { - // For older versions of Win32 (i.e., not XP), the registration may - // fail with aExtraStyle, so we have to re-register without it. - wc.style = CS_DBLCLKS; - ::RegisterClassW(&wc); - } - return aClassName; + // Failures are ignored as they are handled when ::CreateWindow fails + ::RegisterClassW(&wc); } static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512); -/* static */ -const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType) { +static const wchar_t* ChooseWindowClass(WindowType aWindowType) { const wchar_t* className = [aWindowType] { switch (aWindowType) { case WindowType::Invisible: @@ -1235,7 +1225,8 @@ const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType) { return GetMainWindowClass(); } }(); - return RegisterWindowClass(className, 0, gStockApplicationIcon); + RegisterWindowClass(className, 0, gStockApplicationIcon); + return className; } /************************************************************** @@ -1246,24 +1237,62 @@ const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType) { * **************************************************************/ +const DWORD kTitlebarItemsWindowStyles = + WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; +const DWORD kAllBorderStyles = + kTitlebarItemsWindowStyles | WS_THICKFRAME | WS_DLGFRAME; + +static DWORD WindowStylesRemovedForBorderStyle(BorderStyle aStyle) { + if (aStyle == BorderStyle::Default || aStyle == BorderStyle::All) { + return 0; + } + if (aStyle == BorderStyle::None) { + return kAllBorderStyles; + } + DWORD toRemove = 0; + if (!(aStyle & BorderStyle::Border)) { + toRemove |= WS_BORDER; + } + if (!(aStyle & BorderStyle::Title)) { + toRemove |= WS_DLGFRAME; + } + if (!(aStyle & (BorderStyle::Menu | BorderStyle::Close))) { + // Looks like getting rid of the system menu also does away with the close + // box. So, we only get rid of the system menu and the close box if you + // want neither. How does the Windows "Dialog" window class get just + // closebox and no sysmenu? Who knows. + toRemove |= WS_SYSMENU; + } + if (!(aStyle & BorderStyle::ResizeH)) { + toRemove |= WS_THICKFRAME; + } + if (!(aStyle & BorderStyle::Minimize)) { + toRemove |= WS_MINIMIZEBOX; + } + if (!(aStyle & BorderStyle::Maximize)) { + toRemove |= WS_MAXIMIZEBOX; + } + return toRemove; +} + // Return nsWindow styles DWORD nsWindow::WindowStyle() { DWORD style; - switch (mWindowType) { case WindowType::Child: style = WS_OVERLAPPED; break; case WindowType::Dialog: - style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK | + style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_MODALFRAME | WS_CLIPCHILDREN; - if (mBorderStyle != BorderStyle::Default) + if (mBorderStyle != BorderStyle::Default) { style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; + } break; case WindowType::Popup: - style = WS_POPUP | WS_OVERLAPPED; + style = WS_OVERLAPPED | WS_POPUP; break; default: @@ -1272,48 +1301,12 @@ DWORD nsWindow::WindowStyle() { case WindowType::TopLevel: case WindowType::Invisible: - style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | - WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN; + style = WS_OVERLAPPED | WS_CLIPCHILDREN | WS_DLGFRAME | WS_BORDER | + WS_THICKFRAME | kTitlebarItemsWindowStyles; break; } - if (mBorderStyle != BorderStyle::Default && - mBorderStyle != BorderStyle::All) { - if (mBorderStyle == BorderStyle::None || - !(mBorderStyle & BorderStyle::Border)) - style &= ~WS_BORDER; - - if (mBorderStyle == BorderStyle::None || - !(mBorderStyle & BorderStyle::Title)) { - style &= ~WS_DLGFRAME; - } - - if (mBorderStyle == BorderStyle::None || - !(mBorderStyle & BorderStyle::Close)) - style &= ~0; - // XXX The close box can only be removed by changing the window class, - // as far as I know --- roc+moz@cs.cmu.edu - - if (mBorderStyle == BorderStyle::None || - !(mBorderStyle & (BorderStyle::Menu | BorderStyle::Close))) - style &= ~WS_SYSMENU; - // Looks like getting rid of the system menu also does away with the - // close box. So, we only get rid of the system menu if you want neither it - // nor the close box. How does the Windows "Dialog" window class get just - // closebox and no sysmenu? Who knows. - - if (mBorderStyle == BorderStyle::None || - !(mBorderStyle & BorderStyle::ResizeH)) - style &= ~WS_THICKFRAME; - - if (mBorderStyle == BorderStyle::None || - !(mBorderStyle & BorderStyle::Minimize)) - style &= ~WS_MINIMIZEBOX; - - if (mBorderStyle == BorderStyle::None || - !(mBorderStyle & BorderStyle::Maximize)) - style &= ~WS_MAXIMIZEBOX; - } + style &= ~WindowStylesRemovedForBorderStyle(mBorderStyle); if (mIsChildWindow) { style |= WS_CLIPCHILDREN; @@ -2471,46 +2464,6 @@ void nsWindow::ResetLayout() { Invalidate(); } -// Internally track the caption status via a window property. Required -// due to our internal handling of WM_NCACTIVATE when custom client -// margins are set. -static const wchar_t kManageWindowInfoProperty[] = L"ManageWindowInfoProperty"; -typedef BOOL(WINAPI* GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi); -static WindowsDllInterceptor::FuncHookType<GetWindowInfoPtr> - sGetWindowInfoPtrStub; - -BOOL WINAPI GetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) { - if (!sGetWindowInfoPtrStub) { - NS_ASSERTION(FALSE, "Something is horribly wrong in GetWindowInfoHook!"); - return FALSE; - } - int windowStatus = - reinterpret_cast<LONG_PTR>(GetPropW(hWnd, kManageWindowInfoProperty)); - // No property set, return the default data. - if (!windowStatus) return sGetWindowInfoPtrStub(hWnd, pwi); - // Call GetWindowInfo and update dwWindowStatus with our - // internally tracked value. - BOOL result = sGetWindowInfoPtrStub(hWnd, pwi); - if (result && pwi) - pwi->dwWindowStatus = (windowStatus == 1 ? 0 : WS_ACTIVECAPTION); - return result; -} - -void nsWindow::UpdateGetWindowInfoCaptionStatus(bool aActiveCaption) { - if (!mWnd) return; - - sUser32Intercept.Init("user32.dll"); - sGetWindowInfoPtrStub.Set(sUser32Intercept, "GetWindowInfo", - &GetWindowInfoHook); - if (!sGetWindowInfoPtrStub) { - return; - } - - // Update our internally tracked caption status - SetPropW(mWnd, kManageWindowInfoProperty, - reinterpret_cast<HANDLE>(static_cast<INT_PTR>(aActiveCaption) + 1)); -} - #define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19 #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 @@ -2703,6 +2656,8 @@ bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) { mNonClientOffset = NormalWindowNonClientOffset(); } + UpdateOpaqueRegionInternal(); + if (aReflowWindow) { // Force a reflow of content based on the new client // dimensions. @@ -2713,8 +2668,9 @@ bool nsWindow::UpdateNonClientMargins(bool aReflowWindow) { } nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& margins) { - if (!mIsTopWidgetWindow || mBorderStyle == BorderStyle::None) + if (!mIsTopWidgetWindow || mBorderStyle == BorderStyle::None) { return NS_ERROR_INVALID_ARG; + } if (mHideChrome) { mFutureMarginsOnceChromeShows = margins; @@ -2731,19 +2687,13 @@ nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& margins) { // Force a reflow of content based on the new client // dimensions. ResetLayout(); - - int windowStatus = - reinterpret_cast<LONG_PTR>(GetPropW(mWnd, kManageWindowInfoProperty)); - if (windowStatus) { - ::SendMessageW(mWnd, WM_NCACTIVATE, 1 != windowStatus, 0); - } - return NS_OK; } if (margins.top < -1 || margins.bottom < -1 || margins.left < -1 || - margins.right < -1) + margins.right < -1) { return NS_ERROR_INVALID_ARG; + } mNonClientMargins = margins; mCustomNonClient = true; @@ -3999,10 +3949,8 @@ uint32_t nsWindow::GetMaxTouchPoints() const { return WinUtils::GetMaxTouchPoints(); } -void nsWindow::SetWindowClass(const nsAString& xulWinType, - const nsAString& xulWinClass, - const nsAString& xulWinName) { - mIsEarlyBlankWindow = xulWinType.EqualsLiteral("navigator:blank"); +void nsWindow::SetIsEarlyBlankWindow(bool aIsEarlyBlankWindow) { + mIsEarlyBlankWindow = aIsEarlyBlankWindow; } /************************************************************** @@ -5194,8 +5142,6 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, * WM_NCACTIVATE paints nc areas. Avoid this and re-route painting * through WM_NCPAINT via InvalidateNonClientRegion. */ - UpdateGetWindowInfoCaptionStatus(FALSE != wParam); - if (!mCustomNonClient) { break; } @@ -5684,9 +5630,6 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, if (WinUtils::LogToPhysFactor(mWnd) != mDefaultScale) { ChangedDPI(); ResetLayout(); - if (mWidgetListener) { - mWidgetListener->UIResolutionChanged(); - } } } break; @@ -5724,9 +5667,6 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, case WM_DISPLAYCHANGE: { ScreenHelperWin::RefreshScreens(); - if (mWidgetListener) { - mWidgetListener->UIResolutionChanged(); - } break; } @@ -5925,19 +5865,12 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam, a11y::LazyInstantiator::EnableBlindAggregation(mWnd); result = true; } - } else if (objId == UiaRootObjectId && - StaticPrefs::accessibility_uia_enable()) { - if (a11y::LocalAccessible* acc = GetAccessible()) { - RefPtr<IAccessible> ia; - acc->GetNativeInterface(getter_AddRefs(ia)); - MOZ_ASSERT(ia); - RefPtr<IRawElementProviderSimple> uia; - ia->QueryInterface(IID_IRawElementProviderSimple, - getter_AddRefs(uia)); - if (uia) { - *aRetValue = UiaReturnRawElementProvider(mWnd, wParam, lParam, uia); - result = true; - } + } else if (objId == UiaRootObjectId) { + if (RefPtr<IRawElementProviderSimple> root = + a11y::LazyInstantiator::GetRootUia(mWnd)) { + *aRetValue = UiaReturnRawElementProvider(mWnd, wParam, lParam, root); + a11y::LazyInstantiator::EnableBlindAggregation(mWnd); + result = true; } } } break; @@ -7420,7 +7353,9 @@ a11y::LocalAccessible* nsWindow::GetAccessible() { **************************************************************/ void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) { - if (aMode == mTransparencyMode) return; + if (aMode == mTransparencyMode) { + return; + } // stop on dialogs and popups! HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true); @@ -7458,10 +7393,11 @@ void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) { } } - if (aMode == TransparencyMode::Transparent) + if (aMode == TransparencyMode::Transparent) { exStyle |= WS_EX_LAYERED; - else + } else { exStyle &= ~WS_EX_LAYERED; + } VERIFY_WINDOW_STYLE(style); ::SetWindowLongPtrW(hWnd, GWL_STYLE, style); @@ -7469,11 +7405,47 @@ void nsWindow::SetWindowTranslucencyInner(TransparencyMode aMode) { mTransparencyMode = aMode; + UpdateOpaqueRegionInternal(); + if (mCompositorWidgetDelegate) { mCompositorWidgetDelegate->UpdateTransparency(aMode); } } +void nsWindow::UpdateOpaqueRegion(const LayoutDeviceIntRegion& aRegion) { + if (aRegion == mOpaqueRegion) { + return; + } + mOpaqueRegion = aRegion; + UpdateOpaqueRegionInternal(); +} + +void nsWindow::UpdateOpaqueRegionInternal() { + MARGINS margins{0}; + if (mTransparencyMode == TransparencyMode::Transparent) { + // If there is no opaque region, set margins to support a full sheet of + // glass. Comments in MSDN indicate all values must be set to -1 to get a + // full sheet of glass. + margins = {-1, -1, -1, -1}; + if (!mOpaqueRegion.IsEmpty()) { + LayoutDeviceIntRect clientBounds = GetClientBounds(); + // Find the largest rectangle and use that to calculate the inset. + LayoutDeviceIntRect largest = mOpaqueRegion.GetLargestRectangle(); + margins.cxLeftWidth = largest.X(); + margins.cxRightWidth = clientBounds.Width() - largest.XMost(); + margins.cyBottomHeight = clientBounds.Height() - largest.YMost(); + margins.cyTopHeight = largest.Y(); + + auto ncmargin = NonClientSizeMargin(); + margins.cxLeftWidth += ncmargin.left; + margins.cyTopHeight += ncmargin.top; + margins.cxRightWidth += ncmargin.right; + margins.cyBottomHeight += ncmargin.bottom; + } + } + DwmExtendFrameIntoClientArea(mWnd, &margins); +} + /************************************************************** ************************************************************** ** @@ -8200,7 +8172,7 @@ bool nsWindow::CanTakeFocus() { return false; } -/* static */ const wchar_t* nsWindow::GetMainWindowClass() { +static const wchar_t* GetMainWindowClass() { static const wchar_t* sMainWindowClass = nullptr; if (!sMainWindowClass) { nsAutoString className; diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h index a2b2a701bd..9c4b4417f8 100644 --- a/widget/windows/nsWindow.h +++ b/widget/windows/nsWindow.h @@ -261,8 +261,7 @@ class nsWindow final : public nsBaseWidget { const LayoutDeviceIntRegion& aRegion) override; uint32_t GetMaxTouchPoints() const override; - void SetWindowClass(const nsAString& xulWinType, const nsAString& xulWinClass, - const nsAString& xulWinName) override; + void SetIsEarlyBlankWindow(bool) override; /** * Event helpers @@ -518,10 +517,8 @@ class nsWindow final : public nsBaseWidget { bool CanTakeFocus(); bool UpdateNonClientMargins(bool aReflowWindow = true); void UpdateDarkModeToolbar(); - void UpdateGetWindowInfoCaptionStatus(bool aActiveCaption); void ResetLayout(); void InvalidateNonClientRegion(); - static const wchar_t* GetMainWindowClass(); HWND GetOwnerWnd() const { return ::GetWindow(mWnd, GW_OWNER); } bool IsOwnerForegroundWindow() const { HWND owner = GetOwnerWnd(); @@ -592,11 +589,6 @@ class nsWindow final : public nsBaseWidget { DWORD WindowStyle(); DWORD WindowExStyle(); - static const wchar_t* ChooseWindowClass(WindowType); - // This method registers the given window class, and returns the class name. - static const wchar_t* RegisterWindowClass(const wchar_t* aClassName, - UINT aExtraStyle, LPWSTR aIconID); - /** * Popup hooks */ @@ -620,6 +612,9 @@ class nsWindow final : public nsBaseWidget { bool IsSimulatedClientArea(int32_t clientX, int32_t clientY); bool IsWindowButton(int32_t hitTestResult); + void UpdateOpaqueRegion(const LayoutDeviceIntRegion&) override; + void UpdateOpaqueRegionInternal(); + bool DispatchTouchEventFromWMPointer(UINT msg, LPARAM aLParam, const WinPointerInfo& aPointerInfo, mozilla::MouseButton aButton); @@ -808,6 +803,8 @@ class nsWindow final : public nsBaseWidget { // Draggable titlebar region maintained by UpdateWindowDraggingRegion LayoutDeviceIntRegion mDraggableRegion; + // Opaque region maintained by UpdateOpaqueRegion + LayoutDeviceIntRegion mOpaqueRegion; // Graphics LayoutDeviceIntRect mLastPaintBounds; @@ -816,7 +813,6 @@ class nsWindow final : public nsBaseWidget { // Transparency TransparencyMode mTransparencyMode = TransparencyMode::Opaque; - nsIntRegion mPossiblyTransparentRegion; // Win7 Gesture processing and management nsWinGesture mGesture; diff --git a/widget/windows/tests/gtest/TestJumpListBuilder.cpp b/widget/windows/tests/gtest/TestJumpListBuilder.cpp index 5494c42d37..531e326465 100644 --- a/widget/windows/tests/gtest/TestJumpListBuilder.cpp +++ b/widget/windows/tests/gtest/TestJumpListBuilder.cpp @@ -275,14 +275,14 @@ class TestingJumpListBackend : public JumpListBackend { * @param {nsTArray<WindowsJumpListShortcutDescription>&} aArray * The outparam for the array of generated * WindowsJumpListShortcutDescriptions. - * @param {nsTArray<JS::Value>&} aJSValArray + * @param {JS::Handle<JSObject*>} aJSArrayObj * The outparam for the array of JS::Value's representing the generated * WindowsJumpListShortcutDescriptions. */ void GenerateWindowsJumpListShortcutDescriptions( JSContext* aCx, uint32_t howMany, bool longDescription, nsTArray<WindowsJumpListShortcutDescription>& aArray, - nsTArray<JS::Value>& aJSValArray) { + JS::Handle<JSObject*> aJSArrayObj) { for (uint32_t i = 0; i < howMany; ++i) { WindowsJumpListShortcutDescription desc; nsAutoString title(u"Test Task #"); @@ -321,7 +321,8 @@ void GenerateWindowsJumpListShortcutDescriptions( aArray.AppendElement(desc); JS::Rooted<JS::Value> descJSValue(aCx); ASSERT_TRUE(ToJSValue(aCx, desc, &descJSValue)); - aJSValArray.AppendElement(std::move(descJSValue)); + + MOZ_ALWAYS_TRUE(JS_SetElement(aCx, aJSArrayObj, i, descJSValue)); } } @@ -393,15 +394,15 @@ TEST(JumpListBuilder, CheckForRemovals) EXPECT_CALL(*testBackend, BeginList) .WillOnce([](UINT* pcMinSlots, REFIID riid, void** ppv) { RefPtr<IObjectCollection> collection; - DebugOnly<HRESULT> hr = CoCreateInstance( + HRESULT hr = CoCreateInstance( CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, IID_IObjectCollection, getter_AddRefs(collection)); - MOZ_ASSERT(SUCCEEDED(hr)); + MOZ_RELEASE_ASSERT(SUCCEEDED(hr)); RefPtr<IShellLinkW> link; hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW, getter_AddRefs(link)); - MOZ_ASSERT(SUCCEEDED(hr)); + MOZ_RELEASE_ASSERT(SUCCEEDED(hr)); nsAutoString firstLinkHref(u"https://example.com"_ns); link->SetArguments(firstLinkHref.get()); @@ -421,7 +422,7 @@ TEST(JumpListBuilder, CheckForRemovals) RefPtr<IObjectArray> pArray; hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray)); - MOZ_ASSERT(SUCCEEDED(hr)); + MOZ_RELEASE_ASSERT(SUCCEEDED(hr)); *ppv = static_cast<IObjectArray*>(pArray); (static_cast<IUnknown*>(*ppv))->AddRef(); @@ -478,6 +479,115 @@ TEST(JumpListBuilder, CheckForRemovals) } /** + * Tests calling CheckForRemovals and receiving a jump list entry with a very + * long URL doesn't result in JumpListBuilder truncating the URL before handing + * it back to the caller. Expects the following calls in order: + * + * - SetAppID + * - AbortList + * - BeginList + * - AbortList + */ +TEST(JumpListBuilder, CheckForRemovalsLongURL) +{ + RefPtr<StrictMock<TestingJumpListBackend>> testBackend = + new StrictMock<TestingJumpListBackend>(); + nsAutoString aumid(u"TestApplicationID"); + // We set up this expectation here because SetAppID will be called soon + // after construction of the JumpListBuilder via the background thread. + EXPECT_CALL(*testBackend, SetAppID(_)).Times(1); + + nsCOMPtr<nsIJumpListBuilder> builder = + new JumpListBuilder(aumid, testBackend); + ASSERT_TRUE(builder); + + EXPECT_CALL(*testBackend, AbortList()).Times(2); + + constexpr static const nsLiteralString veryLongHref( + u"https://example.verylongurl.test/first/second/third/fourth/fifth/" + "sixth/seventh/eighth/ninth/tenth/eleventh/twelfth/thirteenth/" + "fourteenth/fifteenth-path-item/some/more/junk/after/that/more/more/" + "more/more/more/more/more/more/more/more/more/more/more/more/more/more/" + "more/more/more/more/more/more/more/more/more/more/more"_ns); + // This test ensures that URLs longer than MAX_PATH do not get truncated by + // JumpListBuilder or one of its utilities, so we must ensure that the static + // URL we just defined is actually longer than MAX_PATH. + static_assert(veryLongHref.Length() > MAX_PATH); + + // Let's prepare BeginList to return a one entry collection of IShellLinks. + // The IShellLink will have the URL be very long - over MAX_PATH characters. + EXPECT_CALL(*testBackend, BeginList) + .WillOnce([](UINT* pcMinSlots, REFIID riid, void** ppv) { + RefPtr<IObjectCollection> collection; + HRESULT hr = CoCreateInstance( + CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, + IID_IObjectCollection, getter_AddRefs(collection)); + MOZ_RELEASE_ASSERT(SUCCEEDED(hr)); + + RefPtr<IShellLinkW> link; + hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, getter_AddRefs(link)); + MOZ_RELEASE_ASSERT(SUCCEEDED(hr)); + + link->SetArguments(veryLongHref.get()); + + nsAutoString appPath(u"C:\\Tmp\\firefox.exe"_ns); + link->SetIconLocation(appPath.get(), 0); + + collection->AddObject(link); + + RefPtr<IObjectArray> pArray; + hr = collection->QueryInterface(IID_IObjectArray, + getter_AddRefs(pArray)); + MOZ_RELEASE_ASSERT(SUCCEEDED(hr)); + + *ppv = static_cast<IObjectArray*>(pArray); + (static_cast<IUnknown*>(*ppv))->AddRef(); + + // This is the default value to return, according to + // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icustomdestinationlist-beginlist + *pcMinSlots = 10; + + return S_OK; + }); + + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsapi.cx(); + RefPtr<Promise> promise; + nsresult rv = builder->CheckForRemovals(cx, getter_AddRefs(promise)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_TRUE(promise); + + RefPtr<WaitForResolver> resolver = new WaitForResolver(); + promise->AppendNativeHandler(resolver); + JS::Rooted<JS::Value> result(cx); + resolver->SpinUntilResolvedWithResult(&result); + + ASSERT_TRUE(result.isObject()); + JS::Rooted<JSObject*> obj(cx, result.toObjectOrNull()); + + bool isArray; + ASSERT_TRUE(JS::IsArrayObject(cx, obj, &isArray)); + ASSERT_TRUE(isArray); + + // We should expect to see 1 URL string returned in the array. + uint32_t length = 0; + ASSERT_TRUE(JS::GetArrayLength(cx, obj, &length)); + ASSERT_EQ(length, 1U); + + // The URL should match veryLongHref + JS::Rooted<JS::Value> returnedURLValue(cx); + ASSERT_TRUE(JS_GetElement(cx, obj, 0, &returnedURLValue)); + JS::Rooted<JSString*> returnedURLValueJSString(cx, + returnedURLValue.toString()); + nsAutoJSString returnedURLValueAutoString; + ASSERT_TRUE(returnedURLValueAutoString.init(cx, returnedURLValueJSString)); + + ASSERT_TRUE(returnedURLValueAutoString.Equals(veryLongHref)); +} + +/** * Tests calling PopulateJumpList with empty arguments, which should call the * following methods on the backend, in order: * @@ -506,9 +616,13 @@ TEST(JumpListBuilder, PopulateJumpListEmpty) JSContext* cx = jsapi.cx(); RefPtr<Promise> promise; - nsTArray<JS::Value> taskDescJSVals; + JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj)); + nsAutoString customTitle(u""); - nsTArray<JS::Value> customDescJSVals; + + JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj)); EXPECT_CALL(*testBackend, AbortList()).Times(1); EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1); @@ -516,7 +630,7 @@ TEST(JumpListBuilder, PopulateJumpListEmpty) EXPECT_CALL(*testBackend, DeleteList(_)).Times(0); nsresult rv = - builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals, + builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal, cx, getter_AddRefs(promise)); ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_TRUE(promise); @@ -558,13 +672,15 @@ TEST(JumpListBuilder, PopulateJumpListOnlyTasks) JSContext* cx = jsapi.cx(); RefPtr<Promise> promise; - nsTArray<JS::Value> taskDescJSVals; + JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj)); nsTArray<WindowsJumpListShortcutDescription> taskDescs; GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs, - taskDescJSVals); + taskDescsObj); nsAutoString customTitle(u""); - nsTArray<JS::Value> customDescJSVals; + JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj)); EXPECT_CALL(*testBackend, AbortList()).Times(1); EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1); @@ -575,7 +691,7 @@ TEST(JumpListBuilder, PopulateJumpListOnlyTasks) EXPECT_CALL(*testBackend, DeleteList(_)).Times(0); nsresult rv = - builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals, + builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal, cx, getter_AddRefs(promise)); ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_TRUE(promise); @@ -617,13 +733,17 @@ TEST(JumpListBuilder, PopulateJumpListOnlyCustomItems) JSContext* cx = jsapi.cx(); RefPtr<Promise> promise; - nsTArray<WindowsJumpListShortcutDescription> descs; - nsTArray<JS::Value> customDescJSVals; - GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, descs, - customDescJSVals); + JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> taskDescsJSVal(cx); + taskDescsJSVal.setObject(*taskDescsObj); nsAutoString customTitle(u"My custom title"); - nsTArray<JS::Value> taskDescJSVals; + + JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj)); + nsTArray<WindowsJumpListShortcutDescription> descs; + GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, descs, + customDescsObj); EXPECT_CALL(*testBackend, AbortList()).Times(1); EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1); @@ -636,7 +756,7 @@ TEST(JumpListBuilder, PopulateJumpListOnlyCustomItems) EXPECT_CALL(*testBackend, DeleteList(_)).Times(0); nsresult rv = - builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals, + builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal, cx, getter_AddRefs(promise)); ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_TRUE(promise); @@ -679,30 +799,102 @@ TEST(JumpListBuilder, PopulateJumpList) JSContext* cx = jsapi.cx(); RefPtr<Promise> promise; + JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj)); nsTArray<WindowsJumpListShortcutDescription> taskDescs; - nsTArray<JS::Value> taskDescJSVals; GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs, - taskDescJSVals); + taskDescsObj); + + nsAutoString customTitle(u"My custom title"); + JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj)); nsTArray<WindowsJumpListShortcutDescription> customDescs; - nsTArray<JS::Value> customDescJSVals; GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, customDescs, - customDescJSVals); + customDescsObj); + + EXPECT_CALL(*testBackend, AbortList()).Times(1); + EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1); + EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1); + + EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()), + ShellLinksEq(&customDescs))) + .Times(1); + EXPECT_CALL(*testBackend, CommitList()).Times(1); + EXPECT_CALL(*testBackend, DeleteList(_)).Times(0); + + nsresult rv = + builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal, + cx, getter_AddRefs(promise)); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_TRUE(promise); + + RefPtr<WaitForResolver> resolver = new WaitForResolver(); + promise->AppendNativeHandler(resolver); + JS::Rooted<JS::Value> result(cx); + resolver->SpinUntilResolved(); +} + +/** + * Tests calling PopulateJumpList with tasks and custom items, but makes it so + * that AppendCategory returns E_ACCESSDENIED, which can occur if Windows is + * configured to not show recently opened items. The PopulateJumpList Promise + * should still resolve. + * + * - SetAppID + * - AbortList + * - BeginList + * - AddUserTasks + * - AppendCategory + * - CommitList + * + * This should result in a jump list with just built-in tasks. + */ +TEST(JumpListBuilder, PopulateJumpListNoOpenedItems) +{ + RefPtr<StrictMock<TestingJumpListBackend>> testBackend = + new StrictMock<TestingJumpListBackend>(); + nsAutoString aumid(u"TestApplicationID"); + // We set up this expectation here because SetAppID will be called soon + // after construction of the JumpListBuilder via the background thread. + EXPECT_CALL(*testBackend, SetAppID(_)).Times(1); + + nsCOMPtr<nsIJumpListBuilder> builder = + new JumpListBuilder(aumid, testBackend); + ASSERT_TRUE(builder); + + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsapi.cx(); + RefPtr<Promise> promise; + + JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj)); + nsTArray<WindowsJumpListShortcutDescription> taskDescs; + GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, taskDescs, + taskDescsObj); nsAutoString customTitle(u"My custom title"); + JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj)); + nsTArray<WindowsJumpListShortcutDescription> customDescs; + GenerateWindowsJumpListShortcutDescriptions(cx, 2, false, customDescs, + customDescsObj); + EXPECT_CALL(*testBackend, AbortList()).Times(1); EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1); EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1); EXPECT_CALL(*testBackend, AppendCategory(LPCWSTREq(customTitle.get()), ShellLinksEq(&customDescs))) - .Times(1); + .WillOnce([] { return E_ACCESSDENIED; }); + EXPECT_CALL(*testBackend, CommitList()).Times(1); EXPECT_CALL(*testBackend, DeleteList(_)).Times(0); nsresult rv = - builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals, + builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal, cx, getter_AddRefs(promise)); ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_TRUE(promise); @@ -780,15 +972,20 @@ TEST(JumpListBuilder, TruncateDescription) JSContext* cx = jsapi.cx(); RefPtr<Promise> promise; + JS::Rooted<JSObject*> taskDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> taskDescsJSVal(cx, JS::ObjectValue(*taskDescsObj)); nsTArray<WindowsJumpListShortcutDescription> taskDescs; - nsTArray<JS::Value> taskDescJSVals; GenerateWindowsJumpListShortcutDescriptions(cx, 2, true, taskDescs, - taskDescJSVals); + taskDescsObj); + nsAutoString customTitle(u"My custom title"); + + JS::Rooted<JSObject*> customDescsObj(cx, JS::NewArrayObject(cx, 0)); + JS::Rooted<JS::Value> customDescsJSVal(cx, JS::ObjectValue(*customDescsObj)); nsTArray<WindowsJumpListShortcutDescription> customDescs; - nsTArray<JS::Value> customDescJSVals; GenerateWindowsJumpListShortcutDescriptions(cx, 2, true, customDescs, - customDescJSVals); + customDescsObj); + // We expect the long descriptions to be truncated to 260 characters, so // we'll truncate the descriptions here ourselves. for (auto& taskDesc : taskDescs) { @@ -798,8 +995,6 @@ TEST(JumpListBuilder, TruncateDescription) customDesc.mDescription.SetLength(MAX_PATH - 1); } - nsAutoString customTitle(u"My custom title"); - EXPECT_CALL(*testBackend, AbortList()).Times(1); EXPECT_CALL(*testBackend, BeginList(_, _, _)).Times(1); EXPECT_CALL(*testBackend, AddUserTasks(ShellLinksEq(&taskDescs))).Times(1); @@ -811,7 +1006,7 @@ TEST(JumpListBuilder, TruncateDescription) EXPECT_CALL(*testBackend, DeleteList(_)).Times(0); nsresult rv = - builder->PopulateJumpList(taskDescJSVals, customTitle, customDescJSVals, + builder->PopulateJumpList(taskDescsJSVal, customTitle, customDescsJSVal, cx, getter_AddRefs(promise)); ASSERT_TRUE(NS_SUCCEEDED(rv)); ASSERT_TRUE(promise); diff --git a/widget/windows/tests/gtest/TestWinDND.cpp b/widget/windows/tests/gtest/TestWinDND.cpp index fb7849fd79..9a4b8c3db1 100644 --- a/widget/windows/tests/gtest/TestWinDND.cpp +++ b/widget/windows/tests/gtest/TestWinDND.cpp @@ -693,7 +693,7 @@ nsCOMPtr<nsIFile> GetTemporaryDirectory() { #define ENSURE(expr) NS_ENSURE_SUCCESS(expr, nullptr); ENSURE(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpdir))); - MOZ_ASSERT(tmpdir); + MOZ_RELEASE_ASSERT(tmpdir); ENSURE(tmpdir->AppendNative("TestWinDND"_ns)); ENSURE(tmpdir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0777)); |