Bug 1835241 - Part 3: Concatenate nested namespaces in GC code as per the coding...
[gecko.git] / widget / windows / SystemStatusBar.cpp
blob6cc5668bbe6f1b7b091e510392a6a1ed98a7b88f
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/EventDispatcher.h"
14 #include "mozilla/LinkedList.h"
15 #include "mozilla/StaticPtr.h"
16 #include "mozilla/widget/IconLoader.h"
17 #include "mozilla/dom/XULButtonElement.h"
18 #include "nsComputedDOMStyle.h"
19 #include "nsIContentPolicy.h"
20 #include "nsISupports.h"
21 #include "nsMenuPopupFrame.h"
22 #include "nsXULPopupManager.h"
23 #include "nsIDocShell.h"
24 #include "nsDocShell.h"
25 #include "nsWindowGfx.h"
27 #include "shellapi.h"
29 namespace mozilla::widget {
31 using mozilla::LinkedListElement;
32 using mozilla::dom::Element;
34 class StatusBarEntry final : public LinkedListElement<RefPtr<StatusBarEntry>>,
35 public IconLoader::Listener,
36 public nsISupports {
37 public:
38 explicit StatusBarEntry(Element* aMenu);
39 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
40 NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
41 nsresult Init();
42 void Destroy();
44 MOZ_CAN_RUN_SCRIPT LRESULT OnMessage(HWND hWnd, UINT msg, WPARAM wp,
45 LPARAM lp);
46 const Element* GetMenu() { return mMenu; };
48 nsresult OnComplete(imgIContainer* aImage) override;
50 private:
51 ~StatusBarEntry();
52 RefPtr<mozilla::widget::IconLoader> mIconLoader;
53 // Effectively const but is cycle collected
54 MOZ_KNOWN_LIVE RefPtr<Element> mMenu;
55 NOTIFYICONDATAW mIconData;
56 boolean mInitted;
59 NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry)
62 tmp->Destroy();
63 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry)
65 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader)
66 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu)
67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
69 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry)
70 NS_INTERFACE_MAP_ENTRY(nsISupports)
71 NS_INTERFACE_MAP_END
73 NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry)
74 NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry)
76 StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) {
77 mIconData = {/* cbSize */ sizeof(NOTIFYICONDATA),
78 /* hWnd */ 0,
79 /* uID */ 2,
80 /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
81 /* uCallbackMessage */ WM_USER,
82 /* hIcon */ 0,
83 /* szTip */ L"", // This is updated in Init()
84 /* dwState */ 0,
85 /* dwStateMask */ 0,
86 /* szInfo */ L"",
87 /* uVersion */ {NOTIFYICON_VERSION_4},
88 /* szInfoTitle */ L"",
89 /* dwInfoFlags */ 0};
90 MOZ_ASSERT(mMenu);
93 StatusBarEntry::~StatusBarEntry() {
94 if (!mInitted) {
95 return;
97 Destroy();
98 ::Shell_NotifyIconW(NIM_DELETE, &mIconData);
99 VERIFY(::DestroyWindow(mIconData.hWnd));
102 void StatusBarEntry::Destroy() {
103 if (mIconLoader) {
104 mIconLoader->Destroy();
105 mIconLoader = nullptr;
109 nsresult StatusBarEntry::Init() {
110 MOZ_ASSERT(NS_IsMainThread());
112 // First, look at the content node's "image" attribute.
113 nsAutoString imageURIString;
114 bool hasImageAttr = mMenu->GetAttr(nsGkAtoms::image, imageURIString);
116 nsresult rv;
117 nsCOMPtr<nsIURI> iconURI;
118 if (!hasImageAttr) {
119 // If the content node has no "image" attribute, get the
120 // "list-style-image" property from CSS.
121 RefPtr<mozilla::dom::Document> document = mMenu->GetComposedDoc();
122 if (!document) {
123 return NS_ERROR_FAILURE;
126 RefPtr<const ComputedStyle> sc =
127 nsComputedDOMStyle::GetComputedStyle(mMenu);
128 if (!sc) {
129 return NS_ERROR_FAILURE;
132 iconURI = sc->StyleList()->GetListStyleImageURI();
133 } else {
134 uint64_t dummy = 0;
135 nsContentPolicyType policyType;
136 nsCOMPtr<nsIPrincipal> triggeringPrincipal = mMenu->NodePrincipal();
137 nsContentUtils::GetContentPolicyTypeForUIImageLoading(
138 mMenu, getter_AddRefs(triggeringPrincipal), policyType, &dummy);
139 if (policyType != nsIContentPolicy::TYPE_INTERNAL_IMAGE) {
140 return NS_ERROR_ILLEGAL_VALUE;
143 // If this menu item shouldn't have an icon, the string will be empty,
144 // and NS_NewURI will fail.
145 rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
146 if (NS_FAILED(rv)) return rv;
149 mIconLoader = new IconLoader(this);
151 if (iconURI) {
152 rv = mIconLoader->LoadIcon(iconURI, mMenu);
155 HWND iconWindow;
156 NS_ENSURE_TRUE(iconWindow = ::CreateWindowExW(
157 /* extended style */ 0,
158 /* className */ L"IconWindowClass",
159 /* title */ 0,
160 /* style */ WS_CAPTION,
161 /* x, y, cx, cy */ 0, 0, 0, 0,
162 /* parent */ 0,
163 /* menu */ 0,
164 /* instance */ 0,
165 /* create struct */ 0),
166 NS_ERROR_FAILURE);
167 ::SetWindowLongPtr(iconWindow, GWLP_USERDATA, (LONG_PTR)this);
169 mIconData.hWnd = iconWindow;
170 mIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), IDI_APPLICATION);
172 nsAutoString labelAttr;
173 mMenu->GetAttr(nsGkAtoms::label, labelAttr);
174 const nsString& label = PromiseFlatString(labelAttr);
176 size_t destLength = sizeof mIconData.szTip / (sizeof mIconData.szTip[0]);
177 wchar_t* tooltip = &(mIconData.szTip[0]);
178 ::StringCchCopyNW(tooltip, destLength, label.get(), label.Length());
180 ::Shell_NotifyIconW(NIM_ADD, &mIconData);
181 ::Shell_NotifyIconW(NIM_SETVERSION, &mIconData);
183 mInitted = true;
184 return NS_OK;
187 nsresult StatusBarEntry::OnComplete(imgIContainer* aImage) {
188 NS_ENSURE_ARG_POINTER(aImage);
190 RefPtr<StatusBarEntry> kungFuDeathGrip = this;
192 nsresult rv = nsWindowGfx::CreateIcon(
193 aImage, false, LayoutDeviceIntPoint(),
194 nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &mIconData.hIcon);
195 NS_ENSURE_SUCCESS(rv, rv);
197 ::Shell_NotifyIconW(NIM_MODIFY, &mIconData);
199 if (mIconData.hIcon) {
200 ::DestroyIcon(mIconData.hIcon);
201 mIconData.hIcon = nullptr;
204 // To simplify things, we won't react to CSS changes to update the icon
205 // with this implementation. We can get rid of the IconLoader at this point.
206 mIconLoader->Destroy();
207 mIconLoader = nullptr;
208 return NS_OK;
211 LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
212 if (msg == WM_USER &&
213 (LOWORD(lp) == NIN_SELECT || LOWORD(lp) == NIN_KEYSELECT ||
214 LOWORD(lp) == WM_CONTEXTMENU)) {
215 auto* menu = dom::XULButtonElement::FromNode(mMenu);
216 if (!menu) {
217 return TRUE;
220 nsMenuPopupFrame* popupFrame = menu->GetMenuPopup(FlushType::None);
221 if (NS_WARN_IF(!popupFrame)) {
222 return TRUE;
225 nsIWidget* widget = popupFrame->GetNearestWidget();
226 MOZ_DIAGNOSTIC_ASSERT(widget);
227 if (!widget) {
228 return TRUE;
231 HWND win = static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
232 MOZ_DIAGNOSTIC_ASSERT(win);
233 if (!win) {
234 return TRUE;
237 if (LOWORD(lp) == NIN_KEYSELECT && ::GetForegroundWindow() == win) {
238 // When enter is pressed on the icon, the shell sends two NIN_KEYSELECT
239 // notifications. This might cause us to open two windows. To work around
240 // this, if we're already the foreground window (which happens below),
241 // ignore this notification.
242 return TRUE;
245 if (LOWORD(lp) != WM_CONTEXTMENU &&
246 mMenu->HasAttr(nsGkAtoms::contextmenu)) {
247 ::SetForegroundWindow(win);
248 nsEventStatus status = nsEventStatus_eIgnore;
249 WidgetMouseEvent event(true, eXULSystemStatusBarClick, nullptr,
250 WidgetMouseEvent::eReal);
251 RefPtr<nsPresContext> presContext = popupFrame->PresContext();
252 EventDispatcher::Dispatch(mMenu, presContext, &event, nullptr, &status);
253 return DefWindowProc(hWnd, msg, wp, lp);
256 nsPresContext* pc = popupFrame->PresContext();
257 const CSSIntPoint point = gfx::RoundedToInt(
258 LayoutDeviceIntPoint(GET_X_LPARAM(wp), GET_Y_LPARAM(wp)) /
259 pc->CSSToDevPixelScale());
261 // The menu that is being opened is a Gecko <xul:menu>, and the popup code
262 // that manages it expects that the window that the <xul:menu> belongs to
263 // will be in the foreground when it opens. If we don't do this, then if the
264 // icon is clicked when the window is _not_ in the foreground, then the
265 // opened menu will not be keyboard focusable, nor will it close on its own
266 // if the user clicks away from the menu (at least, not until the user
267 // focuses any window in the parent process).
268 ::SetForegroundWindow(win);
269 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
270 pm->ShowPopupAtScreen(popupFrame->GetContent()->AsElement(), point.x,
271 point.y, false, nullptr);
274 return DefWindowProc(hWnd, msg, wp, lp);
277 NS_IMPL_ISUPPORTS(SystemStatusBar, nsISystemStatusBar)
279 MOZ_CAN_RUN_SCRIPT static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg,
280 WPARAM wp, LPARAM lp) {
281 if (RefPtr<StatusBarEntry> entry =
282 (StatusBarEntry*)GetWindowLongPtr(hWnd, GWLP_USERDATA)) {
283 return entry->OnMessage(hWnd, msg, wp, lp);
285 return TRUE;
288 static StaticRefPtr<SystemStatusBar> sSingleton;
290 SystemStatusBar& SystemStatusBar::GetSingleton() {
291 if (!sSingleton) {
292 sSingleton = new SystemStatusBar();
293 ClearOnShutdown(&sSingleton);
295 return *sSingleton;
298 already_AddRefed<SystemStatusBar> SystemStatusBar::GetAddRefedSingleton() {
299 RefPtr<SystemStatusBar> sm = &GetSingleton();
300 return sm.forget();
303 nsresult SystemStatusBar::Init() {
304 WNDCLASS classStruct = {/* style */ 0,
305 /* lpfnWndProc */ &WindowProc,
306 /* cbClsExtra */ 0,
307 /* cbWndExtra */ 0,
308 /* hInstance */ 0,
309 /* hIcon */ 0,
310 /* hCursor */ 0,
311 /* hbrBackground */ 0,
312 /* lpszMenuName */ 0,
313 /* lpszClassName */ L"IconWindowClass"};
314 NS_ENSURE_TRUE(::RegisterClass(&classStruct), NS_ERROR_FAILURE);
315 return NS_OK;
318 NS_IMETHODIMP
319 SystemStatusBar::AddItem(Element* aElement) {
320 RefPtr<StatusBarEntry> entry = new StatusBarEntry(aElement);
321 nsresult rv = entry->Init();
322 NS_ENSURE_SUCCESS(rv, rv);
324 mStatusBarEntries.insertBack(entry);
325 return NS_OK;
328 NS_IMETHODIMP
329 SystemStatusBar::RemoveItem(Element* aElement) {
330 for (StatusBarEntry* entry : mStatusBarEntries) {
331 if (entry->GetMenu() == aElement) {
332 entry->removeFrom(mStatusBarEntries);
333 return NS_OK;
336 return NS_ERROR_NOT_AVAILABLE;
339 } // namespace mozilla::widget