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/. */
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
,
36 explicit StatusBarEntry(Element
* aMenu
);
37 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
38 NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry
)
42 MOZ_CAN_RUN_SCRIPT LRESULT
OnMessage(HWND hWnd
, UINT msg
, WPARAM wp
,
44 const Element
* GetMenu() { return mMenu
; };
46 nsresult
OnComplete(imgIContainer
* aImage
) override
;
50 RefPtr
<mozilla::widget::IconLoader
> mIconLoader
;
51 // Effectively const but is cycle collected
52 MOZ_KNOWN_LIVE RefPtr
<Element
> mMenu
;
53 NOTIFYICONDATAW mIconData
;
57 NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry
)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry
)
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
)
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
),
78 /* uFlags */ NIF_ICON
| NIF_MESSAGE
| NIF_TIP
| NIF_SHOWTIP
,
79 /* uCallbackMessage */ WM_USER
,
81 /* szTip */ L
"", // This is updated in Init()
85 /* uVersion */ {NOTIFYICON_VERSION_4
},
86 /* szInfoTitle */ L
"",
91 StatusBarEntry::~StatusBarEntry() {
96 ::Shell_NotifyIconW(NIM_DELETE
, &mIconData
);
97 VERIFY(::DestroyWindow(mIconData
.hWnd
));
100 void StatusBarEntry::Destroy() {
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
;
113 mMenu
->GetAttr(kNameSpaceID_None
, nsGkAtoms::image
, imageURIString
);
116 nsCOMPtr
<nsIURI
> iconURI
;
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();
122 return NS_ERROR_FAILURE
;
125 RefPtr
<const ComputedStyle
> sc
=
126 nsComputedDOMStyle::GetComputedStyle(mMenu
);
128 return NS_ERROR_FAILURE
;
131 iconURI
= sc
->StyleList()->GetListStyleImageURI();
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);
151 rv
= mIconLoader
->LoadIcon(iconURI
, mMenu
);
155 NS_ENSURE_TRUE(iconWindow
= ::CreateWindowExW(
156 /* extended style */ 0,
157 /* className */ L
"IconWindowClass",
159 /* style */ WS_CAPTION
,
160 /* x, y, cx, cy */ 0, 0, 0, 0,
164 /* create struct */ 0),
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
);
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;
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());
219 nsMenuPopupFrame
* popupFrame
= menu
->GetPopup();
220 MOZ_DIAGNOSTIC_ASSERT(popupFrame
);
225 nsIWidget
* widget
= popupFrame
->GetNearestWidget();
226 MOZ_DIAGNOSTIC_ASSERT(widget
);
231 HWND win
= static_cast<HWND
>(widget
->GetNativeData(NS_NATIVE_WINDOW
));
232 MOZ_DIAGNOSTIC_ASSERT(win
);
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.
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,
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
);
288 static StaticRefPtr
<SystemStatusBar
> sSingleton
;
290 SystemStatusBar
& SystemStatusBar::GetSingleton() {
292 sSingleton
= new SystemStatusBar();
293 ClearOnShutdown(&sSingleton
);
298 already_AddRefed
<SystemStatusBar
> SystemStatusBar::GetAddRefedSingleton() {
299 RefPtr
<SystemStatusBar
> sm
= &GetSingleton();
303 nsresult
SystemStatusBar::Init() {
304 WNDCLASS classStruct
= {/* style */ 0,
305 /* lpfnWndProc */ &WindowProc
,
311 /* hbrBackground */ 0,
312 /* lpszMenuName */ 0,
313 /* lpszClassName */ L
"IconWindowClass"};
314 NS_ENSURE_TRUE(::RegisterClass(&classStruct
), NS_ERROR_FAILURE
);
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
);
329 SystemStatusBar::RemoveItem(Element
* aElement
) {
330 for (StatusBarEntry
* entry
: mStatusBarEntries
) {
331 if (entry
->GetMenu() == aElement
) {
332 entry
->removeFrom(mStatusBarEntries
);
336 return NS_ERROR_NOT_AVAILABLE
;
339 } // namespace mozilla::widget