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/Element.h"
11 #include "mozilla/ClearOnShutdown.h"
12 #include "mozilla/LinkedList.h"
13 #include "mozilla/StaticPtr.h"
14 #include "nsComputedDOMStyle.h"
15 #include "nsIContentPolicy.h"
16 #include "nsMenuFrame.h"
17 #include "nsMenuPopupFrame.h"
18 #include "nsXULPopupManager.h"
19 #include "IconLoaderHelperWin.h"
21 namespace mozilla::widget
{
23 using mozilla::LinkedListElement
;
24 using mozilla::dom::Element
;
25 using mozilla::widget::IconLoaderListenerWin
;
27 class StatusBarEntry final
: public LinkedListElement
<RefPtr
<StatusBarEntry
>>,
28 public IconLoaderListenerWin
{
30 explicit StatusBarEntry(Element
* aMenu
);
31 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
32 NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry
)
34 LRESULT
OnMessage(HWND hWnd
, UINT msg
, WPARAM wp
, LPARAM lp
);
35 const Element
* GetMenu() { return mMenu
; };
37 nsresult
OnComplete();
41 RefPtr
<mozilla::widget::IconLoader
> mIconLoader
;
42 RefPtr
<mozilla::widget::IconLoaderHelperWin
> mIconLoaderHelper
;
43 RefPtr
<Element
> mMenu
;
44 NOTIFYICONDATAW mIconData
;
48 NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry
)
50 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry
)
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry
)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader
)
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoaderHelper
)
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu
)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
59 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry
)
60 NS_INTERFACE_MAP_ENTRY(nsISupports
)
63 NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry
)
64 NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry
)
66 StatusBarEntry::StatusBarEntry(Element
* aMenu
) : mMenu(aMenu
), mInitted(false) {
67 mIconData
= {/* cbSize */ sizeof(NOTIFYICONDATA
),
70 /* uFlags */ NIF_ICON
| NIF_MESSAGE
| NIF_TIP
| NIF_SHOWTIP
,
71 /* uCallbackMessage */ WM_USER
,
73 /* szTip */ L
"", // This is updated in Init()
77 /* uVersion */ {NOTIFYICON_VERSION_4
},
78 /* szInfoTitle */ L
"",
83 StatusBarEntry::~StatusBarEntry() {
87 ::Shell_NotifyIconW(NIM_DELETE
, &mIconData
);
88 VERIFY(::DestroyWindow(mIconData
.hWnd
));
91 nsresult
StatusBarEntry::Init() {
92 MOZ_ASSERT(NS_IsMainThread());
94 // First, look at the content node's "image" attribute.
95 nsAutoString imageURIString
;
97 mMenu
->GetAttr(kNameSpaceID_None
, nsGkAtoms::image
, imageURIString
);
100 RefPtr
<ComputedStyle
> sc
;
101 nsCOMPtr
<nsIURI
> iconURI
;
103 // If the content node has no "image" attribute, get the
104 // "list-style-image" property from CSS.
105 RefPtr
<mozilla::dom::Document
> document
= mMenu
->GetComposedDoc();
107 return NS_ERROR_FAILURE
;
110 sc
= nsComputedDOMStyle::GetComputedStyle(mMenu
, nullptr);
112 return NS_ERROR_FAILURE
;
115 iconURI
= sc
->StyleList()->GetListStyleImageURI();
118 nsContentPolicyType policyType
;
119 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
= mMenu
->NodePrincipal();
120 nsContentUtils::GetContentPolicyTypeForUIImageLoading(
121 mMenu
, getter_AddRefs(triggeringPrincipal
), policyType
, &dummy
);
122 if (policyType
!= nsIContentPolicy::TYPE_INTERNAL_IMAGE
) {
123 return NS_ERROR_ILLEGAL_VALUE
;
126 // If this menu item shouldn't have an icon, the string will be empty,
127 // and NS_NewURI will fail.
128 rv
= NS_NewURI(getter_AddRefs(iconURI
), imageURIString
);
129 if (NS_FAILED(rv
)) return rv
;
132 mIconLoaderHelper
= new IconLoaderHelperWin(this);
134 mIconLoader
= new IconLoader(mIconLoaderHelper
, mMenu
, rect
);
137 rv
= mIconLoader
->LoadIcon(iconURI
);
141 NS_ENSURE_TRUE(iconWindow
= ::CreateWindowExW(
142 /* extended style */ 0,
143 /* className */ L
"IconWindowClass",
145 /* style */ WS_CAPTION
,
146 /* x, y, cx, cy */ 0, 0, 0, 0,
150 /* create struct */ 0),
152 ::SetWindowLongPtr(iconWindow
, GWLP_USERDATA
, (LONG_PTR
)this);
154 mIconData
.hWnd
= iconWindow
;
155 mIconData
.hIcon
= mIconLoaderHelper
->GetNativeIconImage();
157 nsAutoString labelAttr
;
158 mMenu
->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
, labelAttr
);
159 const nsString
& label
= PromiseFlatString(labelAttr
);
161 size_t destLength
= sizeof mIconData
.szTip
/ (sizeof mIconData
.szTip
[0]);
162 wchar_t* tooltip
= &(mIconData
.szTip
[0]);
163 ::StringCchCopyNW(tooltip
, destLength
, label
.get(), label
.Length());
165 ::Shell_NotifyIconW(NIM_ADD
, &mIconData
);
166 ::Shell_NotifyIconW(NIM_SETVERSION
, &mIconData
);
172 nsresult
StatusBarEntry::OnComplete() {
173 RefPtr
<StatusBarEntry
> kungFuDeathGrip
= this;
174 mIconData
.hIcon
= mIconLoaderHelper
->GetNativeIconImage();
176 ::Shell_NotifyIconW(NIM_MODIFY
, &mIconData
);
178 // To simplify things, we won't react to CSS changes to update the icon
179 // with this implementation. We can get rid of the IconLoader and Helper
180 // at this point, which will also free the allocated HICON.
181 mIconLoaderHelper
->Destroy();
182 mIconLoader
->ReleaseJSObjects();
183 mIconLoader
->Destroy();
184 mIconLoader
= nullptr;
185 mIconLoaderHelper
= nullptr;
189 LRESULT
StatusBarEntry::OnMessage(HWND hWnd
, UINT msg
, WPARAM wp
, LPARAM lp
) {
190 if (msg
== WM_USER
&&
191 (LOWORD(lp
) == WM_LBUTTONUP
|| LOWORD(lp
) == WM_RBUTTONUP
)) {
192 nsMenuFrame
* menu
= do_QueryFrame(mMenu
->GetPrimaryFrame());
197 nsMenuPopupFrame
* popupFrame
= menu
->GetPopup();
201 nsIWidget
* widget
= popupFrame
->GetNearestWidget();
206 HWND win
= static_cast<HWND
>(widget
->GetNativeData(NS_NATIVE_WINDOW
));
211 // The menu that is being opened is a Gecko <xul:menu>, and the popup code
212 // that manages it expects that the window that the <xul:menu> belongs to
213 // will be in the foreground when it opens. If we don't do this, then if the
214 // icon is clicked when the window is _not_ in the foreground, then the
215 // opened menu will not be keyboard focusable, nor will it close on its own
216 // if the user clicks away from the menu (at least, not until the user
217 // focuses any window in the parent process).
218 ::SetForegroundWindow(win
);
219 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
220 pm
->ShowPopupAtScreen(popupFrame
->GetContent(), GET_X_LPARAM(wp
),
221 GET_Y_LPARAM(wp
), false, nullptr);
224 return DefWindowProc(hWnd
, msg
, wp
, lp
);
227 NS_IMPL_ISUPPORTS(SystemStatusBar
, nsISystemStatusBar
)
229 static LRESULT CALLBACK
WindowProc(HWND hWnd
, UINT msg
, WPARAM wp
, LPARAM lp
) {
230 StatusBarEntry
* entry
=
231 (StatusBarEntry
*)GetWindowLongPtr(hWnd
, GWLP_USERDATA
);
233 return entry
->OnMessage(hWnd
, msg
, wp
, lp
);
238 static StaticRefPtr
<SystemStatusBar
> sSingleton
;
240 SystemStatusBar
& SystemStatusBar::GetSingleton() {
242 sSingleton
= new SystemStatusBar();
243 ClearOnShutdown(&sSingleton
);
248 already_AddRefed
<SystemStatusBar
> SystemStatusBar::GetAddRefedSingleton() {
249 RefPtr
<SystemStatusBar
> sm
= &GetSingleton();
253 nsresult
SystemStatusBar::Init() {
254 WNDCLASS classStruct
= {/* style */ 0,
255 /* lpfnWndProc */ &WindowProc
,
261 /* hbrBackground */ 0,
262 /* lpszMenuName */ 0,
263 /* lpszClassName */ L
"IconWindowClass"};
264 NS_ENSURE_TRUE(::RegisterClass(&classStruct
), NS_ERROR_FAILURE
);
269 SystemStatusBar::AddItem(Element
* aElement
) {
270 RefPtr
<StatusBarEntry
> entry
= new StatusBarEntry(aElement
);
271 nsresult rv
= entry
->Init();
272 NS_ENSURE_SUCCESS(rv
, rv
);
274 mStatusBarEntries
.insertBack(entry
);
279 SystemStatusBar::RemoveItem(Element
* aElement
) {
280 for (StatusBarEntry
* entry
: mStatusBarEntries
) {
281 if (entry
->GetMenu() == aElement
) {
282 entry
->removeFrom(mStatusBarEntries
);
286 return NS_ERROR_NOT_AVAILABLE
;
289 } // namespace mozilla::widget