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/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
,
35 explicit StatusBarEntry(Element
* aMenu
);
36 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
37 NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry
)
41 LRESULT
OnMessage(HWND hWnd
, UINT msg
, WPARAM wp
, LPARAM lp
);
42 const Element
* GetMenu() { return mMenu
; };
44 nsresult
OnComplete(imgIContainer
* aImage
) override
;
48 RefPtr
<mozilla::widget::IconLoader
> mIconLoader
;
49 RefPtr
<Element
> mMenu
;
50 NOTIFYICONDATAW mIconData
;
54 NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry
)
56 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry
)
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
)
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
),
75 /* uFlags */ NIF_ICON
| NIF_MESSAGE
| NIF_TIP
| NIF_SHOWTIP
,
76 /* uCallbackMessage */ WM_USER
,
78 /* szTip */ L
"", // This is updated in Init()
82 /* uVersion */ {NOTIFYICON_VERSION_4
},
83 /* szInfoTitle */ L
"",
88 StatusBarEntry::~StatusBarEntry() {
93 ::Shell_NotifyIconW(NIM_DELETE
, &mIconData
);
94 VERIFY(::DestroyWindow(mIconData
.hWnd
));
97 void StatusBarEntry::Destroy() {
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
;
110 mMenu
->GetAttr(kNameSpaceID_None
, nsGkAtoms::image
, imageURIString
);
113 RefPtr
<ComputedStyle
> sc
;
114 nsCOMPtr
<nsIURI
> iconURI
;
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();
120 return NS_ERROR_FAILURE
;
123 sc
= nsComputedDOMStyle::GetComputedStyle(mMenu
, nullptr);
125 return NS_ERROR_FAILURE
;
128 iconURI
= sc
->StyleList()->GetListStyleImageURI();
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);
148 rv
= mIconLoader
->LoadIcon(iconURI
, mMenu
);
152 NS_ENSURE_TRUE(iconWindow
= ::CreateWindowExW(
153 /* extended style */ 0,
154 /* className */ L
"IconWindowClass",
156 /* style */ WS_CAPTION
,
157 /* x, y, cx, cy */ 0, 0, 0, 0,
161 /* create struct */ 0),
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
);
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;
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());
215 nsMenuPopupFrame
* popupFrame
= menu
->GetPopup();
220 nsIWidget
* widget
= popupFrame
->GetNearestWidget();
225 HWND win
= static_cast<HWND
>(widget
->GetNativeData(NS_NATIVE_WINDOW
));
230 nsCOMPtr
<nsIDocShell
> docShell
= popupFrame
->PresContext()->GetDocShell();
231 nsCOMPtr
<nsIBaseWindow
> baseWin
= do_QueryInterface(docShell
);
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
);
262 return entry
->OnMessage(hWnd
, msg
, wp
, lp
);
267 static StaticRefPtr
<SystemStatusBar
> sSingleton
;
269 SystemStatusBar
& SystemStatusBar::GetSingleton() {
271 sSingleton
= new SystemStatusBar();
272 ClearOnShutdown(&sSingleton
);
277 already_AddRefed
<SystemStatusBar
> SystemStatusBar::GetAddRefedSingleton() {
278 RefPtr
<SystemStatusBar
> sm
= &GetSingleton();
282 nsresult
SystemStatusBar::Init() {
283 WNDCLASS classStruct
= {/* style */ 0,
284 /* lpfnWndProc */ &WindowProc
,
290 /* hbrBackground */ 0,
291 /* lpszMenuName */ 0,
292 /* lpszClassName */ L
"IconWindowClass"};
293 NS_ENSURE_TRUE(::RegisterClass(&classStruct
), NS_ERROR_FAILURE
);
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
);
308 SystemStatusBar::RemoveItem(Element
* aElement
) {
309 for (StatusBarEntry
* entry
: mStatusBarEntries
) {
310 if (entry
->GetMenu() == aElement
) {
311 entry
->removeFrom(mStatusBarEntries
);
315 return NS_ERROR_NOT_AVAILABLE
;
318 } // namespace mozilla::widget