summaryrefslogtreecommitdiffstats
path: root/widget/windows
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows')
-rw-r--r--widget/windows/CompositorWidgetParent.cpp12
-rw-r--r--widget/windows/GfxInfo.cpp6
-rw-r--r--widget/windows/JumpListBuilder.cpp109
-rw-r--r--widget/windows/RemoteBackbuffer.cpp16
-rw-r--r--widget/windows/ToastNotification.cpp3
-rw-r--r--widget/windows/WinCompositorWidget.h8
-rw-r--r--widget/windows/filedialog/PWinFileDialog.ipdl17
-rw-r--r--widget/windows/filedialog/WinFileDialogChild.cpp60
-rw-r--r--widget/windows/filedialog/WinFileDialogCommands.cpp111
-rw-r--r--widget/windows/filedialog/WinFileDialogCommands.h186
-rw-r--r--widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh13
-rw-r--r--widget/windows/filedialog/WinFileDialogParent.cpp80
-rw-r--r--widget/windows/filedialog/WinFileDialogParent.h15
-rw-r--r--widget/windows/nsDataObj.cpp6
-rw-r--r--widget/windows/nsFilePicker.cpp344
-rw-r--r--widget/windows/nsFilePicker.h19
-rw-r--r--widget/windows/nsNativeThemeWin.cpp44
-rw-r--r--widget/windows/nsUXThemeData.cpp2
-rw-r--r--widget/windows/nsUXThemeData.h1
-rw-r--r--widget/windows/nsWindow.cpp272
-rw-r--r--widget/windows/nsWindow.h16
-rw-r--r--widget/windows/tests/gtest/TestJumpListBuilder.cpp261
-rw-r--r--widget/windows/tests/gtest/TestWinDND.cpp2
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));