summaryrefslogtreecommitdiffstats
path: root/widget/windows/SystemStatusBar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/SystemStatusBar.cpp')
-rw-r--r--widget/windows/SystemStatusBar.cpp302
1 files changed, 302 insertions, 0 deletions
diff --git a/widget/windows/SystemStatusBar.cpp b/widget/windows/SystemStatusBar.cpp
new file mode 100644
index 0000000000..8dc4ffed4b
--- /dev/null
+++ b/widget/windows/SystemStatusBar.cpp
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <strsafe.h>
+#include "SystemStatusBar.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticPtr.h"
+#include "nsComputedDOMStyle.h"
+#include "nsIContentPolicy.h"
+#include "nsMenuFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "nsXULPopupManager.h"
+#include "IconLoaderHelperWin.h"
+#include "nsIDocShell.h"
+#include "nsDocShell.h"
+
+namespace mozilla::widget {
+
+using mozilla::LinkedListElement;
+using mozilla::dom::Element;
+using mozilla::widget::IconLoaderListenerWin;
+
+class StatusBarEntry final : public LinkedListElement<RefPtr<StatusBarEntry>>,
+ public IconLoaderListenerWin {
+ public:
+ explicit StatusBarEntry(Element* aMenu);
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
+ nsresult Init();
+ LRESULT OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
+ const Element* GetMenu() { return mMenu; };
+
+ nsresult OnComplete();
+
+ private:
+ ~StatusBarEntry();
+ RefPtr<mozilla::widget::IconLoader> mIconLoader;
+ RefPtr<mozilla::widget::IconLoaderHelperWin> mIconLoaderHelper;
+ RefPtr<Element> mMenu;
+ NOTIFYICONDATAW mIconData;
+ boolean mInitted;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(StatusBarEntry)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StatusBarEntry)
+ tmp->OnComplete();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StatusBarEntry)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIconLoaderHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenu)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StatusBarEntry)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(StatusBarEntry)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(StatusBarEntry)
+
+StatusBarEntry::StatusBarEntry(Element* aMenu) : mMenu(aMenu), mInitted(false) {
+ mIconData = {/* cbSize */ sizeof(NOTIFYICONDATA),
+ /* hWnd */ 0,
+ /* uID */ 2,
+ /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
+ /* uCallbackMessage */ WM_USER,
+ /* hIcon */ 0,
+ /* szTip */ L"", // This is updated in Init()
+ /* dwState */ 0,
+ /* dwStateMask */ 0,
+ /* szInfo */ L"",
+ /* uVersion */ {NOTIFYICON_VERSION_4},
+ /* szInfoTitle */ L"",
+ /* dwInfoFlags */ 0};
+ MOZ_ASSERT(mMenu);
+}
+
+StatusBarEntry::~StatusBarEntry() {
+ if (!mInitted) {
+ return;
+ }
+ ::Shell_NotifyIconW(NIM_DELETE, &mIconData);
+ VERIFY(::DestroyWindow(mIconData.hWnd));
+}
+
+nsresult StatusBarEntry::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // First, look at the content node's "image" attribute.
+ nsAutoString imageURIString;
+ bool hasImageAttr =
+ mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::image, imageURIString);
+
+ nsresult rv;
+ RefPtr<ComputedStyle> sc;
+ nsCOMPtr<nsIURI> iconURI;
+ if (!hasImageAttr) {
+ // If the content node has no "image" attribute, get the
+ // "list-style-image" property from CSS.
+ RefPtr<mozilla::dom::Document> document = mMenu->GetComposedDoc();
+ if (!document) {
+ return NS_ERROR_FAILURE;
+ }
+
+ sc = nsComputedDOMStyle::GetComputedStyle(mMenu, nullptr);
+ if (!sc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ iconURI = sc->StyleList()->GetListStyleImageURI();
+ } else {
+ uint64_t dummy = 0;
+ nsContentPolicyType policyType;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = mMenu->NodePrincipal();
+ nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ mMenu, getter_AddRefs(triggeringPrincipal), policyType, &dummy);
+ if (policyType != nsIContentPolicy::TYPE_INTERNAL_IMAGE) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // If this menu item shouldn't have an icon, the string will be empty,
+ // and NS_NewURI will fail.
+ rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ mIconLoaderHelper = new IconLoaderHelperWin(this);
+ nsIntRect rect;
+ mIconLoader = new IconLoader(mIconLoaderHelper, mMenu, rect);
+
+ if (iconURI) {
+ rv = mIconLoader->LoadIcon(iconURI);
+ }
+
+ HWND iconWindow;
+ NS_ENSURE_TRUE(iconWindow = ::CreateWindowExW(
+ /* extended style */ 0,
+ /* className */ L"IconWindowClass",
+ /* title */ 0,
+ /* style */ WS_CAPTION,
+ /* x, y, cx, cy */ 0, 0, 0, 0,
+ /* parent */ 0,
+ /* menu */ 0,
+ /* instance */ 0,
+ /* create struct */ 0),
+ NS_ERROR_FAILURE);
+ ::SetWindowLongPtr(iconWindow, GWLP_USERDATA, (LONG_PTR)this);
+
+ mIconData.hWnd = iconWindow;
+ mIconData.hIcon = mIconLoaderHelper->GetNativeIconImage();
+
+ nsAutoString labelAttr;
+ mMenu->GetAttr(kNameSpaceID_None, nsGkAtoms::label, labelAttr);
+ const nsString& label = PromiseFlatString(labelAttr);
+
+ size_t destLength = sizeof mIconData.szTip / (sizeof mIconData.szTip[0]);
+ wchar_t* tooltip = &(mIconData.szTip[0]);
+ ::StringCchCopyNW(tooltip, destLength, label.get(), label.Length());
+
+ ::Shell_NotifyIconW(NIM_ADD, &mIconData);
+ ::Shell_NotifyIconW(NIM_SETVERSION, &mIconData);
+
+ mInitted = true;
+ return NS_OK;
+}
+
+nsresult StatusBarEntry::OnComplete() {
+ RefPtr<StatusBarEntry> kungFuDeathGrip = this;
+ mIconData.hIcon = mIconLoaderHelper->GetNativeIconImage();
+
+ ::Shell_NotifyIconW(NIM_MODIFY, &mIconData);
+
+ // To simplify things, we won't react to CSS changes to update the icon
+ // with this implementation. We can get rid of the IconLoader and Helper
+ // at this point, which will also free the allocated HICON.
+ mIconLoaderHelper->Destroy();
+ mIconLoader->ReleaseJSObjects();
+ mIconLoader->Destroy();
+ mIconLoader = nullptr;
+ mIconLoaderHelper = nullptr;
+ return NS_OK;
+}
+
+LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ if (msg == WM_USER &&
+ (LOWORD(lp) == WM_LBUTTONUP || LOWORD(lp) == WM_RBUTTONUP)) {
+ nsMenuFrame* menu = do_QueryFrame(mMenu->GetPrimaryFrame());
+ if (!menu) {
+ return TRUE;
+ }
+
+ nsMenuPopupFrame* popupFrame = menu->GetPopup();
+ if (!popupFrame) {
+ return TRUE;
+ }
+
+ nsIWidget* widget = popupFrame->GetNearestWidget();
+ if (!widget) {
+ return TRUE;
+ }
+
+ HWND win = static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
+ if (!win) {
+ return TRUE;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = popupFrame->PresContext()->GetDocShell();
+ nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(docShell);
+ if (!baseWin) {
+ return TRUE;
+ }
+
+ double scale = 1.0;
+ baseWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
+ int32_t x = NSToIntRound(GET_X_LPARAM(wp) / scale);
+ int32_t y = NSToIntRound(GET_Y_LPARAM(wp) / scale);
+
+ // The menu that is being opened is a Gecko <xul:menu>, and the popup code
+ // that manages it expects that the window that the <xul:menu> belongs to
+ // will be in the foreground when it opens. If we don't do this, then if the
+ // icon is clicked when the window is _not_ in the foreground, then the
+ // opened menu will not be keyboard focusable, nor will it close on its own
+ // if the user clicks away from the menu (at least, not until the user
+ // focuses any window in the parent process).
+ ::SetForegroundWindow(win);
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ pm->ShowPopupAtScreen(popupFrame->GetContent(), x, y, false, nullptr);
+ }
+
+ return DefWindowProc(hWnd, msg, wp, lp);
+}
+
+NS_IMPL_ISUPPORTS(SystemStatusBar, nsISystemStatusBar)
+
+static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
+ StatusBarEntry* entry =
+ (StatusBarEntry*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
+ if (entry) {
+ return entry->OnMessage(hWnd, msg, wp, lp);
+ }
+ return TRUE;
+}
+
+static StaticRefPtr<SystemStatusBar> sSingleton;
+
+SystemStatusBar& SystemStatusBar::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new SystemStatusBar();
+ ClearOnShutdown(&sSingleton);
+ }
+ return *sSingleton;
+}
+
+already_AddRefed<SystemStatusBar> SystemStatusBar::GetAddRefedSingleton() {
+ RefPtr<SystemStatusBar> sm = &GetSingleton();
+ return sm.forget();
+}
+
+nsresult SystemStatusBar::Init() {
+ WNDCLASS classStruct = {/* style */ 0,
+ /* lpfnWndProc */ &WindowProc,
+ /* cbClsExtra */ 0,
+ /* cbWndExtra */ 0,
+ /* hInstance */ 0,
+ /* hIcon */ 0,
+ /* hCursor */ 0,
+ /* hbrBackground */ 0,
+ /* lpszMenuName */ 0,
+ /* lpszClassName */ L"IconWindowClass"};
+ NS_ENSURE_TRUE(::RegisterClass(&classStruct), NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemStatusBar::AddItem(Element* aElement) {
+ RefPtr<StatusBarEntry> entry = new StatusBarEntry(aElement);
+ nsresult rv = entry->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatusBarEntries.insertBack(entry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SystemStatusBar::RemoveItem(Element* aElement) {
+ for (StatusBarEntry* entry : mStatusBarEntries) {
+ if (entry->GetMenu() == aElement) {
+ entry->removeFrom(mStatusBarEntries);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+} // namespace mozilla::widget