From e3b0295e366e4a08f374efe035086f8dc817bead Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Wed, 29 Apr 2026 02:21:22 +0200 Subject: [PATCH] no-bug: Continue work --- .../urlbar/content/UrlbarInput-mjs.patch | 27 ++--- src/widget/cocoa/nsCocoaWindow-mm.patch | 27 +++++ src/widget/gtk/nsWindow-cpp.patch | 18 +++ src/widget/nsIWidget-h.patch | 28 +++++ src/widget/windows/nsWindow-cpp.patch | 59 +++++++++ src/zen/common/modules/ZenUIManager.mjs | 20 +++- src/zen/common/sys/ZenCustomizableUI.sys.mjs | 6 + src/zen/kbs/ZenKeyboardShortcuts.sys.mjs | 11 +- src/zen/little-window/ZenLittleWindow.sys.mjs | 113 +++++++++++------- src/zen/little-window/zen-little-window.css | 2 +- src/zen/sessionstore/ZenWindowSync.sys.mjs | 12 +- src/zen/toolkit/moz.build | 1 + .../window-control/ZenWindowControl.cpp | 47 ++++++++ .../toolkit/window-control/ZenWindowControl.h | 25 ++++ .../toolkit/window-control/components.conf | 14 +++ src/zen/toolkit/window-control/moz.build | 26 ++++ .../window-control/nsIZenWindowControl.idl | 34 ++++++ 17 files changed, 399 insertions(+), 71 deletions(-) create mode 100644 src/widget/cocoa/nsCocoaWindow-mm.patch create mode 100644 src/widget/gtk/nsWindow-cpp.patch create mode 100644 src/widget/nsIWidget-h.patch create mode 100644 src/widget/windows/nsWindow-cpp.patch create mode 100644 src/zen/toolkit/window-control/ZenWindowControl.cpp create mode 100644 src/zen/toolkit/window-control/ZenWindowControl.h create mode 100644 src/zen/toolkit/window-control/components.conf create mode 100644 src/zen/toolkit/window-control/moz.build create mode 100644 src/zen/toolkit/window-control/nsIZenWindowControl.idl diff --git a/src/browser/components/urlbar/content/UrlbarInput-mjs.patch b/src/browser/components/urlbar/content/UrlbarInput-mjs.patch index a71b52637..4fa56eeea 100644 --- a/src/browser/components/urlbar/content/UrlbarInput-mjs.patch +++ b/src/browser/components/urlbar/content/UrlbarInput-mjs.patch @@ -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 = diff --git a/src/widget/cocoa/nsCocoaWindow-mm.patch b/src/widget/cocoa/nsCocoaWindow-mm.patch new file mode 100644 index 000000000..40ca4ab27 --- /dev/null +++ b/src/widget/cocoa/nsCocoaWindow-mm.patch @@ -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]; diff --git a/src/widget/gtk/nsWindow-cpp.patch b/src/widget/gtk/nsWindow-cpp.patch new file mode 100644 index 000000000..723edc10e --- /dev/null +++ b/src/widget/gtk/nsWindow-cpp.patch @@ -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(); + } + diff --git a/src/widget/nsIWidget-h.patch b/src/widget/nsIWidget-h.patch new file mode 100644 index 000000000..d145c3fb4 --- /dev/null +++ b/src/widget/nsIWidget-h.patch @@ -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 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) diff --git a/src/widget/windows/nsWindow-cpp.patch b/src/widget/windows/nsWindow-cpp.patch new file mode 100644 index 000000000..d3c651898 --- /dev/null +++ b/src/widget/windows/nsWindow-cpp.patch @@ -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) { diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index cb79b917a..45d9fd40e 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -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); diff --git a/src/zen/common/sys/ZenCustomizableUI.sys.mjs b/src/zen/common/sys/ZenCustomizableUI.sys.mjs index 7a48b7f5c..ed832b367 100644 --- a/src/zen/common/sys/ZenCustomizableUI.sys.mjs +++ b/src/zen/common/sys/ZenCustomizableUI.sys.mjs @@ -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"; diff --git a/src/zen/kbs/ZenKeyboardShortcuts.sys.mjs b/src/zen/kbs/ZenKeyboardShortcuts.sys.mjs index 57a928402..73ae647a8 100644 --- a/src/zen/kbs/ZenKeyboardShortcuts.sys.mjs +++ b/src/zen/kbs/ZenKeyboardShortcuts.sys.mjs @@ -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() { diff --git a/src/zen/little-window/ZenLittleWindow.sys.mjs b/src/zen/little-window/ZenLittleWindow.sys.mjs index 371e1fe52..ff31c5654 100644 --- a/src/zen/little-window/ZenLittleWindow.sys.mjs +++ b/src/zen/little-window/ZenLittleWindow.sys.mjs @@ -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); + }); } } diff --git a/src/zen/little-window/zen-little-window.css b/src/zen/little-window/zen-little-window.css index 53406145d..c2c1af026 100644 --- a/src/zen/little-window/zen-little-window.css +++ b/src/zen/little-window/zen-little-window.css @@ -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 { diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index 595824727..c3176fdb8 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -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: diff --git a/src/zen/toolkit/moz.build b/src/zen/toolkit/moz.build index 00ec74c19..f93213dc5 100644 --- a/src/zen/toolkit/moz.build +++ b/src/zen/toolkit/moz.build @@ -4,4 +4,5 @@ DIRS += [ "common", + "window-control", ] diff --git a/src/zen/toolkit/window-control/ZenWindowControl.cpp b/src/zen/toolkit/window-control/ZenWindowControl.cpp new file mode 100644 index 000000000..d988d04d2 --- /dev/null +++ b/src/zen/toolkit/window-control/ZenWindowControl.cpp @@ -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 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 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 widget = WidgetFor(aWindow); + if (!widget) return NS_ERROR_FAILURE; + widget->SetZenShowLocked(false); + widget->Show(true); + return NS_OK; +} + +} // namespace zen diff --git a/src/zen/toolkit/window-control/ZenWindowControl.h b/src/zen/toolkit/window-control/ZenWindowControl.h new file mode 100644 index 000000000..cc15e1b78 --- /dev/null +++ b/src/zen/toolkit/window-control/ZenWindowControl.h @@ -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 diff --git a/src/zen/toolkit/window-control/components.conf b/src/zen/toolkit/window-control/components.conf new file mode 100644 index 000000000..537cc2eed --- /dev/null +++ b/src/zen/toolkit/window-control/components.conf @@ -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, + }, +] diff --git a/src/zen/toolkit/window-control/moz.build b/src/zen/toolkit/window-control/moz.build new file mode 100644 index 000000000..a6925e487 --- /dev/null +++ b/src/zen/toolkit/window-control/moz.build @@ -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" diff --git a/src/zen/toolkit/window-control/nsIZenWindowControl.idl b/src/zen/toolkit/window-control/nsIZenWindowControl.idl new file mode 100644 index 000000000..255615c79 --- /dev/null +++ b/src/zen/toolkit/window-control/nsIZenWindowControl.idl @@ -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); +};