Bug 1777519 [wpt PR 34660] - Avoid OSError from --kill-safari stat'ing random paths...
[gecko.git] / widget / windows / nsColorPicker.cpp
blob892d152329fcba71e088f036d4f22b95e7555aaa
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsColorPicker.h"
9 #include <shlwapi.h>
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/AutoRestore.h"
13 #include "nsIWidget.h"
14 #include "nsString.h"
15 #include "WidgetUtils.h"
16 #include "nsPIDOMWindow.h"
18 using namespace mozilla::widget;
20 namespace {
21 // Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
22 // temporary child windows of mParentWidget created to address RTL issues
23 // in picker dialogs. We are responsible for destroying these.
24 class AutoDestroyTmpWindow {
25 public:
26 explicit AutoDestroyTmpWindow(HWND aTmpWnd) : mWnd(aTmpWnd) {}
28 ~AutoDestroyTmpWindow() {
29 if (mWnd) DestroyWindow(mWnd);
32 inline HWND get() const { return mWnd; }
34 private:
35 HWND mWnd;
38 static DWORD ColorStringToRGB(const nsAString& aColor) {
39 DWORD result = 0;
41 for (uint32_t i = 1; i < aColor.Length(); ++i) {
42 result *= 16;
44 char16_t c = aColor[i];
45 if (c >= '0' && c <= '9') {
46 result += c - '0';
47 } else if (c >= 'a' && c <= 'f') {
48 result += 10 + (c - 'a');
49 } else {
50 result += 10 + (c - 'A');
54 DWORD r = result & 0x00FF0000;
55 DWORD g = result & 0x0000FF00;
56 DWORD b = result & 0x000000FF;
58 r = r >> 16;
59 b = b << 16;
61 result = r | g | b;
63 return result;
66 static nsString ToHexString(BYTE n) {
67 nsString result;
68 if (n <= 0x0F) {
69 result.Append('0');
71 result.AppendInt(n, 16);
72 return result;
75 static void BGRIntToRGBString(DWORD color, nsAString& aResult) {
76 BYTE r = GetRValue(color);
77 BYTE g = GetGValue(color);
78 BYTE b = GetBValue(color);
80 aResult.Assign('#');
81 aResult.Append(ToHexString(r));
82 aResult.Append(ToHexString(g));
83 aResult.Append(ToHexString(b));
85 } // namespace
87 static AsyncColorChooser* gColorChooser;
89 AsyncColorChooser::AsyncColorChooser(COLORREF aInitialColor,
90 const nsTArray<nsString>& aDefaultColors,
91 nsIWidget* aParentWidget,
92 nsIColorPickerShownCallback* aCallback)
93 : mozilla::Runnable("AsyncColorChooser"),
94 mInitialColor(aInitialColor),
95 mDefaultColors(aDefaultColors.Clone()),
96 mColor(aInitialColor),
97 mParentWidget(aParentWidget),
98 mCallback(aCallback) {}
100 NS_IMETHODIMP
101 AsyncColorChooser::Run() {
102 MOZ_ASSERT(NS_IsMainThread(),
103 "Color pickers can only be opened from main thread currently");
105 // Allow only one color picker to be opened at a time, to workaround bug
106 // 944737
107 if (!gColorChooser) {
108 mozilla::AutoRestore<AsyncColorChooser*> restoreColorChooser(gColorChooser);
109 gColorChooser = this;
111 AutoDestroyTmpWindow adtw(
112 (HWND)(mParentWidget.get()
113 ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW)
114 : nullptr));
116 COLORREF customColors[16];
117 for (size_t i = 0; i < mozilla::ArrayLength(customColors); i++) {
118 if (i < mDefaultColors.Length()) {
119 customColors[i] = ColorStringToRGB(mDefaultColors[i]);
120 } else {
121 customColors[i] = 0x00FFFFFF;
125 CHOOSECOLOR options;
126 options.lStructSize = sizeof(options);
127 options.hwndOwner = adtw.get();
128 options.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK;
129 options.rgbResult = mInitialColor;
130 options.lpCustColors = customColors;
131 options.lpfnHook = HookProc;
133 mColor = ChooseColor(&options) ? options.rgbResult : mInitialColor;
134 } else {
135 NS_WARNING(
136 "Currently, it's not possible to open more than one color "
137 "picker at a time");
138 mColor = mInitialColor;
141 if (mCallback) {
142 nsAutoString colorStr;
143 BGRIntToRGBString(mColor, colorStr);
144 mCallback->Done(colorStr);
147 return NS_OK;
150 void AsyncColorChooser::Update(COLORREF aColor) {
151 if (mColor != aColor) {
152 mColor = aColor;
154 nsAutoString colorStr;
155 BGRIntToRGBString(mColor, colorStr);
156 mCallback->Update(colorStr);
160 /* static */ UINT_PTR CALLBACK AsyncColorChooser::HookProc(HWND aDialog,
161 UINT aMsg,
162 WPARAM aWParam,
163 LPARAM aLParam) {
164 if (!gColorChooser) {
165 return 0;
168 if (aMsg == WM_INITDIALOG) {
169 // "The default dialog box procedure processes the WM_INITDIALOG message
170 // before passing it to the hook procedure.
171 // For all other messages, the hook procedure receives the message first."
172 // https://docs.microsoft.com/en-us/windows/win32/api/commdlg/nc-commdlg-lpcchookproc
173 // "The dialog box procedure should return TRUE to direct the system to
174 // set the keyboard focus to the control specified by wParam."
175 // https://docs.microsoft.com/en-us/windows/win32/dlgbox/wm-initdialog
176 return 1;
179 if (aMsg == WM_CTLCOLORSTATIC) {
180 // The color picker does not expose a proper way to retrieve the current
181 // color, so we need to obtain it from the static control displaying the
182 // current color instead.
183 const int kCurrentColorBoxID = 709;
184 if ((HWND)aLParam == GetDlgItem(aDialog, kCurrentColorBoxID)) {
185 gColorChooser->Update(GetPixel((HDC)aWParam, 0, 0));
189 // Let the default dialog box procedure processes the message.
190 return 0;
193 ///////////////////////////////////////////////////////////////////////////////
194 // nsIColorPicker
196 nsColorPicker::nsColorPicker() {}
198 nsColorPicker::~nsColorPicker() {}
200 NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
202 NS_IMETHODIMP
203 nsColorPicker::Init(mozIDOMWindowProxy* parent, const nsAString& title,
204 const nsAString& aInitialColor,
205 const nsTArray<nsString>& aDefaultColors) {
206 MOZ_ASSERT(parent,
207 "Null parent passed to colorpicker, no color picker for you!");
208 mParentWidget =
209 WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(parent));
210 mInitialColor = ColorStringToRGB(aInitialColor);
211 mDefaultColors.Assign(aDefaultColors);
212 return NS_OK;
215 NS_IMETHODIMP
216 nsColorPicker::Open(nsIColorPickerShownCallback* aCallback) {
217 NS_ENSURE_ARG(aCallback);
218 nsCOMPtr<nsIRunnable> event = new AsyncColorChooser(
219 mInitialColor, mDefaultColors, mParentWidget, aCallback);
220 return NS_DispatchToMainThread(event);