Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / widget / windows / SystemStatusBar.cpp
blobbf3001aa4f4d2109998f919684d26a9e041e01df
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 "nsComputedDOMStyle.h"
18 #include "nsIContentPolicy.h"
19 #include "nsISupports.h"
20 #include "nsMenuFrame.h"
21 #include "nsMenuPopupFrame.h"
22 #include "nsXULPopupManager.h"
23 #include "nsIDocShell.h"
24 #include "nsDocShell.h"
25 #include "nsWindowGfx.h"
27 namespace mozilla::widget {
29 using mozilla::LinkedListElement;
30 using mozilla::dom::Element;
32 class StatusBarEntry final : public LinkedListElement<RefPtr<StatusBarEntry>>,
33 public IconLoader::Listener,
34 public nsISupports {
35 public:
36 explicit StatusBarEntry(Element* aMenu);
37 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
38 NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
39 nsresult Init();
40 void Destroy();
42 MOZ_CAN_RUN_SCRIPT LRESULT OnMessage(HWND hWnd, UINT msg, WPARAM wp,
43 LPARAM lp);
44 const Element* GetMenu() { return mMenu; };
46 nsresult OnComplete(imgIContainer* aImage) override;
48 private:
49 ~StatusBarEntry();
50 RefPtr<mozilla::widget::IconLoader> mIconLoader;
51 // Effectively const but is cycle collected
52 MOZ_KNOWN_LIVE RefPtr<Element> mMenu;
53 NOTIFYICONDATAW mIconData;
54 boolean mInitted;
57 NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry)
60 tmp->Destroy();
61 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
62 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry)
63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader)
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu)
65 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
67 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry)
68 NS_INTERFACE_MAP_ENTRY(nsISupports)
69 NS_INTERFACE_MAP_END
71 NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry)
72 NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry)
74 StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) {
75 mIconData = {/* cbSize */ sizeof(NOTIFYICONDATA),
76 /* hWnd */ 0,
77 /* uID */ 2,
78 /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
79 /* uCallbackMessage */ WM_USER,
80 /* hIcon */ 0,
81 /* szTip */ L"", // This is updated in Init()
82 /* dwState */ 0,
83 /* dwStateMask */ 0,
84 /* szInfo */ L"",
85 /* uVersion */ {NOTIFYICON_VERSION_4},
86 /* szInfoTitle */ L"",
87 /* dwInfoFlags */ 0};
88 MOZ_ASSERT(mMenu);
91 StatusBarEntry::~StatusBarEntry() {
92 if (!mInitted) {
93 return;
95 Destroy();
96 ::Shell_NotifyIconW(NIM_DELETE, &mIconData);
97 VERIFY(::DestroyWindow(mIconData.hWnd));
100 void StatusBarEntry::Destroy() {
101 if (mIconLoader) {
102 mIconLoader->Destroy();
103 mIconLoader = nullptr;
107 nsresult StatusBarEntry::Init() {
108 MOZ_ASSERT(NS_IsMainThread());
110 // First, look at the content node's "image" attribute.
111 nsAutoString imageURIString;
112 bool hasImageAttr =
113 mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::image, imageURIString);
115 nsresult rv;
116 nsCOMPtr<nsIURI> iconURI;
117 if (!hasImageAttr) {
118 // If the content node has no "image" attribute, get the
119 // "list-style-image" property from CSS.
120 RefPtr<mozilla::dom::Document> document = mMenu->GetComposedDoc();
121 if (!document) {
122 return NS_ERROR_FAILURE;
125 RefPtr<const ComputedStyle> sc =
126 nsComputedDOMStyle::GetComputedStyle(mMenu);
127 if (!sc) {
128 return NS_ERROR_FAILURE;
131 iconURI = sc->StyleList()->GetListStyleImageURI();
132 } else {
133 uint64_t dummy = 0;
134 nsContentPolicyType policyType;
135 nsCOMPtr<nsIPrincipal> triggeringPrincipal = mMenu->NodePrincipal();
136 nsContentUtils::GetContentPolicyTypeForUIImageLoading(
137 mMenu, getter_AddRefs(triggeringPrincipal), policyType, &dummy);
138 if (policyType != nsIContentPolicy::TYPE_INTERNAL_IMAGE) {
139 return NS_ERROR_ILLEGAL_VALUE;
142 // If this menu item shouldn't have an icon, the string will be empty,
143 // and NS_NewURI will fail.
144 rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
145 if (NS_FAILED(rv)) return rv;
148 mIconLoader = new IconLoader(this);
150 if (iconURI) {
151 rv = mIconLoader->LoadIcon(iconURI, mMenu);
154 HWND iconWindow;
155 NS_ENSURE_TRUE(iconWindow = ::CreateWindowExW(
156 /* extended style */ 0,
157 /* className */ L"IconWindowClass",
158 /* title */ 0,
159 /* style */ WS_CAPTION,
160 /* x, y, cx, cy */ 0, 0, 0, 0,
161 /* parent */ 0,
162 /* menu */ 0,
163 /* instance */ 0,
164 /* create struct */ 0),
165 NS_ERROR_FAILURE);
166 ::SetWindowLongPtr(iconWindow, GWLP_USERDATA, (LONG_PTR)this);
168 mIconData.hWnd = iconWindow;
169 mIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), IDI_APPLICATION);
171 nsAutoString labelAttr;
172 mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::label, labelAttr);
173 const nsString& label = PromiseFlatString(labelAttr);
175 size_t destLength = sizeof mIconData.szTip / (sizeof mIconData.szTip[0]);
176 wchar_t* tooltip = &(mIconData.szTip[0]);
177 ::StringCchCopyNW(tooltip, destLength, label.get(), label.Length());
179 ::Shell_NotifyIconW(NIM_ADD, &mIconData);
180 ::Shell_NotifyIconW(NIM_SETVERSION, &mIconData);
182 mInitted = true;
183 return NS_OK;
186 nsresult StatusBarEntry::OnComplete(imgIContainer* aImage) {
187 NS_ENSURE_ARG_POINTER(aImage);
189 RefPtr<StatusBarEntry> kungFuDeathGrip = this;
191 nsresult rv = nsWindowGfx::CreateIcon(
192 aImage, false, LayoutDeviceIntPoint(),
193 nsWindowGfx::GetIconMetrics(nsWindowGfx::kRegularIcon), &mIconData.hIcon);
194 NS_ENSURE_SUCCESS(rv, rv);
196 ::Shell_NotifyIconW(NIM_MODIFY, &mIconData);
198 if (mIconData.hIcon) {
199 ::DestroyIcon(mIconData.hIcon);
200 mIconData.hIcon = nullptr;
203 // To simplify things, we won't react to CSS changes to update the icon
204 // with this implementation. We can get rid of the IconLoader at this point.
205 mIconLoader->Destroy();
206 mIconLoader = nullptr;
207 return NS_OK;
210 LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
211 if (msg == WM_USER &&
212 (LOWORD(lp) == NIN_SELECT || LOWORD(lp) == NIN_KEYSELECT ||
213 LOWORD(lp) == WM_CONTEXTMENU)) {
214 nsMenuFrame* menu = do_QueryFrame(mMenu->GetPrimaryFrame());
215 if (!menu) {
216 return TRUE;
219 nsMenuPopupFrame* popupFrame = menu->GetPopup();
220 MOZ_DIAGNOSTIC_ASSERT(popupFrame);
221 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(kNameSpaceID_None, nsGkAtoms::contextmenu)) {
247 ::SetForegroundWindow(win);
248 nsEventStatus status = nsEventStatus_eIgnore;
249 WidgetMouseEvent event(true, eXULSystemStatusBarClick, nullptr,
250 WidgetMouseEvent::eReal);
251 RefPtr<nsPresContext> presContext = menu->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(), point.x, point.y, false,
271 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