Bug 1692971 [wpt PR 27638] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / widget / windows / SystemStatusBar.cpp
blob2064430063da07c9c01416ed89203ecba8b17fae
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
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 <strsafe.h>
8 #include "SystemStatusBar.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/dom/Element.h"
12 #include "mozilla/ClearOnShutdown.h"
13 #include "mozilla/LinkedList.h"
14 #include "mozilla/StaticPtr.h"
15 #include "mozilla/widget/IconLoader.h"
16 #include "nsComputedDOMStyle.h"
17 #include "nsIContentPolicy.h"
18 #include "nsISupports.h"
19 #include "nsMenuFrame.h"
20 #include "nsMenuPopupFrame.h"
21 #include "nsXULPopupManager.h"
22 #include "nsIDocShell.h"
23 #include "nsDocShell.h"
24 #include "nsWindowGfx.h"
26 namespace mozilla::widget {
28 using mozilla::LinkedListElement;
29 using mozilla::dom::Element;
31 class StatusBarEntry final : public LinkedListElement<RefPtr<StatusBarEntry>>,
32 public IconLoader::Listener,
33 public nsISupports {
34 public:
35 explicit StatusBarEntry(Element* aMenu);
36 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
37 NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
38 nsresult Init();
39 void Destroy();
41 LRESULT OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
42 const Element* GetMenu() { return mMenu; };
44 nsresult OnComplete(imgIContainer* aImage) override;
46 private:
47 ~StatusBarEntry();
48 RefPtr<mozilla::widget::IconLoader> mIconLoader;
49 RefPtr<Element> mMenu;
50 NOTIFYICONDATAW mIconData;
51 boolean mInitted;
54 NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
56 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry)
57 tmp->Destroy();
58 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry)
60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader)
61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu)
62 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
64 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry)
65 NS_INTERFACE_MAP_ENTRY(nsISupports)
66 NS_INTERFACE_MAP_END
68 NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry)
69 NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry)
71 StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) {
72 mIconData = {/* cbSize */ sizeof(NOTIFYICONDATA),
73 /* hWnd */ 0,
74 /* uID */ 2,
75 /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
76 /* uCallbackMessage */ WM_USER,
77 /* hIcon */ 0,
78 /* szTip */ L"", // This is updated in Init()
79 /* dwState */ 0,
80 /* dwStateMask */ 0,
81 /* szInfo */ L"",
82 /* uVersion */ {NOTIFYICON_VERSION_4},
83 /* szInfoTitle */ L"",
84 /* dwInfoFlags */ 0};
85 MOZ_ASSERT(mMenu);
88 StatusBarEntry::~StatusBarEntry() {
89 if (!mInitted) {
90 return;
92 Destroy();
93 ::Shell_NotifyIconW(NIM_DELETE, &mIconData);
94 VERIFY(::DestroyWindow(mIconData.hWnd));
97 void StatusBarEntry::Destroy() {
98 if (mIconLoader) {
99 mIconLoader->Destroy();
100 mIconLoader = nullptr;
104 nsresult StatusBarEntry::Init() {
105 MOZ_ASSERT(NS_IsMainThread());
107 // First, look at the content node's "image" attribute.
108 nsAutoString imageURIString;
109 bool hasImageAttr =
110 mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::image, imageURIString);
112 nsresult rv;
113 RefPtr<ComputedStyle> sc;
114 nsCOMPtr<nsIURI> iconURI;
115 if (!hasImageAttr) {
116 // If the content node has no "image" attribute, get the
117 // "list-style-image" property from CSS.
118 RefPtr<mozilla::dom::Document> document = mMenu->GetComposedDoc();
119 if (!document) {
120 return NS_ERROR_FAILURE;
123 sc = nsComputedDOMStyle::GetComputedStyle(mMenu, nullptr);
124 if (!sc) {
125 return NS_ERROR_FAILURE;
128 iconURI = sc->StyleList()->GetListStyleImageURI();
129 } else {
130 uint64_t dummy = 0;
131 nsContentPolicyType policyType;
132 nsCOMPtr<nsIPrincipal> triggeringPrincipal = mMenu->NodePrincipal();
133 nsContentUtils::GetContentPolicyTypeForUIImageLoading(
134 mMenu, getter_AddRefs(triggeringPrincipal), policyType, &dummy);
135 if (policyType != nsIContentPolicy::TYPE_INTERNAL_IMAGE) {
136 return NS_ERROR_ILLEGAL_VALUE;
139 // If this menu item shouldn't have an icon, the string will be empty,
140 // and NS_NewURI will fail.
141 rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
142 if (NS_FAILED(rv)) return rv;
145 mIconLoader = new IconLoader(this);
147 if (iconURI) {
148 rv = mIconLoader->LoadIcon(iconURI, mMenu);
151 HWND iconWindow;
152 NS_ENSURE_TRUE(iconWindow = ::CreateWindowExW(
153 /* extended style */ 0,
154 /* className */ L"IconWindowClass",
155 /* title */ 0,
156 /* style */ WS_CAPTION,
157 /* x, y, cx, cy */ 0, 0, 0, 0,
158 /* parent */ 0,
159 /* menu */ 0,
160 /* instance */ 0,
161 /* create struct */ 0),
162 NS_ERROR_FAILURE);
163 ::SetWindowLongPtr(iconWindow, GWLP_USERDATA, (LONG_PTR)this);
165 mIconData.hWnd = iconWindow;
166 mIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), IDI_APPLICATION);
168 nsAutoString labelAttr;
169 mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::label, labelAttr);
170 const nsString& label = PromiseFlatString(labelAttr);
172 size_t destLength = sizeof mIconData.szTip / (sizeof mIconData.szTip[0]);
173 wchar_t* tooltip = &(mIconData.szTip[0]);
174 ::StringCchCopyNW(tooltip, destLength, label.get(), label.Length());
176 ::Shell_NotifyIconW(NIM_ADD, &mIconData);
177 ::Shell_NotifyIconW(NIM_SETVERSION, &mIconData);
179 mInitted = true;
180 return NS_OK;
183 nsresult StatusBarEntry::OnComplete(imgIContainer* aImage) {
184 NS_ENSURE_ARG_POINTER(aImage);
186 RefPtr<StatusBarEntry> kungFuDeathGrip = this;
188 nsresult rv = nsWindowGfx::CreateIcon(
189 aImage, false, LayoutDeviceIntPoint(),
190 nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &mIconData.hIcon);
191 NS_ENSURE_SUCCESS(rv, rv);
193 ::Shell_NotifyIconW(NIM_MODIFY, &mIconData);
195 if (mIconData.hIcon) {
196 ::DestroyIcon(mIconData.hIcon);
197 mIconData.hIcon = nullptr;
200 // To simplify things, we won't react to CSS changes to update the icon
201 // with this implementation. We can get rid of the IconLoader at this point.
202 mIconLoader->Destroy();
203 mIconLoader = nullptr;
204 return NS_OK;
207 LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
208 if (msg == WM_USER &&
209 (LOWORD(lp) == WM_LBUTTONUP || LOWORD(lp) == WM_RBUTTONUP)) {
210 nsMenuFrame* menu = do_QueryFrame(mMenu->GetPrimaryFrame());
211 if (!menu) {
212 return TRUE;
215 nsMenuPopupFrame* popupFrame = menu->GetPopup();
216 if (!popupFrame) {
217 return TRUE;
220 nsIWidget* widget = popupFrame->GetNearestWidget();
221 if (!widget) {
222 return TRUE;
225 HWND win = static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
226 if (!win) {
227 return TRUE;
230 nsCOMPtr<nsIDocShell> docShell = popupFrame->PresContext()->GetDocShell();
231 nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(docShell);
232 if (!baseWin) {
233 return TRUE;
236 double scale = 1.0;
237 baseWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
238 int32_t x = NSToIntRound(GET_X_LPARAM(wp) / scale);
239 int32_t y = NSToIntRound(GET_Y_LPARAM(wp) / scale);
241 // The menu that is being opened is a Gecko <xul:menu>, and the popup code
242 // that manages it expects that the window that the <xul:menu> belongs to
243 // will be in the foreground when it opens. If we don't do this, then if the
244 // icon is clicked when the window is _not_ in the foreground, then the
245 // opened menu will not be keyboard focusable, nor will it close on its own
246 // if the user clicks away from the menu (at least, not until the user
247 // focuses any window in the parent process).
248 ::SetForegroundWindow(win);
249 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
250 pm->ShowPopupAtScreen(popupFrame->GetContent(), x, y, false, nullptr);
253 return DefWindowProc(hWnd, msg, wp, lp);
256 NS_IMPL_ISUPPORTS(SystemStatusBar, nsISystemStatusBar)
258 static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
259 StatusBarEntry* entry =
260 (StatusBarEntry*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
261 if (entry) {
262 return entry->OnMessage(hWnd, msg, wp, lp);
264 return TRUE;
267 static StaticRefPtr<SystemStatusBar> sSingleton;
269 SystemStatusBar& SystemStatusBar::GetSingleton() {
270 if (!sSingleton) {
271 sSingleton = new SystemStatusBar();
272 ClearOnShutdown(&sSingleton);
274 return *sSingleton;
277 already_AddRefed<SystemStatusBar> SystemStatusBar::GetAddRefedSingleton() {
278 RefPtr<SystemStatusBar> sm = &GetSingleton();
279 return sm.forget();
282 nsresult SystemStatusBar::Init() {
283 WNDCLASS classStruct = {/* style */ 0,
284 /* lpfnWndProc */ &WindowProc,
285 /* cbClsExtra */ 0,
286 /* cbWndExtra */ 0,
287 /* hInstance */ 0,
288 /* hIcon */ 0,
289 /* hCursor */ 0,
290 /* hbrBackground */ 0,
291 /* lpszMenuName */ 0,
292 /* lpszClassName */ L"IconWindowClass"};
293 NS_ENSURE_TRUE(::RegisterClass(&classStruct), NS_ERROR_FAILURE);
294 return NS_OK;
297 NS_IMETHODIMP
298 SystemStatusBar::AddItem(Element* aElement) {
299 RefPtr<StatusBarEntry> entry = new StatusBarEntry(aElement);
300 nsresult rv = entry->Init();
301 NS_ENSURE_SUCCESS(rv, rv);
303 mStatusBarEntries.insertBack(entry);
304 return NS_OK;
307 NS_IMETHODIMP
308 SystemStatusBar::RemoveItem(Element* aElement) {
309 for (StatusBarEntry* entry : mStatusBarEntries) {
310 if (entry->GetMenu() == aElement) {
311 entry->removeFrom(mStatusBarEntries);
312 return NS_OK;
315 return NS_ERROR_NOT_AVAILABLE;
318 } // namespace mozilla::widget