no-bug: Continue work

This commit is contained in:
mr. m
2026-04-29 02:21:22 +02:00
parent d15f5331ff
commit e3b0295e36
17 changed files with 399 additions and 71 deletions

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs
index b23244f9d3278918b016bb3fcab19687bc2e292a..0bcb63f7abaf406077b52469eb913fcb75ba13f8 100644
index b23244f9d3278918b016bb3fcab19687bc2e292a..e5de81b3060a1ee76b1a6aff2e4ae1ca50f2caa9 100644
--- a/browser/components/urlbar/content/UrlbarInput.mjs
+++ b/browser/components/urlbar/content/UrlbarInput.mjs
@@ -90,6 +90,13 @@ const lazy = XPCOMUtils.declareLazy({
@@ -294,20 +294,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..0bcb63f7abaf406077b52469eb913fcb
l10nId,
l10nId == "urlbar-placeholder-with-name"
? { name: engineName }
@@ -4891,6 +5012,12 @@ export class UrlbarInput extends HTMLElement {
_on_blur(event) {
lazy.logger.debug("Blur Event");
+ if (
+ this.document.commandDispatcher.focusedElement == this.inputField &&
+ !lazy.UrlbarPrefs.get("closeOnWindowBlur")
+ ) {
+ return;
+ }
// We cannot count every blur events after a missed engagement as abandoment
// because the user may have clicked on some view element that executes
// a command causing a focus change. For example opening preferences from
@@ -4964,6 +5091,11 @@ export class UrlbarInput extends HTMLElement {
@@ -4964,6 +5085,11 @@ export class UrlbarInput extends HTMLElement {
}
_on_click(event) {
@@ -319,7 +306,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..0bcb63f7abaf406077b52469eb913fcb
switch (event.target) {
case this.inputField:
case this._inputContainer:
@@ -5042,7 +5174,7 @@ export class UrlbarInput extends HTMLElement {
@@ -5042,7 +5168,7 @@ export class UrlbarInput extends HTMLElement {
}
}
@@ -328,7 +315,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..0bcb63f7abaf406077b52469eb913fcb
this.view.autoOpen({ event });
} else {
if (this._untrimOnFocusAfterKeydown) {
@@ -5082,9 +5214,16 @@ export class UrlbarInput extends HTMLElement {
@@ -5082,9 +5208,16 @@ export class UrlbarInput extends HTMLElement {
}
_on_mousedown(event) {
@@ -346,7 +333,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..0bcb63f7abaf406077b52469eb913fcb
if (
event.composedTarget != this.inputField &&
event.composedTarget != this._inputContainer
@@ -5094,6 +5233,10 @@ export class UrlbarInput extends HTMLElement {
@@ -5094,6 +5227,10 @@ export class UrlbarInput extends HTMLElement {
this.focusedViaMousedown = !this.focused;
this._preventClickSelectsAll = this.focused;
@@ -357,7 +344,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..0bcb63f7abaf406077b52469eb913fcb
// Keep the focus status, since the attribute may be changed
// upon calling this.focus().
@@ -5129,7 +5272,7 @@ export class UrlbarInput extends HTMLElement {
@@ -5129,7 +5266,7 @@ export class UrlbarInput extends HTMLElement {
}
// Don't close the view when clicking on a tab; we may want to keep the
// view open on tab switch, and the TabSelect event arrived earlier.
@@ -366,7 +353,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..0bcb63f7abaf406077b52469eb913fcb
break;
}
@@ -5411,7 +5554,7 @@ export class UrlbarInput extends HTMLElement {
@@ -5411,7 +5548,7 @@ export class UrlbarInput extends HTMLElement {
// When we are in actions search mode we can show more results so
// increase the limit.
let maxResults =

View File

@@ -0,0 +1,27 @@
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
index 515177a3a97094142593a98fb1b3023acf1ccb87..fb6f932547f9523a240f95915b7440dcdfa16975 100644
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -5240,7 +5240,7 @@ static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
// calls to ...orderFront: in TRY blocks. See bmo bug 470864.
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
mWindow.contentView.needsDisplay = YES;
- if (!nativeParentWindow || mPopupLevel != PopupLevel::Parent) {
+ if (!mZenShowLocked && (!nativeParentWindow || mPopupLevel != PopupLevel::Parent)) {
[mWindow orderFront:nil];
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
@@ -5287,7 +5287,12 @@ static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
// We don't want most alwaysontop / alert windows to pull focus when
// they're opened, as these tend to be for peripheral indicators and
// displays.
- if ((mAlwaysOnTop && mPiPType != PiPType::DocumentPiP) || mIsAlert) {
+ if (mZenShowLocked) {
+ // Zen: window-control service has this widget locked-hidden;
+ // skip the native order-front but let the rest of Show()'s
+ // bookkeeping run normally.
+ } else if ((mAlwaysOnTop && mPiPType != PiPType::DocumentPiP) ||
+ mIsAlert) {
[mWindow orderFront:nil];
} else {
[mWindow makeKeyAndOrderFront:nil];

View File

@@ -0,0 +1,18 @@
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
index 89950bd72c77ed961e59faa2fadb8f21a78fd23a..8c74bfa08af3c16cc05e11ea34902cbcdde3c64a 100644
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -1076,7 +1076,12 @@ void nsWindow::Show(bool aState) {
}
#endif
- NativeShow(aState);
+ // Zen: skip the actual GTK show when window-control has the widget
+ // locked-hidden; mIsShown is already set above so Mozilla's
+ // bookkeeping treats the widget as shown.
+ if (!(aState && mZenShowLocked)) {
+ NativeShow(aState);
+ }
RefreshWindowClass();
}

View File

@@ -0,0 +1,28 @@
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
index c22e055b9254ed1c8943c232a8339c574563dffc..0a0cb7cad4e878f93c03078564dac051dde1d014 100644
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -698,6 +698,14 @@ class nsIWidget : public nsSupportsWeakReference {
*/
virtual void Show(bool aState) = 0;
+ /**
+ * Zen: when set to true, Show(true) calls on this widget become
+ * no-ops until SetZenShowLocked(false) is called. Used to keep a
+ * window invisible while it's being set up.
+ */
+ void SetZenShowLocked(bool aLocked) { mZenShowLocked = aLocked; }
+ bool IsZenShowLocked() const { return mZenShowLocked; }
+
/**
* Whether or not a widget must be recreated after being hidden to show
* again properly.
@@ -2419,6 +2427,8 @@ class nsIWidget : public nsSupportsWeakReference {
mozilla::Maybe<FullscreenSavedState> mSavedBounds;
bool mUpdateCursor;
+ // Zen: when true, Show(true) is a no-op. See SetZenShowLocked().
+ bool mZenShowLocked = false;
bool mIMEHasFocus;
bool mIMEHasQuit;
// if the window is fully occluded (rendering may be paused in response)

View File

@@ -0,0 +1,59 @@
diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
index baed7d09e31291b26a1d0a1ddfd63b57f0ce67d0..9daa1140e9538427d002c6f8ff99cd68265249cc 100644
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -1761,28 +1761,33 @@ void nsWindow::Show(bool aState) {
// cursor.
SetCursor(Cursor{eCursor_standard});
- switch (mFrameState->GetSizeMode()) {
- case nsSizeMode_Fullscreen:
- ::ShowWindow(mWnd, SW_SHOW);
- break;
- case nsSizeMode_Maximized:
- ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
- break;
- case nsSizeMode_Minimized:
- ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
- break;
- default:
- if (CanTakeFocus() &&
- (!mAlwaysOnTop || mPiPType == PiPType::DocumentPiP)) {
- ::ShowWindow(mWnd, SW_SHOWNORMAL);
- } else {
- ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
- // Don't flicker the window if we're restoring session
- if (!sIsRestoringSession) {
- (void)GetAttention(2);
+ // Zen: skip the actual ShowWindow() call when window-control
+ // has the widget locked-hidden; mIsVisible is already set above
+ // so Mozilla's bookkeeping treats the widget as shown.
+ if (!mZenShowLocked) {
+ switch (mFrameState->GetSizeMode()) {
+ case nsSizeMode_Fullscreen:
+ ::ShowWindow(mWnd, SW_SHOW);
+ break;
+ case nsSizeMode_Maximized:
+ ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
+ break;
+ case nsSizeMode_Minimized:
+ ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
+ break;
+ default:
+ if (CanTakeFocus() &&
+ (!mAlwaysOnTop || mPiPType == PiPType::DocumentPiP)) {
+ ::ShowWindow(mWnd, SW_SHOWNORMAL);
+ } else {
+ ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
+ // Don't flicker the window if we're restoring session
+ if (!sIsRestoringSession) {
+ (void)GetAttention(2);
+ }
}
- }
- break;
+ break;
+ }
}
if (!mHasBeenShown) {

View File

@@ -295,6 +295,7 @@ window.gZenUIManager = {
onFloatingURLBarOpen() {
requestAnimationFrame(() => {
this.updateTabsToolbar();
window.dispatchEvent(new CustomEvent("ZenFloatingURLBarOpened"));
});
},
@@ -607,6 +608,12 @@ window.gZenUIManager = {
gURLBar._zenHandleUrlbarClose = null;
}
window.dispatchEvent(
new CustomEvent("ZenURLBarClosedEarly", {
detail: { onSwitch, onElementPicked },
})
);
const isFocusedBefore = gURLBar.focused;
setTimeout(() => {
// We use this attribute on Tabbrowser::addTab
@@ -952,7 +959,18 @@ window.gZenVerticalTabsManager = {
this,
"_canReplaceNewTab",
"zen.urlbar.replace-newtab",
true
true,
null,
val => {
// On little windows, we always want to replace new tabs
if (
window._zenStartupLittleWindow ||
document.documentElement.hasAttribute("zen-little-window")
) {
return true;
}
return val;
}
);
var updateEvent = this._updateEvent.bind(this);
var onPrefChange = this._onPrefChange.bind(this);

View File

@@ -3,6 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { ZenLittleWindow } from "resource:///modules/zen/ZenLittleWindow.sys.mjs";
export const ZenCustomizableUI = new (class {
constructor() {}
@@ -38,10 +39,15 @@ export const ZenCustomizableUI = new (class {
// We do not have access to the window object here
init(window) {
this.#initLittleWindow(window);
this.#addSidebarButtons(window);
this.#modifyToolbarButtons(window);
}
#initLittleWindow(window) {
ZenLittleWindow.onLittleWindow(window);
}
#addSidebarButtons(window) {
const kDefaultSidebarWidth =
AppConstants.platform === "macosx" ? "230px" : "186px";

View File

@@ -1266,6 +1266,9 @@ const KbsManager = {
const mainKeyset = browser.document.getElementById(
ZEN_DEVTOOLS_KEYSET_ID
);
if (!mainKeyset) {
return null;
}
mainKeyset.before(browser._zenDevtoolsKeyset);
}
return browser._zenDevtoolsKeyset;
@@ -1480,7 +1483,13 @@ const KbsManager = {
const originalDevKeyset = browser.document.getElementById(
ZEN_DEVTOOLS_KEYSET_ID
);
originalDevKeyset.after(devtoolsKeyset);
if (originalDevKeyset) {
originalDevKeyset.after(devtoolsKeyset);
} else {
console.warn(
"Zen CKS: DevTools keyset not found, devTools shortcuts won't be applied"
);
}
},
async resetAllShortcuts() {

View File

@@ -2,6 +2,18 @@
* 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/. */
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
lazy,
"ZenWindowControl",
"@mozilla.org/zen/window-control;1",
Ci.nsIZenWindowControl
);
const URLBAR_HEIGHT = 340;
const URLBAR_WIDTH = 640;
@@ -10,34 +22,8 @@ const FEATURES =
`resizable,minimizable,scrollbars,width=${URLBAR_WIDTH},height=${URLBAR_HEIGHT},centerscreen`;
class nsZenLittleWindow {
#initialized = false;
init() {
if (this.#initialized) {
return;
}
this.#initialized = true;
Services.obs.addObserver(this, "browser-window-before-show");
}
uninit() {
if (!this.#initialized) {
return;
}
this.#initialized = false;
try {
Services.obs.removeObserver(this, "browser-window-before-show");
} catch (e) {}
}
observe(subject, topic) {
if (
topic === "browser-window-before-show" &&
this.#isLittleWindow(subject)
) {
this.#attachAutoclose(subject);
}
}
init() {}
uninit() {}
/**
* Open a fresh little window, or focus an existing empty one if there
@@ -53,15 +39,15 @@ class nsZenLittleWindow {
return win;
}
}
if (typeof opener?.OpenBrowserWindow !== "function") {
return null;
}
let win = opener.OpenBrowserWindow({
zenLittleWindow: true,
all: false,
features: FEATURES,
});
win.focus();
win.windowUtils.suppressAnimation(true);
// Hide the OS-level window until the floating urlbar is ready, so the
// user never sees a half-laid-out chrome flash on top.
lazy.ZenWindowControl.hide(win);
return win;
}
@@ -87,23 +73,66 @@ class nsZenLittleWindow {
}
}
#attachAutoclose(win) {
const onClosed = event => {
if (event.detail?.onElementPicked && event.type === "ZenURLBarClosed") {
onLittleWindow(win) {
if (!this.#isLittleWindow(win)) {
return;
}
const observer = new win.ResizeObserver(entries => {
if (win.closed) {
return;
}
if (!win.closed && this.#isOnEmptyTab(win)) {
for (const entry of entries) {
if (entry.target.id === "urlbar") {
const { width, height } = entry.target.getBoundingClientRect();
win.resizeTo(width, height);
}
}
});
const onClosed = event => {
observer.disconnect();
if (!win.closed && !event.detail?.onElementPicked) {
lazy.ZenWindowControl.hide(win);
win.close();
} else {
// Resize window back to normal size
win.resizeTo(1240, 840);
const [width, height] = [1000, 600];
win.setResizable(true);
win.resizeTo(1000, 600);
win.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow)
.center(null, true, true)
}
};
win.document.documentElement.setAttribute("zen-little-window", "true");
win.resizeTo(URLBAR_WIDTH, URLBAR_HEIGHT);
win.focus();
const urlbar = win.gURLBar;
observer.observe(urlbar);
// TODO: Handle window blur event
win.setResizable(false);
win.addEventListener(
"ZenFloatingURLBarOpened",
() => {
win.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow)
.center(null, true, true)
if (AppConstants.platform == "macosx" && !Services.focus.activeWindow) {
Cc["@mozilla.org/widget/macdocksupport;1"]
.getService(Ci.nsIMacDockSupport)
.activateApplication(true);
}
win.focus();
urlbar.focus();
},
{ once: true }
);
win.addEventListener("ZenURLBarClosed", onClosed, { once: true });
win.addEventListener("blur", onClosed, { once: true });
win.addEventListener("unload", () => observer.disconnect(), { once: true });
// Hacky, but used to prevent flashing and still being able to render
lazy.ZenWindowControl.show(win);
lazy.ZenWindowControl.hide(win);
win.gZenWorkspaces.promiseInitialized.then(() => {
win.windowUtils.suppressAnimation(false);
lazy.ZenWindowControl.show(win);
});
}
}

View File

@@ -22,7 +22,7 @@
&[zen-has-empty-tab="true"] {
/* Keep in sync with URLBAR_HEIGHT in ZenLittleWindow.sys.mjs */
--zen-minimum-window-height: 340px;
--zen-minimum-window-height: 40px;
min-width: unset !important;
#zen-appcontent-wrapper {

View File

@@ -206,12 +206,6 @@ class nsZenWindowSync {
* @param {Window} aWindow - The browser window that is about to be shown.
*/
#onWindowBeforeShow(aWindow) {
if (
aWindow.gZenWindowSync ||
aWindow.document.documentElement.hasAttribute("zen-unsynced-window")
) {
return;
}
if (aWindow._zenStartupLittleWindow) {
aWindow.document.documentElement.setAttribute(
"zen-little-window",
@@ -219,6 +213,12 @@ class nsZenWindowSync {
);
delete aWindow._zenStartupLittleWindow;
}
if (
aWindow.gZenWindowSync ||
aWindow.document.documentElement.hasAttribute("zen-unsynced-window")
) {
return;
}
this.log("Setting up window sync for window", aWindow);
// There are 2 possibilities to know if we are trying to open
// a new *unsynced* window:

View File

@@ -4,4 +4,5 @@
DIRS += [
"common",
"window-control",
]

View File

@@ -0,0 +1,47 @@
/* 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 "ZenWindowControl.h"
#include "WidgetUtils.h"
#include "nsCOMPtr.h"
#include "nsIWidget.h"
#include "nsPIDOMWindow.h"
namespace zen {
NS_IMPL_ISUPPORTS(ZenWindowControl, nsIZenWindowControl)
namespace {
static nsCOMPtr<nsIWidget> WidgetFor(mozIDOMWindowProxy* aWindow) {
if (!aWindow) return nullptr;
nsPIDOMWindowOuter* outer = nsPIDOMWindowOuter::From(aWindow);
if (!outer) return nullptr;
return mozilla::widget::WidgetUtils::DOMWindowToWidget(outer);
}
} // namespace
NS_IMETHODIMP
ZenWindowControl::Hide(mozIDOMWindowProxy* aWindow) {
nsCOMPtr<nsIWidget> widget = WidgetFor(aWindow);
if (!widget) return NS_ERROR_FAILURE;
// Hide first, then arm the lock so any subsequent Show(true) called
// from anywhere in the tree is rejected by the widget itself.
widget->Show(false);
widget->SetZenShowLocked(true);
return NS_OK;
}
NS_IMETHODIMP
ZenWindowControl::Show(mozIDOMWindowProxy* aWindow) {
nsCOMPtr<nsIWidget> widget = WidgetFor(aWindow);
if (!widget) return NS_ERROR_FAILURE;
widget->SetZenShowLocked(false);
widget->Show(true);
return NS_OK;
}
} // namespace zen

View File

@@ -0,0 +1,25 @@
/* 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/. */
#ifndef mozilla_ZenWindowControl_h_
#define mozilla_ZenWindowControl_h_
#include "nsIZenWindowControl.h"
namespace zen {
class ZenWindowControl final : public nsIZenWindowControl {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIZENWINDOWCONTROL
ZenWindowControl() = default;
private:
~ZenWindowControl() = default;
};
} // namespace zen
#endif

View File

@@ -0,0 +1,14 @@
# 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/.
Classes = [
{
'cid': '{c1d2e3f4-9abc-4def-8123-456789abcdef}',
'interfaces': ['nsIZenWindowControl'],
'contract_ids': ['@mozilla.org/zen/window-control;1'],
'type': 'zen::ZenWindowControl',
'headers': ['mozilla/ZenWindowControl.h'],
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
},
]

View File

@@ -0,0 +1,26 @@
# 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/.
XPIDL_SOURCES += [
"nsIZenWindowControl.idl",
]
EXPORTS.mozilla += [
"ZenWindowControl.h",
]
SOURCES += [
"ZenWindowControl.cpp",
]
XPCOM_MANIFESTS += [
"components.conf",
]
LOCAL_INCLUDES += [
"/widget",
]
FINAL_LIBRARY = "xul"
XPIDL_MODULE = "zen_window_control"

View File

@@ -0,0 +1,34 @@
/* 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 "nsISupports.idl"
interface mozIDOMWindowProxy;
/**
* @brief Toggle the OS-level visibility of a chrome window without
* destroying it. Used by little-window flow to keep a window off-screen
* while the urlbar is wired up.
*
* Hide() locks the widget at the component level; once locked, the
* widget remains hidden until Show() is called. Calling Show() on a
* widget that is not currently locked is a no-op so external paths
* can't accidentally un-hide a widget that wasn't hidden through this
* service.
*/
[scriptable, uuid(c1d2e3f4-9abc-4def-8123-456789abcdef)]
interface nsIZenWindowControl : nsISupports {
/**
* Hide the window at the widget level
* (NSWindow.orderOut / ShowWindow(SW_HIDE) / gtk_widget_hide) and
* mark its widget as locked-hidden. Idempotent.
*/
void hide(in mozIDOMWindowProxy aWindow);
/**
* If the window's widget is currently locked-hidden by this service,
* un-hide it and clear the lock. Otherwise no-op.
*/
void show(in mozIDOMWindowProxy aWindow);
};