Bug 1642138: Improve integration with the macOS-level Window menu handling to unlock built-in OS functionality such as tiling of windows. r=mstange

Differential Revision: https://phabricator.services.mozilla.com/D159723
This commit is contained in:
Stephen A Pohl 2022-11-03 19:32:27 +00:00
parent 8c8a637b75
commit 177c2181ca
9 changed files with 46 additions and 80 deletions

View file

@ -438,10 +438,9 @@
</menu>
#ifdef XP_MACOSX
<menu id="windowMenu"
onpopupshowing="macWindowMenuDidShow();"
onpopuphidden="macWindowMenuDidHide();"
data-l10n-id="menu-window-menu">
<menupopup id="windowPopup">
<menuseparator/>
<menuitem command="minimizeWindow" key="key_minimizeWindow"/>
<menuitem command="zoomWindow"/>
<!-- decomment when "BringAllToFront" is implemented

View file

@ -6,6 +6,4 @@ https_first_disabled = true
run-if = os == "mac" # Mac only feature
support-files =
file_shareurl.html
[browser_window_menu_list.js]
run-if = os == "mac" # Mac only feature
[browser_file_close_tabs.js]

View file

@ -1,45 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_window_menu_list() {
// This title is different depending on the build. For example, it's "Nightly"
// for a local build, "Mozilla Firefox" for an official release build.
const windowTitle = window.document.title;
await checkWindowMenu([windowTitle, "Browser chrome tests"]);
let newWindow = await BrowserTestUtils.openNewBrowserWindow();
await checkWindowMenu([windowTitle, "Browser chrome tests", windowTitle]);
await BrowserTestUtils.closeWindow(newWindow);
});
async function checkWindowMenu(labels) {
let menu = document.querySelector("#windowMenu");
// We can't toggle menubar items on OSX, so mocking instead.
await new Promise(resolve => {
menu.addEventListener("popupshown", resolve, { once: true });
menu.dispatchEvent(new MouseEvent("popupshowing"));
menu.dispatchEvent(new MouseEvent("popupshown"));
});
let menuitems = [...menu.querySelectorAll("menuseparator ~ menuitem")];
is(menuitems.length, labels.length, "Correct number of windows in the menu");
is(
menuitems.map(item => item.label).join(","),
labels.join(","),
"Correct labels on menuitems"
);
for (let menuitem of menuitems) {
ok(
menuitem instanceof customElements.get("menuitem"),
"sibling is menuitem"
);
}
// We can't toggle menubar items on OSX, so mocking instead.
await new Promise(resolve => {
menu.addEventListener("popuphidden", resolve, { once: true });
menu.dispatchEvent(new MouseEvent("popuphiding"));
menu.dispatchEvent(new MouseEvent("popuphidden"));
});
}

View file

@ -104,7 +104,6 @@
</menupopup>
</menu>
<menu id="tasksMenu"/>
<menu id="windowMenu"/>
<menu id="menu_Help"/>
</menubar>
</toolbar>

View file

@ -4,36 +4,6 @@
* 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/. */
function macWindowMenuDidShow() {
let frag = document.createDocumentFragment();
for (let win of Services.wm.getEnumerator("")) {
if (win.document.documentElement.getAttribute("inwindowmenu") == "false") {
continue;
}
let item = document.createXULElement("menuitem");
item.setAttribute("label", win.document.title);
if (win == window) {
item.setAttribute("checked", "true");
}
item.addEventListener("command", () => {
if (win.windowState == window.STATE_MINIMIZED) {
win.restore();
}
win.focus();
});
frag.appendChild(item);
}
document.getElementById("windowPopup").appendChild(frag);
}
function macWindowMenuDidHide() {
let sep = document.getElementById("sep-window-list");
// Clear old items
while (sep.nextElementSibling) {
sep.nextElementSibling.remove();
}
}
function zoomWindow() {
if (window.windowState == window.STATE_NORMAL) {
window.maximize();

View file

@ -107,6 +107,8 @@ class nsMenuBarX : public nsMenuParentX, public nsChangeObserver, public mozilla
nsMenuX* GetMenuAt(uint32_t aIndex);
nsMenuX* GetXULHelpMenu();
void SetSystemHelpMenu();
nsMenuX* GetXULWindowMenu();
void SetSystemWindowMenu();
nsresult Paint();
void ForceUpdateNativeMenuAt(const nsAString& aIndexString);
void ForceNativeMenuReload(); // used for testing

View file

@ -398,6 +398,35 @@ void nsMenuBarX::SetSystemHelpMenu() {
NS_OBJC_END_TRY_ABORT_BLOCK;
}
nsMenuX* nsMenuBarX::GetXULWindowMenu() {
// The Window menu is usually one of the last ones, so we start there and
// count back.
for (int32_t i = GetMenuCount() - 1; i >= 0; --i) {
nsMenuX* aMenu = GetMenuAt(i);
if (aMenu && nsMenuX::IsXULWindowMenu(aMenu->Content())) {
return aMenu;
}
}
return nil;
}
// We want to tell the OS which one of our menus is the Window menu in order
// to get 'free' functionality from the OS that varies based on the version of
// macOS, such as moving windows to the left/right of the screen on macOS 13.
void nsMenuBarX::SetSystemWindowMenu() {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
nsMenuX* xulWindowMenu = GetXULWindowMenu();
if (xulWindowMenu) {
NSMenu* windowMenu = xulWindowMenu->NativeNSMenu();
if (windowMenu) {
NSApp.windowsMenu = windowMenu;
}
}
NS_OBJC_END_TRY_ABORT_BLOCK;
}
nsresult nsMenuBarX::Paint() {
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
@ -419,6 +448,7 @@ nsresult nsMenuBarX::Paint() {
// Set menu bar and event target.
NSApp.mainMenu = mNativeMenu;
SetSystemHelpMenu();
SetSystemWindowMenu();
nsMenuBarX::sLastGeckoMenuBarPainted = this;
gSomeMenuBarPainted = YES;

View file

@ -161,6 +161,7 @@ class nsMenuX final : public nsMenuParentX,
void Dump(uint32_t aIndent) const;
static bool IsXULHelpMenu(nsIContent* aMenuContent);
static bool IsXULWindowMenu(nsIContent* aMenuContent);
// Set an observer that gets notified of menu opening and closing.
// The menu does not keep a strong reference the observer. The observer must

View file

@ -914,6 +914,18 @@ bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent) {
return retval;
}
bool nsMenuX::IsXULWindowMenu(nsIContent* aMenuContent) {
bool retval = false;
if (aMenuContent && aMenuContent->IsElement()) {
nsAutoString id;
aMenuContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
if (id.Equals(u"windowMenu"_ns)) {
retval = true;
}
}
return retval;
}
//
// nsChangeObserver
//