mirror of
https://github.com/zen-browser/desktop.git
synced 2026-05-27 15:25:09 +00:00
Compare commits
7 Commits
1.19.13b
...
little-zen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd9381997 | ||
|
|
685cddf7c2 | ||
|
|
27f40393d5 | ||
|
|
f99b8af86d | ||
|
|
e3b0295e36 | ||
|
|
d15f5331ff | ||
|
|
cccbcf662e |
@@ -151,3 +151,5 @@ zen-window-sync-migration-dialog-accept = Got It
|
||||
zen-appmenu-new-blank-window =
|
||||
.label = New blank window
|
||||
|
||||
zen-spaces-search-placeholder =
|
||||
.placeholder = Search your spaces...
|
||||
|
||||
@@ -7,3 +7,6 @@
|
||||
|
||||
- name: zen.keyboard.shortcuts.disable-mainkeyset-clear
|
||||
value: false # for debugging
|
||||
|
||||
- name: zen.keyboard.shortcuts.global.enabled
|
||||
value: true
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-welcome.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-media-controls.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-download-box-animation.css" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-little-window.css" />
|
||||
</linkset>
|
||||
|
||||
# Startup "preloaded" scripts that requre globals such as gBrowser and gURLBar
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include ../../../zen/mods/jar.inc.mn
|
||||
#include ../../../zen/spaces/jar.inc.mn
|
||||
#include ../../../zen/tabs/jar.inc.mn
|
||||
#include ../../../zen/kbs/jar.inc.mn
|
||||
#include ../../../zen/glance/jar.inc.mn
|
||||
#include ../../../zen/folders/jar.inc.mn
|
||||
#include ../../../zen/welcome/jar.inc.mn
|
||||
@@ -20,3 +19,4 @@
|
||||
#include ../../../zen/fonts/jar.inc.mn
|
||||
#include ../../../zen/boosts/jar.inc.mn
|
||||
#include ../../../zen/live-folders/jar.inc.mn
|
||||
#include ../../../zen/little-window/jar.inc.mn
|
||||
|
||||
@@ -68,4 +68,6 @@
|
||||
<command id="cmd_zenNewLiveFolder" />
|
||||
|
||||
<command id="cmd_zenDuplicateTab" />
|
||||
|
||||
<command id="cmd_zenNewLittleWindow" />
|
||||
</commandset>
|
||||
|
||||
23
src/browser/base/content/zen-panels/spaces-search.inc
Normal file
23
src/browser/base/content/zen-panels/spaces-search.inc
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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/.
|
||||
|
||||
<panel id="zen-spaces-popup"
|
||||
nonnativepopover="true"
|
||||
type="arrow"
|
||||
orient="vertical"
|
||||
side="bottom"
|
||||
hidden="true"
|
||||
consumeoutsideclicks="never">
|
||||
<hbox class="zen-spaces-list-header" flex="1">
|
||||
<image class="zen-spaces-list-search-icon" src="chrome://global/skin/icons/search-glass.svg"/>
|
||||
<html:input id="zen-spaces-list-search"
|
||||
data-l10n-id="zen-spaces-search-placeholder"
|
||||
type="search" />
|
||||
</hbox>
|
||||
<scrollbox class="zen-spaces-list-scrollbox" flex="1">
|
||||
<vbox id="zen-spaces-list"></vbox>
|
||||
<hbox id="zen-spaces-search-no-results" hidden="true" flex="1"
|
||||
data-l10n-id="zen-spaces-search-no-results" />
|
||||
</scrollbox>
|
||||
</panel>
|
||||
@@ -5,6 +5,7 @@
|
||||
#include zen-panels/theme-picker.inc
|
||||
#include zen-panels/emojis-picker.inc
|
||||
#include zen-panels/folders-search.inc
|
||||
#include zen-panels/spaces-search.inc
|
||||
#include zen-panels/site-data.inc
|
||||
|
||||
#include zen-panels/popups.inc
|
||||
|
||||
@@ -8,13 +8,16 @@ const { nsZenMultiWindowFeature } = ChromeUtils.importESModule(
|
||||
{ global: "current" }
|
||||
);
|
||||
|
||||
const { nsKeyShortcutModifiers } = ChromeUtils.importESModule(
|
||||
"chrome://browser/content/zen-components/ZenKeyboardShortcuts.mjs",
|
||||
{
|
||||
global: "current",
|
||||
}
|
||||
const {
|
||||
nsKeyShortcutModifiers,
|
||||
ZenKeyboardShortcuts,
|
||||
VALID_SHORTCUT_GROUPS,
|
||||
} = ChromeUtils.importESModule(
|
||||
"resource:///modules/zen/ZenKeyboardShortcuts.sys.mjs"
|
||||
);
|
||||
|
||||
const gZenKeyboardShortcutsManager = ZenKeyboardShortcuts.manager;
|
||||
|
||||
var gZenMarketplaceManager = {
|
||||
async init() {
|
||||
const checkForUpdates = document.getElementById("zenThemeMarketplaceCheckForUpdates");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs
|
||||
index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f5c811a82 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({
|
||||
@@ -75,7 +75,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
}
|
||||
|
||||
if (isCanonized) {
|
||||
@@ -2696,6 +2728,42 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2696,6 +2728,45 @@ export class UrlbarInput extends HTMLElement {
|
||||
await this.#updateLayoutBreakoutDimensions();
|
||||
}
|
||||
|
||||
@@ -84,7 +84,10 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
+ }
|
||||
+
|
||||
+ get zenUrlbarBehavior() {
|
||||
+ if (this.document.documentElement.hasAttribute("inDOMFullscreen")) {
|
||||
+ if (
|
||||
+ this.document.documentElement.hasAttribute("inDOMFullscreen") ||
|
||||
+ this.document.documentElement.hasAttribute("zen-little-window")
|
||||
+ ) {
|
||||
+ return "float";
|
||||
+ }
|
||||
+ return lazy.ZEN_URLBAR_BEHAVIOR;
|
||||
@@ -118,7 +121,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
startLayoutExtend() {
|
||||
if (!this.#allowBreakout || this.hasAttribute("breakout-extend")) {
|
||||
// Do not expand if the Urlbar does not support being expanded or it is
|
||||
@@ -2710,6 +2778,13 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2710,6 +2781,13 @@ export class UrlbarInput extends HTMLElement {
|
||||
|
||||
this.setAttribute("breakout-extend", "true");
|
||||
|
||||
@@ -132,7 +135,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
// Enable the animation only after the first extend call to ensure it
|
||||
// doesn't run when opening a new window.
|
||||
if (!this.hasAttribute("breakout-extend-animate")) {
|
||||
@@ -2729,6 +2804,27 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2729,6 +2807,27 @@ export class UrlbarInput extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,7 +163,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
this.removeAttribute("breakout-extend");
|
||||
this.#updateTextboxPosition();
|
||||
}
|
||||
@@ -2759,7 +2855,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2759,7 +2858,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
forceUnifiedSearchButtonAvailable = false
|
||||
) {
|
||||
let prevState = this.getAttribute("pageproxystate");
|
||||
@@ -169,7 +172,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
this.setAttribute("pageproxystate", state);
|
||||
this._inputContainer.setAttribute("pageproxystate", state);
|
||||
this._identityBox?.setAttribute("pageproxystate", state);
|
||||
@@ -3031,10 +3127,12 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3031,10 +3130,12 @@ export class UrlbarInput extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
this.style.top = px(
|
||||
@@ -182,7 +185,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3093,9 +3191,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3093,9 +3194,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,7 +197,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
);
|
||||
this.style.setProperty(
|
||||
"--urlbar-height",
|
||||
@@ -3597,6 +3696,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3597,6 +3699,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
|
||||
_toggleActionOverride(event) {
|
||||
@@ -202,7 +205,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (
|
||||
event.keyCode == KeyEvent.DOM_VK_SHIFT ||
|
||||
event.keyCode == KeyEvent.DOM_VK_ALT ||
|
||||
@@ -3709,8 +3809,8 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3709,8 +3812,8 @@ export class UrlbarInput extends HTMLElement {
|
||||
if (!this.#isAddressbar) {
|
||||
return val;
|
||||
}
|
||||
@@ -213,7 +216,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
: val;
|
||||
// Only trim value if the directionality doesn't change to RTL and we're not
|
||||
// showing a strikeout https protocol.
|
||||
@@ -4006,6 +4106,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4006,6 +4109,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
resultDetails = null,
|
||||
browser = this.window.gBrowser.selectedBrowser
|
||||
) {
|
||||
@@ -221,7 +224,19 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (this.#isAddressbar) {
|
||||
this.#prepareAddressbarLoad(
|
||||
url,
|
||||
@@ -4117,6 +4218,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4088,6 +4192,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
* @returns {"current" | "tabshifted" | "tab" | "save" | "window"}
|
||||
*/
|
||||
_whereToOpen(event) {
|
||||
+ if (this.document.documentElement.hasAttribute("zen-little-window")) {
|
||||
+ // Little windows are single-tab popups -- never spawn extra tabs
|
||||
+ // or new windows from the urlbar.
|
||||
+ return "current";
|
||||
+ }
|
||||
let isKeyboardEvent = KeyboardEvent.isInstance(event);
|
||||
let reuseEmpty = isKeyboardEvent;
|
||||
let where = undefined;
|
||||
@@ -4117,6 +4226,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
reuseEmpty = true;
|
||||
}
|
||||
@@ -232,7 +247,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (
|
||||
where == "tab" &&
|
||||
reuseEmpty &&
|
||||
@@ -4124,6 +4229,9 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4124,6 +4237,9 @@ export class UrlbarInput extends HTMLElement {
|
||||
) {
|
||||
where = "current";
|
||||
}
|
||||
@@ -242,7 +257,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
return where;
|
||||
}
|
||||
|
||||
@@ -4378,6 +4486,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4378,6 +4494,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
this.setResultForCurrentValue(null);
|
||||
this.handleCommand();
|
||||
this.controller.clearLastQueryContextCache();
|
||||
@@ -250,7 +265,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
|
||||
this._suppressStartQuery = false;
|
||||
});
|
||||
@@ -4385,7 +4494,6 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4385,7 +4502,6 @@ export class UrlbarInput extends HTMLElement {
|
||||
contextMenu.addEventListener("popupshowing", () => {
|
||||
// Close the results pane when the input field contextual menu is open,
|
||||
// because paste and go doesn't want a result selection.
|
||||
@@ -258,7 +273,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
|
||||
let controller =
|
||||
this.document.commandDispatcher.getControllerForCommand("cmd_paste");
|
||||
@@ -4541,7 +4649,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4541,7 +4657,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
if (!engineName && !source && !this.hasAttribute("searchmode")) {
|
||||
return;
|
||||
}
|
||||
@@ -271,7 +286,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (this._searchModeIndicatorTitle) {
|
||||
this._searchModeIndicatorTitle.textContent = "";
|
||||
this._searchModeIndicatorTitle.removeAttribute("data-l10n-id");
|
||||
@@ -4851,6 +4963,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4851,6 +4971,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
|
||||
this.document.l10n.setAttributes(
|
||||
this.inputField,
|
||||
@@ -279,7 +294,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
l10nId,
|
||||
l10nId == "urlbar-placeholder-with-name"
|
||||
? { name: engineName }
|
||||
@@ -4964,6 +5077,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4964,6 +5085,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
|
||||
_on_click(event) {
|
||||
@@ -291,7 +306,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
switch (event.target) {
|
||||
case this.inputField:
|
||||
case this._inputContainer:
|
||||
@@ -5042,7 +5160,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5042,7 +5168,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +315,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
this.view.autoOpen({ event });
|
||||
} else {
|
||||
if (this._untrimOnFocusAfterKeydown) {
|
||||
@@ -5082,9 +5200,16 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5082,9 +5208,16 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
|
||||
_on_mousedown(event) {
|
||||
@@ -318,7 +333,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (
|
||||
event.composedTarget != this.inputField &&
|
||||
event.composedTarget != this._inputContainer
|
||||
@@ -5094,6 +5219,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5094,6 +5227,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
|
||||
this.focusedViaMousedown = !this.focused;
|
||||
this._preventClickSelectsAll = this.focused;
|
||||
@@ -329,7 +344,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
|
||||
// Keep the focus status, since the attribute may be changed
|
||||
// upon calling this.focus().
|
||||
@@ -5129,7 +5258,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.
|
||||
@@ -338,7 +353,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5411,7 +5540,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 =
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
diff --git a/browser/modules/BrowserWindowTracker.sys.mjs b/browser/modules/BrowserWindowTracker.sys.mjs
|
||||
index 9aecab66d8f23fac9f16cea2120a5fe903ae1122..692f2bfe3899a58925789503a6bb2a547cdbf7f3 100644
|
||||
index 9aecab66d8f23fac9f16cea2120a5fe903ae1122..e023c27bcb027d29ba9b3469eca5957d42040c46 100644
|
||||
--- a/browser/modules/BrowserWindowTracker.sys.mjs
|
||||
+++ b/browser/modules/BrowserWindowTracker.sys.mjs
|
||||
@@ -330,6 +330,7 @@ export const BrowserWindowTracker = {
|
||||
@@ -210,7 +210,8 @@ export const BrowserWindowTracker = {
|
||||
!win.closed &&
|
||||
(options.allowPopups || win.toolbar.visible) &&
|
||||
(options.allowTaskbarTabs ||
|
||||
- !win.document.documentElement.hasAttribute("taskbartab")) &&
|
||||
+ (!win.document.documentElement.hasAttribute("taskbartab") &&
|
||||
+ !win.document.documentElement.hasAttribute("zen-little-window"))) &&
|
||||
(!("private" in options) ||
|
||||
lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
|
||||
lazy.PrivateBrowsingUtils.isWindowPrivate(win) == options.private)
|
||||
@@ -330,6 +331,8 @@ export const BrowserWindowTracker = {
|
||||
args = null,
|
||||
remote = undefined,
|
||||
fission = undefined,
|
||||
+ zenSyncedWindow = true,
|
||||
+ zenLittleWindow = false,
|
||||
} = options;
|
||||
|
||||
args = lazy.AIWindow.handleAIWindowOptions(options);
|
||||
@@ -386,6 +387,12 @@ export const BrowserWindowTracker = {
|
||||
@@ -386,6 +389,16 @@ export const BrowserWindowTracker = {
|
||||
windowFeatures,
|
||||
args
|
||||
);
|
||||
+ win._zenStartupSyncFlag = Services.prefs.getBoolPref("zen.window-sync.prefer-unsynced-windows")
|
||||
+ ? (zenSyncedWindow ? 'unsynced' : 'synced')
|
||||
+ : (zenSyncedWindow ? 'synced' : 'unsynced');
|
||||
+ if (zenLittleWindow) {
|
||||
+ win._zenStartupLittleWindow = true;
|
||||
+ win._zenStartupSyncFlag = 'unsynced';
|
||||
+ }
|
||||
+ if (win._zenStartupSyncFlag === 'unsynced' && openerWindow) {
|
||||
+ win._zenStartupUnsyncedUserContextId = openerWindow.gZenWorkspaces.getCurrentSpaceContainerId();
|
||||
+ }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/modules/URILoadingHelper.sys.mjs b/browser/modules/URILoadingHelper.sys.mjs
|
||||
index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee91598080730a 100644
|
||||
index a005dbdf84609622ef8054f73f78c0c290e76125..2d347ac12d53ae97b61750d421a489ce10af3376 100644
|
||||
--- a/browser/modules/URILoadingHelper.sys.mjs
|
||||
+++ b/browser/modules/URILoadingHelper.sys.mjs
|
||||
@@ -224,6 +224,7 @@ function openInWindow(url, params, sourceWindow) {
|
||||
@@ -19,7 +19,17 @@ index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee9159
|
||||
where = "tab";
|
||||
targetBrowser = null;
|
||||
} else if (
|
||||
@@ -974,7 +975,7 @@ export const URILoadingHelper = {
|
||||
@@ -724,7 +725,8 @@ export const URILoadingHelper = {
|
||||
"navigator:browser" &&
|
||||
(!skipPopups || top.toolbar.visible) &&
|
||||
(!skipTaskbarTabs ||
|
||||
- !top.document.documentElement.hasAttribute("taskbartab")) &&
|
||||
+ (!top.document.documentElement.hasAttribute("taskbartab") &&
|
||||
+ !top.document.documentElement.hasAttribute("zen-little-window"))) &&
|
||||
(!forceNonPrivate || !PrivateBrowsingUtils.isWindowPrivate(top))
|
||||
) {
|
||||
return top;
|
||||
@@ -974,7 +976,7 @@ export const URILoadingHelper = {
|
||||
ignoreQueryString || replaceQueryString,
|
||||
ignoreFragmentWhenComparing
|
||||
);
|
||||
@@ -28,7 +38,7 @@ index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee9159
|
||||
for (let i = 0; i < browsers.length; i++) {
|
||||
let browser = browsers[i];
|
||||
let browserCompare = cleanURL(
|
||||
@@ -1030,7 +1031,7 @@ export const URILoadingHelper = {
|
||||
@@ -1030,7 +1032,7 @@ export const URILoadingHelper = {
|
||||
);
|
||||
aSplitView.ownerGlobal.focus();
|
||||
} else {
|
||||
|
||||
27
src/widget/cocoa/nsCocoaWindow-mm.patch
Normal file
27
src/widget/cocoa/nsCocoaWindow-mm.patch
Normal 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];
|
||||
18
src/widget/gtk/nsWindow-cpp.patch
Normal file
18
src/widget/gtk/nsWindow-cpp.patch
Normal 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();
|
||||
}
|
||||
|
||||
28
src/widget/nsIWidget-h.patch
Normal file
28
src/widget/nsIWidget-h.patch
Normal 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)
|
||||
59
src/widget/windows/nsWindow-cpp.patch
Normal file
59
src/widget/windows/nsWindow-cpp.patch
Normal 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) {
|
||||
@@ -16,3 +16,5 @@ category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 applicatio
|
||||
#include common/Components.manifest
|
||||
#include sessionstore/SessionComponents.manifest
|
||||
#include live-folders/LiveFoldersComponents.manifest
|
||||
#include kbs/KbsComponents.manifest
|
||||
#include little-window/LittleWindowComponents.manifest
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"chrome://browser/content/zen-components/ZenCompactMode.mjs",
|
||||
"chrome://browser/content/ZenUIManager.mjs",
|
||||
"chrome://browser/content/zen-components/ZenMods.mjs",
|
||||
"chrome://browser/content/zen-components/ZenKeyboardShortcuts.mjs",
|
||||
"chrome://browser/content/zen-components/ZenSessionStore.mjs",
|
||||
"chrome://browser/content/zen-components/ZenMediaController.mjs",
|
||||
"chrome://browser/content/zen-components/ZenGlanceManager.mjs",
|
||||
|
||||
@@ -32,7 +32,6 @@ class ZenStartup {
|
||||
return;
|
||||
}
|
||||
this.#hasInitializedLayout = true;
|
||||
gZenKeyboardShortcutsManager.beforeInit();
|
||||
try {
|
||||
const kNavbarItems = ["nav-bar", "PersonalToolbar"];
|
||||
const kNewContainerId = "zen-appcontent-navbar-container";
|
||||
|
||||
@@ -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
|
||||
@@ -951,7 +958,9 @@ window.gZenVerticalTabsManager = {
|
||||
?.includes("toolbar") ||
|
||||
document.documentElement
|
||||
.getAttribute("chromehidden")
|
||||
?.includes("menubar")
|
||||
?.includes("menubar") ||
|
||||
document.documentElement.hasAttribute("zen-little-window") ||
|
||||
window._zenStartupLittleWindow
|
||||
);
|
||||
});
|
||||
|
||||
@@ -959,7 +968,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);
|
||||
@@ -1270,7 +1290,8 @@ window.gZenVerticalTabsManager = {
|
||||
|
||||
const topButtons = document.getElementById("zen-sidebar-top-buttons");
|
||||
const isCompactMode =
|
||||
gZenCompactModeManager.preference && !forCustomizableMode;
|
||||
(gZenCompactModeManager.preference && !forCustomizableMode) ||
|
||||
this.hidesTabsToolbar;
|
||||
const isVerticalTabs = this._prefsVerticalTabs || forCustomizableMode;
|
||||
const isSidebarExpanded = this._prefsSidebarExpanded || !isVerticalTabs;
|
||||
const isRightSide = this._prefsRightSide && isVerticalTabs;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
EXTRA_JS_MODULES += [
|
||||
"sys/ZenActorsManager.sys.mjs",
|
||||
"sys/ZenCustomizableUI.sys.mjs",
|
||||
"sys/ZenSearchPopup.sys.mjs",
|
||||
"sys/ZenUIMigration.sys.mjs",
|
||||
]
|
||||
|
||||
|
||||
@@ -20,9 +20,13 @@ body,
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
:root:is([inDOMFullscreen="true"], [chromehidden~="location"], [chromehidden~="toolbar"]) {
|
||||
:root:is(
|
||||
[inDOMFullscreen="true"], [chromehidden~="location"],
|
||||
[chromehidden~="toolbar"], [zen-little-window="true"]
|
||||
) {
|
||||
#navigator-toolbox,
|
||||
#zen-sidebar-splitter {
|
||||
#zen-sidebar-splitter,
|
||||
#zen-sidebar-top-buttons {
|
||||
visibility: collapse;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@
|
||||
}
|
||||
|
||||
#main-window[windowtype="navigator:browser"]:not([chromehidden~='toolbar']) {
|
||||
min-height: 495px !important;
|
||||
min-height: var(--zen-minimum-window-height, 495px) !important;
|
||||
|
||||
@media (-moz-windows-mica) or (-moz-platform: macos) or ((-moz-platform: linux) and
|
||||
-moz-pref('zen.widget.linux.transparency')) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
152
src/zen/common/sys/ZenSearchPopup.sys.mjs
Normal file
152
src/zen/common/sys/ZenSearchPopup.sys.mjs
Normal file
@@ -0,0 +1,152 @@
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Generic searchable XUL <panel> driver. One instance owns one panel +
|
||||
* its search input + list + (optional) no-results element, and exposes
|
||||
* `populate(items)` and `open(anchor, options)`.
|
||||
*
|
||||
* Each item passed to `populate` is `{ label, render?, onPick }`:
|
||||
* - label: string used for the data-label search filter.
|
||||
* - render: optional () => Element factory. If omitted a bare hbox
|
||||
* with a <label> is created.
|
||||
* - onPick: callback invoked when the item is clicked or activated
|
||||
* via Enter on the keyboard.
|
||||
*
|
||||
* The driver handles:
|
||||
* - filtering by lowercased substring match against data-label;
|
||||
* - arrow-key / Tab navigation with [selected="true"] highlight;
|
||||
* - Enter to activate the highlighted item;
|
||||
* - autofocus of the search input on popupshown;
|
||||
* - cleanup of all listeners on popuphidden.
|
||||
*/
|
||||
export class ZenSearchPopup {
|
||||
#panel = null;
|
||||
#searchInput = null;
|
||||
#list = null;
|
||||
#noResults = null;
|
||||
#itemSelector = ".zen-search-popup-item";
|
||||
#items = [];
|
||||
|
||||
/**
|
||||
* @param {object} aOptions
|
||||
* @param {Element} aOptions.panel The <panel> XUL element.
|
||||
* @param {Element} aOptions.searchInput The search <html:input>.
|
||||
* @param {Element} aOptions.list The container holding items.
|
||||
* @param {Element} [aOptions.noResults] Optional "no results" element.
|
||||
* @param {string} [aOptions.itemSelector] Per-item selector. Default
|
||||
* is `.zen-search-popup-item`; custom items must carry that class
|
||||
* or override this option.
|
||||
*/
|
||||
constructor({ panel, searchInput, list, noResults, itemSelector }) {
|
||||
this.#panel = panel;
|
||||
this.#searchInput = searchInput;
|
||||
this.#list = list;
|
||||
this.#noResults = noResults;
|
||||
if (itemSelector) this.#itemSelector = itemSelector;
|
||||
}
|
||||
|
||||
populate(items) {
|
||||
this.#items = items;
|
||||
this.#list.innerHTML = "";
|
||||
const doc = this.#panel.ownerDocument;
|
||||
for (const item of items) {
|
||||
let node;
|
||||
if (typeof item.render === "function") {
|
||||
node = item.render();
|
||||
} else {
|
||||
node = doc.createXULElement("hbox");
|
||||
const label = doc.createXULElement("label");
|
||||
label.setAttribute("value", item.label);
|
||||
node.appendChild(label);
|
||||
}
|
||||
node.classList.add(this.#itemSelector.replace(/^\./, ""));
|
||||
node.setAttribute("data-label", item.label);
|
||||
node.addEventListener("click", () => {
|
||||
this.#panel.hidePopup();
|
||||
item.onPick?.(item);
|
||||
});
|
||||
this.#list.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
open(anchor, { position = "after_end", onShown, onHidden } = {}) {
|
||||
if (!this.#panel || !this.#list) return;
|
||||
|
||||
this.#panel.hidden = false;
|
||||
|
||||
if (this.#searchInput) this.#searchInput.value = "";
|
||||
if (this.#noResults) this.#noResults.hidden = true;
|
||||
|
||||
const doc = this.#panel.ownerDocument;
|
||||
const sel = this.#itemSelector;
|
||||
|
||||
const onSearch = () => {
|
||||
const query = (this.#searchInput?.value || "").toLowerCase();
|
||||
let visible = 0;
|
||||
for (const item of this.#list.querySelectorAll(sel)) {
|
||||
const label = item.getAttribute("data-label")?.toLowerCase() || "";
|
||||
const found = label.includes(query);
|
||||
item.hidden = !found;
|
||||
if (found) visible++;
|
||||
}
|
||||
if (this.#noResults) this.#noResults.hidden = visible > 0;
|
||||
};
|
||||
if (this.#searchInput) {
|
||||
this.#searchInput.addEventListener("input", onSearch);
|
||||
}
|
||||
|
||||
const onKeyDown = event => {
|
||||
if (
|
||||
event.key === "ArrowDown" ||
|
||||
event.key === "ArrowUp" ||
|
||||
event.key === "Tab"
|
||||
) {
|
||||
event.preventDefault();
|
||||
const isUp =
|
||||
event.key === "ArrowUp" || (event.key === "Tab" && event.shiftKey);
|
||||
const items = Array.from(this.#list.querySelectorAll(sel)).filter(
|
||||
it => !it.hidden
|
||||
);
|
||||
if (!items.length) return;
|
||||
let index = items.indexOf(
|
||||
this.#list.querySelector(`${sel}[selected="true"]`)
|
||||
);
|
||||
index = isUp
|
||||
? (index - 1 + items.length) % items.length
|
||||
: (index + 1) % items.length;
|
||||
items.forEach(it => it.removeAttribute("selected"));
|
||||
const target = items[index];
|
||||
target.setAttribute("selected", "true");
|
||||
target.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
} else if (event.key === "Enter") {
|
||||
const sel2 = this.#list.querySelector(`${sel}[selected="true"]`);
|
||||
if (sel2) sel2.click();
|
||||
}
|
||||
};
|
||||
doc.addEventListener("keydown", onKeyDown);
|
||||
|
||||
const onPanelShown = event => {
|
||||
if (event.target !== this.#panel) return;
|
||||
this.#searchInput?.focus();
|
||||
this.#searchInput?.select?.();
|
||||
onShown?.();
|
||||
};
|
||||
this.#panel.addEventListener("popupshown", onPanelShown);
|
||||
|
||||
const onPanelHidden = event => {
|
||||
if (event.target !== this.#panel) return;
|
||||
if (this.#searchInput) {
|
||||
this.#searchInput.removeEventListener("input", onSearch);
|
||||
}
|
||||
doc.removeEventListener("keydown", onKeyDown);
|
||||
this.#panel.removeEventListener("popupshown", onPanelShown);
|
||||
this.#panel.removeEventListener("popuphidden", onPanelHidden);
|
||||
onHidden?.();
|
||||
};
|
||||
this.#panel.addEventListener("popuphidden", onPanelHidden);
|
||||
|
||||
this.#panel.openPopup(anchor, position);
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,13 @@ document.addEventListener(
|
||||
case "cmd_zenNewNavigatorUnsynced":
|
||||
OpenBrowserWindow({ zenSyncedWindow: false });
|
||||
break;
|
||||
case "cmd_zenNewLittleWindow": {
|
||||
const { ZenLittleWindow } = ChromeUtils.importESModule(
|
||||
"resource:///modules/zen/ZenLittleWindow.sys.mjs"
|
||||
);
|
||||
ZenLittleWindow.openLittleWindow(window);
|
||||
break;
|
||||
}
|
||||
case "cmd_zenNewLiveFolder": {
|
||||
const { ZenLiveFoldersManager } = ChromeUtils.importESModule(
|
||||
"resource:///modules/zen/ZenLiveFoldersManager.sys.mjs"
|
||||
|
||||
@@ -158,6 +158,14 @@
|
||||
) {
|
||||
separation = 0;
|
||||
}
|
||||
// Little windows are visually a single floating bar; we never want
|
||||
// chrome padding around them.
|
||||
if (
|
||||
document.documentElement.hasAttribute("zen-little-window") ||
|
||||
window._zenStartupLittleWindow
|
||||
) {
|
||||
separation = 0;
|
||||
}
|
||||
// In order to still use it on fullscreen, even if it's 0px, add .1px (almost invisible)
|
||||
separation = Math.max(kMinElementSeparation, separation);
|
||||
document.documentElement.style.setProperty(
|
||||
|
||||
@@ -126,6 +126,9 @@ window.gZenCompactModeManager = {
|
||||
},
|
||||
|
||||
get shouldBeCompact() {
|
||||
if (document.documentElement.hasAttribute("zen-little-window")) {
|
||||
return false;
|
||||
}
|
||||
return !document.documentElement
|
||||
.getAttribute("chromehidden")
|
||||
?.includes("toolbar");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import { nsZenDOMOperatedFeature } from "chrome://browser/content/zen-components/ZenCommonUtils.mjs";
|
||||
import { ZenSearchPopup } from "resource:///modules/ZenSearchPopup.sys.mjs";
|
||||
|
||||
function formatRelativeTime(timestamp) {
|
||||
const now = Date.now();
|
||||
@@ -42,6 +43,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
);
|
||||
|
||||
#popup = null;
|
||||
#searchPopup = null;
|
||||
#popupTimer = null;
|
||||
#mouseTimer = null;
|
||||
#lastHighlightedGroup = null;
|
||||
@@ -188,15 +190,12 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
|
||||
#initTabsPopup() {
|
||||
this.#popup = document.getElementById("zen-folder-tabs-popup");
|
||||
|
||||
const search = this.#popup.querySelector("#zen-folder-tabs-list-search");
|
||||
const tabsList = this.#popup.querySelector("#zen-folder-tabs-list");
|
||||
|
||||
search.addEventListener("input", () => {
|
||||
const query = search.value.toLowerCase();
|
||||
for (const item of tabsList.children) {
|
||||
item.hidden = !item.getAttribute("data-label").includes(query);
|
||||
}
|
||||
this.#searchPopup = new ZenSearchPopup({
|
||||
panel: this.#popup,
|
||||
searchInput: this.#popup.querySelector("#zen-folder-tabs-list-search"),
|
||||
list: this.#popup.querySelector("#zen-folder-tabs-list"),
|
||||
noResults: document.getElementById("zen-folder-tabs-search-no-results"),
|
||||
itemSelector: ".folders-tabs-list-item",
|
||||
});
|
||||
|
||||
this.#popup.addEventListener("mouseover", () => {
|
||||
@@ -788,93 +787,18 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
document.getElementById("zen-folder-tabs-search-no-results").hidden = true;
|
||||
this.#populateTabsList(activeGroup);
|
||||
|
||||
const search = this.#popup.querySelector("#zen-folder-tabs-list-search");
|
||||
document.l10n.setArgs(search, {
|
||||
"folder-name": activeGroup.name,
|
||||
});
|
||||
const tabsList = this.#popup.querySelector("#zen-folder-tabs-list");
|
||||
|
||||
const onSearchInput = () => {
|
||||
const query = search.value.toLowerCase();
|
||||
let foundTabs = 0;
|
||||
for (const item of tabsList.children) {
|
||||
const found = item.getAttribute("data-label").includes(query);
|
||||
item.hidden = !found;
|
||||
if (found) {
|
||||
foundTabs++;
|
||||
}
|
||||
}
|
||||
document.getElementById("zen-folder-tabs-search-no-results").hidden =
|
||||
foundTabs > 0;
|
||||
};
|
||||
search.addEventListener("input", onSearchInput);
|
||||
|
||||
const onKeyDown = event => {
|
||||
// Arrow down and up to navigate through the list
|
||||
if (
|
||||
event.key === "ArrowDown" ||
|
||||
event.key === "ArrowUp" ||
|
||||
event.key === "Tab"
|
||||
) {
|
||||
event.preventDefault();
|
||||
let isUp =
|
||||
event.key === "ArrowUp" || (event.key === "Tab" && event.shiftKey);
|
||||
const items = Array.from(tabsList.children).filter(
|
||||
item => !item.hidden
|
||||
);
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
let index = items.indexOf(
|
||||
tabsList.querySelector(".folders-tabs-list-item[selected]")
|
||||
);
|
||||
if (!isUp) {
|
||||
index = (index + 1) % items.length;
|
||||
} else {
|
||||
index = (index - 1 + items.length) % items.length;
|
||||
}
|
||||
items.forEach(item => item.removeAttribute("selected"));
|
||||
const targetItem = items[index];
|
||||
targetItem.setAttribute("selected", "true");
|
||||
targetItem.scrollIntoView({ block: "start", behavior: "smooth" });
|
||||
} else if (event.key === "Enter") {
|
||||
// Enter to select the currently highlighted item
|
||||
const highlightedItem = tabsList.querySelector(
|
||||
".folders-tabs-list-item[selected]"
|
||||
);
|
||||
if (highlightedItem) {
|
||||
highlightedItem.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
document.l10n.setArgs(
|
||||
this.#popup.querySelector("#zen-folder-tabs-list-search"),
|
||||
{ "folder-name": activeGroup.name }
|
||||
);
|
||||
|
||||
const target = event.target;
|
||||
target.setAttribute("open", true);
|
||||
|
||||
const handlePopupHidden = event => {
|
||||
if (event.target !== this.#popup) {
|
||||
return;
|
||||
}
|
||||
search.value = "";
|
||||
target.removeAttribute("open");
|
||||
search.removeEventListener("input", onSearchInput);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
|
||||
this.#popup.addEventListener(
|
||||
"popupshown",
|
||||
() => {
|
||||
search.focus();
|
||||
search.select();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
this.#popup.addEventListener("popuphidden", handlePopupHidden, {
|
||||
once: true,
|
||||
this.#searchPopup.open(target, {
|
||||
position: this.#searchPopupOptions,
|
||||
onHidden: () => target.removeAttribute("open"),
|
||||
});
|
||||
this.#popup.openPopup(target, this.#searchPopupOptions);
|
||||
}
|
||||
|
||||
get #searchPopupOptions() {
|
||||
|
||||
6
src/zen/kbs/KbsComponents.manifest
Normal file
6
src/zen/kbs/KbsComponents.manifest
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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/.
|
||||
|
||||
category browser-before-ui-startup resource:///modules/zen/ZenKeyboardShortcuts.sys.mjs ZenKeyboardShortcuts.init
|
||||
category browser-quit-application-granted resource:///modules/zen/ZenKeyboardShortcuts.sys.mjs ZenKeyboardShortcuts.uninit
|
||||
File diff suppressed because it is too large
Load Diff
134
src/zen/kbs/global-shortcuts/ZenGlobalShortcuts.cpp
Normal file
134
src/zen/kbs/global-shortcuts/ZenGlobalShortcuts.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
/* 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 "ZenGlobalShortcuts.h"
|
||||
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsGlobalWindowOuter.h"
|
||||
#include "nsIWindowMediator.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace zen {
|
||||
|
||||
ZenGlobalShortcuts* ZenGlobalShortcuts::sInstance = nullptr;
|
||||
|
||||
NS_IMPL_ISUPPORTS(ZenGlobalShortcuts, nsIZenGlobalShortcuts)
|
||||
|
||||
ZenGlobalShortcuts::ZenGlobalShortcuts() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!sInstance);
|
||||
sInstance = this;
|
||||
}
|
||||
|
||||
ZenGlobalShortcuts::~ZenGlobalShortcuts() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
for (auto& reg : mRegistrations) {
|
||||
NativeUnregister(reg);
|
||||
}
|
||||
mRegistrations.Clear();
|
||||
NativeShutdown();
|
||||
sInstance = nullptr;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ZenGlobalShortcuts::RegisterShortcut(const nsACString& aId,
|
||||
const nsACString& aKey,
|
||||
uint32_t aModifiers, bool* aRetVal) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
*aRetVal = false;
|
||||
|
||||
for (const auto& reg : mRegistrations) {
|
||||
if (reg.id.Equals(aId)) return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
Registration reg;
|
||||
reg.id = aId;
|
||||
reg.internalId = mNextInternalId++;
|
||||
|
||||
if (NS_FAILED(NativeRegister(reg, aKey, aModifiers))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mRegistrations.AppendElement(std::move(reg));
|
||||
*aRetVal = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ZenGlobalShortcuts::UnregisterShortcut(const nsACString& aId) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
for (size_t i = 0; i < mRegistrations.Length(); ++i) {
|
||||
if (mRegistrations[i].id.Equals(aId)) {
|
||||
NativeUnregister(mRegistrations[i]);
|
||||
mRegistrations.RemoveElementAt(i);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ZenGlobalShortcuts::UnregisterAll() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
for (auto& reg : mRegistrations) {
|
||||
NativeUnregister(reg);
|
||||
}
|
||||
mRegistrations.Clear();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const ZenGlobalShortcuts::Registration* ZenGlobalShortcuts::FindByInternalId(
|
||||
uint32_t aInternalId) const {
|
||||
for (const auto& reg : mRegistrations) {
|
||||
if (reg.internalId == aInternalId) return ®
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::OnNativeShortcut(uint32_t aInternalId) {
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
"ZenGlobalShortcuts::OnNativeShortcut",
|
||||
[aInternalId]() { OnNativeShortcut(aInternalId); }));
|
||||
return;
|
||||
}
|
||||
if (!sInstance) return;
|
||||
|
||||
const Registration* reg = sInstance->FindByInternalId(aInternalId);
|
||||
if (!reg) return;
|
||||
DispatchEventForId(reg->id);
|
||||
}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::DispatchEventForId(const nsACString& aId) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIWindowMediator> med = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
|
||||
if (!med) return;
|
||||
|
||||
nsCOMPtr<mozIDOMWindowProxy> mostRecent;
|
||||
med->GetMostRecentBrowserWindow(getter_AddRefs(mostRecent));
|
||||
if (!mostRecent) return;
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowOuter> outer = nsPIDOMWindowOuter::From(mostRecent);
|
||||
if (!outer) return;
|
||||
|
||||
RefPtr<mozilla::dom::Document> doc = outer->GetExtantDoc();
|
||||
if (!doc) return;
|
||||
|
||||
nsAutoString eventName;
|
||||
eventName.AssignLiteral(u"zen-global-shortcut-");
|
||||
AppendUTF8toUTF16(aId, eventName);
|
||||
|
||||
nsContentUtils::DispatchTrustedEvent(doc, nsGlobalWindowOuter::Cast(outer),
|
||||
eventName, mozilla::CanBubble::eYes,
|
||||
mozilla::Cancelable::eNo);
|
||||
}
|
||||
|
||||
} // namespace zen
|
||||
61
src/zen/kbs/global-shortcuts/ZenGlobalShortcuts.h
Normal file
61
src/zen/kbs/global-shortcuts/ZenGlobalShortcuts.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* 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_ZenGlobalShortcuts_h_
|
||||
#define mozilla_ZenGlobalShortcuts_h_
|
||||
|
||||
#include "nsIZenGlobalShortcuts.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace zen {
|
||||
|
||||
/**
|
||||
* @brief Singleton XPCOM service that registers OS-level global hotkeys
|
||||
* and dispatches a trusted DOM event on the most recently focused
|
||||
* browser window when one fires.
|
||||
*/
|
||||
class ZenGlobalShortcuts final : public nsIZenGlobalShortcuts {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIZENGLOBALSHORTCUTS
|
||||
|
||||
ZenGlobalShortcuts();
|
||||
|
||||
// Per-shortcut record. Public so the per-OS backend can read/write its
|
||||
// fields directly without going through accessors.
|
||||
struct Registration {
|
||||
nsCString id;
|
||||
uint32_t internalId = 0;
|
||||
void* nativeHandle = nullptr;
|
||||
};
|
||||
|
||||
// Called by the per-OS layer when a registered shortcut is triggered
|
||||
// by the system. Safe to call from any thread; bounces to the main
|
||||
// thread before touching DOM state.
|
||||
static void OnNativeShortcut(uint32_t aInternalId);
|
||||
|
||||
private:
|
||||
~ZenGlobalShortcuts();
|
||||
|
||||
static ZenGlobalShortcuts* sInstance;
|
||||
|
||||
const Registration* FindByInternalId(uint32_t aInternalId) const;
|
||||
static void DispatchEventForId(const nsACString& aId);
|
||||
|
||||
// Per-OS implementations live in cocoa/, windows/, or the stub.
|
||||
static nsresult NativeRegister(Registration& aReg, const nsACString& aKey,
|
||||
uint32_t aModifiers);
|
||||
static void NativeUnregister(Registration& aReg);
|
||||
static void NativeShutdown();
|
||||
|
||||
nsTArray<Registration> mRegistrations;
|
||||
uint32_t mNextInternalId = 1;
|
||||
};
|
||||
|
||||
} // namespace zen
|
||||
|
||||
#endif
|
||||
27
src/zen/kbs/global-shortcuts/ZenGlobalShortcutsStub.cpp
Normal file
27
src/zen/kbs/global-shortcuts/ZenGlobalShortcutsStub.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/* 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 "ZenGlobalShortcuts.h"
|
||||
|
||||
// Linux/other-toolkit fallback. A real implementation needs X11
|
||||
// XGrabKey on the root window or, on Wayland, the
|
||||
// org.freedesktop.portal.GlobalShortcuts portal over D-Bus. Until one
|
||||
// is added, registrations always fail and JS-side code can fall back
|
||||
// to in-window shortcuts.
|
||||
|
||||
namespace zen {
|
||||
|
||||
// static
|
||||
nsresult ZenGlobalShortcuts::NativeRegister(Registration&, const nsACString&,
|
||||
uint32_t) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::NativeUnregister(Registration&) {}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::NativeShutdown() {}
|
||||
|
||||
} // namespace zen
|
||||
210
src/zen/kbs/global-shortcuts/cocoa/ZenGlobalShortcutsCocoa.mm
Normal file
210
src/zen/kbs/global-shortcuts/cocoa/ZenGlobalShortcutsCocoa.mm
Normal file
@@ -0,0 +1,210 @@
|
||||
/* 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 "ZenGlobalShortcuts.h"
|
||||
|
||||
#include "mozilla/TextEvents.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
namespace zen {
|
||||
namespace {
|
||||
|
||||
using mozilla::CodeNameIndex;
|
||||
using mozilla::WidgetKeyboardEvent;
|
||||
|
||||
constexpr FourCharCode kZenHotKeySignature = 'zen ';
|
||||
|
||||
// Mozilla-internal aliases referenced by NativeKeyToDOMCodeName.inc but
|
||||
// not part of Carbon. Mirrors widget/cocoa/TextInputHandler.h so we
|
||||
// don't need to drag the whole header in.
|
||||
enum {
|
||||
kVK_PC_ContextMenu = 0x6E,
|
||||
kVK_Powerbook_KeypadEnter = 0x34,
|
||||
};
|
||||
|
||||
class MacGlobalShortcuts final {
|
||||
public:
|
||||
MacGlobalShortcuts() = delete;
|
||||
|
||||
static nsresult Register(ZenGlobalShortcuts::Registration& aReg,
|
||||
const nsACString& aKey, uint32_t aModifiers);
|
||||
static void Unregister(ZenGlobalShortcuts::Registration& aReg);
|
||||
static void Shutdown();
|
||||
|
||||
private:
|
||||
static bool EnsureHandler();
|
||||
static OSStatus HandleHotKey(EventHandlerCallRef, EventRef, void*);
|
||||
static bool ResolveKey(const nsACString& aKey, UInt32& aOut);
|
||||
static UInt32 ToCarbonModifiers(uint32_t aMods);
|
||||
|
||||
static EventHandlerUPP sUPP;
|
||||
static EventHandlerRef sHandler;
|
||||
};
|
||||
|
||||
EventHandlerUPP MacGlobalShortcuts::sUPP = nullptr;
|
||||
EventHandlerRef MacGlobalShortcuts::sHandler = nullptr;
|
||||
|
||||
// static
|
||||
OSStatus MacGlobalShortcuts::HandleHotKey(EventHandlerCallRef, EventRef inEvent,
|
||||
void*) {
|
||||
EventHotKeyID hkID;
|
||||
if (GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID,
|
||||
nullptr, sizeof(hkID), nullptr, &hkID) == noErr) {
|
||||
ZenGlobalShortcuts::OnNativeShortcut(hkID.id);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
// static
|
||||
bool MacGlobalShortcuts::EnsureHandler() {
|
||||
if (sHandler) return true;
|
||||
|
||||
sUPP = NewEventHandlerUPP(HandleHotKey);
|
||||
if (!sUPP) return false;
|
||||
|
||||
EventTypeSpec spec = {kEventClassKeyboard, kEventHotKeyPressed};
|
||||
OSStatus status =
|
||||
InstallApplicationEventHandler(sUPP, 1, &spec, nullptr, &sHandler);
|
||||
if (status != noErr) {
|
||||
DisposeEventHandlerUPP(sUPP);
|
||||
sUPP = nullptr;
|
||||
sHandler = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert the JS-friendly key string into a DOM code-name (e.g. "A" ->
|
||||
// "KeyA", "5" -> "Digit5", "F1"/"f1" -> "F1", "Space"/"space" -> "Space").
|
||||
// Returns false for inputs we don't accept.
|
||||
static bool ToDOMCodeName(const nsACString& aKey, nsAString& aOut) {
|
||||
aOut.Truncate();
|
||||
if (aKey.Length() == 1) {
|
||||
char c = aKey[0];
|
||||
if (c >= 'a' && c <= 'z') c = char(c - 32);
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
aOut.AssignLiteral(u"Key");
|
||||
} else if (c >= '0' && c <= '9') {
|
||||
aOut.AssignLiteral(u"Digit");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
aOut.Append(char16_t(c));
|
||||
return true;
|
||||
}
|
||||
// Multi-character: assume it's a DOM code name, normalized to leading
|
||||
// upper-case ("space" -> "Space", "f1" -> "F1").
|
||||
AppendUTF8toUTF16(aKey, aOut);
|
||||
if (!aOut.IsEmpty() && aOut[0] >= 'a' && aOut[0] <= 'z') {
|
||||
aOut.BeginWriting()[0] = char16_t(aOut[0] - 32);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct CodeIndexToMacKey {
|
||||
CodeNameIndex idx;
|
||||
UInt32 keyCode;
|
||||
};
|
||||
|
||||
// Generated from widget's mapping table. Order matches the .inc, so when
|
||||
// multiple native keys map to the same DOM code (e.g. NumpadEnter ->
|
||||
// kVK_ANSI_KeypadEnter and kVK_Powerbook_KeypadEnter), the first entry
|
||||
// wins -- which is the one we'd want to pass to RegisterEventHotKey.
|
||||
static constexpr CodeIndexToMacKey kCodeIndexToMacKeyTable[] = {
|
||||
#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
|
||||
{mozilla::aCodeNameIndex, static_cast<UInt32>(aNativeKey)},
|
||||
#include "NativeKeyToDOMCodeName.inc"
|
||||
#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
|
||||
};
|
||||
|
||||
// static
|
||||
bool MacGlobalShortcuts::ResolveKey(const nsACString& aKey, UInt32& aOut) {
|
||||
nsAutoString domCode;
|
||||
if (!ToDOMCodeName(aKey, domCode)) return false;
|
||||
|
||||
CodeNameIndex idx = WidgetKeyboardEvent::GetCodeNameIndex(domCode);
|
||||
if (idx == mozilla::CODE_NAME_INDEX_USE_STRING) return false;
|
||||
|
||||
for (const auto& entry : kCodeIndexToMacKeyTable) {
|
||||
if (entry.idx == idx) {
|
||||
aOut = entry.keyCode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
UInt32 MacGlobalShortcuts::ToCarbonModifiers(uint32_t aMods) {
|
||||
UInt32 m = 0;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_SHIFT) m |= shiftKey;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_CTRL) m |= controlKey;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_ALT) m |= optionKey;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_META) m |= cmdKey;
|
||||
return m;
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult MacGlobalShortcuts::Register(ZenGlobalShortcuts::Registration& aReg,
|
||||
const nsACString& aKey,
|
||||
uint32_t aModifiers) {
|
||||
if (!EnsureHandler()) return NS_ERROR_FAILURE;
|
||||
|
||||
UInt32 keyCode;
|
||||
if (!ResolveKey(aKey, keyCode)) return NS_ERROR_INVALID_ARG;
|
||||
|
||||
EventHotKeyID hkID;
|
||||
hkID.signature = kZenHotKeySignature;
|
||||
hkID.id = aReg.internalId;
|
||||
|
||||
EventHotKeyRef ref = nullptr;
|
||||
OSStatus status =
|
||||
RegisterEventHotKey(keyCode, ToCarbonModifiers(aModifiers), hkID,
|
||||
GetApplicationEventTarget(), 0, &ref);
|
||||
if (status != noErr || !ref) return NS_ERROR_FAILURE;
|
||||
|
||||
aReg.nativeHandle = static_cast<void*>(ref);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void MacGlobalShortcuts::Unregister(ZenGlobalShortcuts::Registration& aReg) {
|
||||
if (!aReg.nativeHandle) return;
|
||||
UnregisterEventHotKey(static_cast<EventHotKeyRef>(aReg.nativeHandle));
|
||||
aReg.nativeHandle = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
void MacGlobalShortcuts::Shutdown() {
|
||||
if (sHandler) {
|
||||
RemoveEventHandler(sHandler);
|
||||
sHandler = nullptr;
|
||||
}
|
||||
if (sUPP) {
|
||||
DisposeEventHandlerUPP(sUPP);
|
||||
sUPP = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
nsresult ZenGlobalShortcuts::NativeRegister(Registration& aReg,
|
||||
const nsACString& aKey,
|
||||
uint32_t aModifiers) {
|
||||
return MacGlobalShortcuts::Register(aReg, aKey, aModifiers);
|
||||
}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::NativeUnregister(Registration& aReg) {
|
||||
MacGlobalShortcuts::Unregister(aReg);
|
||||
}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::NativeShutdown() { MacGlobalShortcuts::Shutdown(); }
|
||||
|
||||
} // namespace zen
|
||||
18
src/zen/kbs/global-shortcuts/cocoa/moz.build
Normal file
18
src/zen/kbs/global-shortcuts/cocoa/moz.build
Normal file
@@ -0,0 +1,18 @@
|
||||
# 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/.
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
||||
|
||||
SOURCES += [
|
||||
"ZenGlobalShortcutsCocoa.mm",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"../",
|
||||
"/widget",
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
"-framework Carbon",
|
||||
]
|
||||
14
src/zen/kbs/global-shortcuts/components.conf
Normal file
14
src/zen/kbs/global-shortcuts/components.conf
Normal 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': '{b8e9f3a2-7c1d-4a5b-9e6f-3d8c2a1b5e74}',
|
||||
'interfaces': ['nsIZenGlobalShortcuts'],
|
||||
'contract_ids': ['@mozilla.org/zen/global-shortcuts;1'],
|
||||
'type': 'zen::ZenGlobalShortcuts',
|
||||
'headers': ['mozilla/ZenGlobalShortcuts.h'],
|
||||
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
|
||||
},
|
||||
]
|
||||
29
src/zen/kbs/global-shortcuts/moz.build
Normal file
29
src/zen/kbs/global-shortcuts/moz.build
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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 += [
|
||||
"nsIZenGlobalShortcuts.idl",
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
"ZenGlobalShortcuts.h",
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
"ZenGlobalShortcuts.cpp",
|
||||
]
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
"components.conf",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
|
||||
DIRS += ["cocoa"]
|
||||
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
|
||||
DIRS += ["windows"]
|
||||
else:
|
||||
SOURCES += ["ZenGlobalShortcutsStub.cpp"]
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
||||
XPIDL_MODULE = "zen_global_shortcuts"
|
||||
49
src/zen/kbs/global-shortcuts/nsIZenGlobalShortcuts.idl
Normal file
49
src/zen/kbs/global-shortcuts/nsIZenGlobalShortcuts.idl
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 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"
|
||||
|
||||
/**
|
||||
* @brief OS-level global keyboard shortcut registration for Zen.
|
||||
*
|
||||
* Registers shortcuts with the operating system so that pressing the
|
||||
* key combination triggers a callback even when no Zen window is
|
||||
* focused (or the application is in the background). When a shortcut
|
||||
* fires, a trusted DOM event is dispatched on the most recently
|
||||
* focused browser window. The event type is
|
||||
* "zen-global-shortcut-<id>", where <id> is the identifier passed at
|
||||
* registration time.
|
||||
*/
|
||||
[scriptable, uuid(b8e9f3a2-7c1d-4a5b-9e6f-3d8c2a1b5e74)]
|
||||
interface nsIZenGlobalShortcuts : nsISupports {
|
||||
const unsigned long MODIFIER_NONE = 0;
|
||||
const unsigned long MODIFIER_SHIFT = 1;
|
||||
const unsigned long MODIFIER_CTRL = 2;
|
||||
const unsigned long MODIFIER_ALT = 4;
|
||||
const unsigned long MODIFIER_META = 8;
|
||||
|
||||
/**
|
||||
* @brief Register a global keyboard shortcut.
|
||||
* @param aId Caller-chosen identifier; the dispatched event name will be
|
||||
* "zen-global-shortcut-" + aId. Must be unique across active
|
||||
* registrations.
|
||||
* @param aKey Key name. Supported: "A".."Z", "0".."9", "F1".."F12",
|
||||
* "Space".
|
||||
* @param aModifiers Bitmask of MODIFIER_* constants. On macOS, META
|
||||
* is Command; on Windows, META is the Windows key.
|
||||
* @return true if the OS accepted the registration.
|
||||
*/
|
||||
boolean registerShortcut(in ACString aId, in ACString aKey,
|
||||
in unsigned long aModifiers);
|
||||
|
||||
/**
|
||||
* @brief Unregister a previously registered shortcut by id.
|
||||
*/
|
||||
void unregisterShortcut(in ACString aId);
|
||||
|
||||
/**
|
||||
* @brief Unregister all shortcuts registered through this service.
|
||||
*/
|
||||
void unregisterAll();
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
/* 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 "ZenGlobalShortcuts.h"
|
||||
|
||||
#include "nsString.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace zen {
|
||||
namespace {
|
||||
|
||||
constexpr wchar_t kWindowClassName[] = L"ZenGlobalShortcutsWindow";
|
||||
|
||||
class WinGlobalShortcuts final {
|
||||
public:
|
||||
WinGlobalShortcuts() = delete;
|
||||
|
||||
static nsresult Register(ZenGlobalShortcuts::Registration& aReg,
|
||||
const nsACString& aKey, uint32_t aModifiers);
|
||||
static void Unregister(ZenGlobalShortcuts::Registration& aReg);
|
||||
static void Shutdown();
|
||||
|
||||
private:
|
||||
static bool EnsureWindow();
|
||||
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
||||
static bool ResolveKey(const nsACString& aKey, UINT& aOut);
|
||||
static UINT ToWinModifiers(uint32_t aMods);
|
||||
|
||||
static HWND sWindow;
|
||||
static ATOM sClass;
|
||||
};
|
||||
|
||||
HWND WinGlobalShortcuts::sWindow = nullptr;
|
||||
ATOM WinGlobalShortcuts::sClass = 0;
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK WinGlobalShortcuts::WndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
if (msg == WM_HOTKEY) {
|
||||
ZenGlobalShortcuts::OnNativeShortcut(static_cast<uint32_t>(wParam));
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
// static
|
||||
bool WinGlobalShortcuts::EnsureWindow() {
|
||||
if (sWindow) return true;
|
||||
|
||||
HINSTANCE module = GetModuleHandleW(nullptr);
|
||||
if (!sClass) {
|
||||
WNDCLASSEXW wc = {};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hInstance = module;
|
||||
wc.lpszClassName = kWindowClassName;
|
||||
sClass = RegisterClassExW(&wc);
|
||||
if (!sClass) return false;
|
||||
}
|
||||
|
||||
sWindow = CreateWindowExW(0, kWindowClassName, L"", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, nullptr, module, nullptr);
|
||||
return sWindow != nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
bool WinGlobalShortcuts::ResolveKey(const nsACString& aKey, UINT& aOut) {
|
||||
if (aKey.Length() == 1) {
|
||||
char c = aKey[0];
|
||||
if (c >= 'a' && c <= 'z') c = char(c - 32);
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
|
||||
aOut = static_cast<UINT>(c);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (aKey.LowerCaseEqualsLiteral("space")) {
|
||||
aOut = VK_SPACE;
|
||||
return true;
|
||||
}
|
||||
if ((aKey.Length() == 2 || aKey.Length() == 3) &&
|
||||
(aKey[0] == 'F' || aKey[0] == 'f')) {
|
||||
int n = aKey[1] - '0';
|
||||
if (n < 0 || n > 9) return false;
|
||||
if (aKey.Length() == 3) {
|
||||
int d = aKey[2] - '0';
|
||||
if (d < 0 || d > 9) return false;
|
||||
n = n * 10 + d;
|
||||
}
|
||||
if (n >= 1 && n <= 12) {
|
||||
aOut = VK_F1 + (n - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
UINT WinGlobalShortcuts::ToWinModifiers(uint32_t aMods) {
|
||||
UINT m = MOD_NOREPEAT;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_SHIFT) m |= MOD_SHIFT;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_CTRL) m |= MOD_CONTROL;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_ALT) m |= MOD_ALT;
|
||||
if (aMods & nsIZenGlobalShortcuts::MODIFIER_META) m |= MOD_WIN;
|
||||
return m;
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult WinGlobalShortcuts::Register(ZenGlobalShortcuts::Registration& aReg,
|
||||
const nsACString& aKey,
|
||||
uint32_t aModifiers) {
|
||||
if (!EnsureWindow()) return NS_ERROR_FAILURE;
|
||||
|
||||
UINT vk;
|
||||
if (!ResolveKey(aKey, vk)) return NS_ERROR_INVALID_ARG;
|
||||
|
||||
if (!RegisterHotKey(sWindow, static_cast<int>(aReg.internalId),
|
||||
ToWinModifiers(aModifiers), vk)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aReg.nativeHandle =
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(aReg.internalId));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void WinGlobalShortcuts::Unregister(ZenGlobalShortcuts::Registration& aReg) {
|
||||
if (!sWindow || !aReg.nativeHandle) return;
|
||||
UnregisterHotKey(
|
||||
sWindow,
|
||||
static_cast<int>(reinterpret_cast<uintptr_t>(aReg.nativeHandle)));
|
||||
aReg.nativeHandle = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
void WinGlobalShortcuts::Shutdown() {
|
||||
if (sWindow) {
|
||||
DestroyWindow(sWindow);
|
||||
sWindow = nullptr;
|
||||
}
|
||||
if (sClass) {
|
||||
UnregisterClassW(kWindowClassName, GetModuleHandleW(nullptr));
|
||||
sClass = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
nsresult ZenGlobalShortcuts::NativeRegister(Registration& aReg,
|
||||
const nsACString& aKey,
|
||||
uint32_t aModifiers) {
|
||||
return WinGlobalShortcuts::Register(aReg, aKey, aModifiers);
|
||||
}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::NativeUnregister(Registration& aReg) {
|
||||
WinGlobalShortcuts::Unregister(aReg);
|
||||
}
|
||||
|
||||
// static
|
||||
void ZenGlobalShortcuts::NativeShutdown() { WinGlobalShortcuts::Shutdown(); }
|
||||
|
||||
} // namespace zen
|
||||
@@ -2,4 +2,12 @@
|
||||
# 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/.
|
||||
|
||||
content/browser/zen-components/ZenKeyboardShortcuts.mjs (../../zen/kbs/ZenKeyboardShortcuts.mjs)
|
||||
FINAL_LIBRARY = "xul"
|
||||
|
||||
SOURCES += [
|
||||
"ZenGlobalShortcutsWindows.cpp",
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"../",
|
||||
]
|
||||
11
src/zen/kbs/moz.build
Normal file
11
src/zen/kbs/moz.build
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES.zen += [
|
||||
"ZenKeyboardShortcuts.sys.mjs",
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
"global-shortcuts",
|
||||
]
|
||||
6
src/zen/little-window/LittleWindowComponents.manifest
Normal file
6
src/zen/little-window/LittleWindowComponents.manifest
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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/.
|
||||
|
||||
category browser-before-ui-startup resource:///modules/zen/ZenLittleWindow.sys.mjs ZenLittleWindow.init
|
||||
category browser-quit-application-granted resource:///modules/zen/ZenLittleWindow.sys.mjs ZenLittleWindow.uninit
|
||||
141
src/zen/little-window/ZenLittleWindow.sys.mjs
Normal file
141
src/zen/little-window/ZenLittleWindow.sys.mjs
Normal file
@@ -0,0 +1,141 @@
|
||||
/* 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/. */
|
||||
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
import { ZenSpacesSearch } from "resource:///modules/zen/ZenSpacesSearch.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
lazy,
|
||||
"ZenWindowControl",
|
||||
"@mozilla.org/zen/window-control;1",
|
||||
Ci.nsIZenWindowControl
|
||||
);
|
||||
|
||||
const URLBAR_HEIGHT = 340;
|
||||
const URLBAR_WIDTH = 640;
|
||||
|
||||
const FEATURES =
|
||||
"titlebar,close,toolbar,location,personalbar=no,status,menubar=no," +
|
||||
`resizable,minimizable,scrollbars,width=${URLBAR_WIDTH},height=${URLBAR_HEIGHT},centerscreen`;
|
||||
|
||||
class nsZenLittleWindow {
|
||||
init() {}
|
||||
uninit() {}
|
||||
|
||||
/**
|
||||
* Open a fresh little window, or focus an existing empty one if there
|
||||
* already is a little window sitting on its empty tab.
|
||||
*
|
||||
* @param {Window} opener The browser window asking for the little window.
|
||||
* @returns {Window|null} The window that received focus.
|
||||
*/
|
||||
openLittleWindow(opener) {
|
||||
for (const win of this.#iterLittleWindows()) {
|
||||
if (this.#isOnEmptyTab(win)) {
|
||||
win.focus();
|
||||
return win;
|
||||
}
|
||||
}
|
||||
let win = opener.OpenBrowserWindow({
|
||||
zenLittleWindow: true,
|
||||
all: false,
|
||||
features: FEATURES,
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
#isLittleWindow(win) {
|
||||
return (
|
||||
!!win._zenStartupLittleWindow ||
|
||||
win.document?.documentElement?.hasAttribute("zen-little-window")
|
||||
);
|
||||
}
|
||||
|
||||
#isOnEmptyTab(win) {
|
||||
const tab = win.gBrowser?.selectedTab;
|
||||
return !!tab?.hasAttribute("zen-empty-tab");
|
||||
}
|
||||
|
||||
*#iterLittleWindows() {
|
||||
const en = Services.wm.getEnumerator("navigator:browser");
|
||||
while (en.hasMoreElements()) {
|
||||
const win = en.getNext();
|
||||
if (!win.closed && this.#isLittleWindow(win)) {
|
||||
yield win;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLittleWindow(win) {
|
||||
if (!this.#isLittleWindow(win)) {
|
||||
return;
|
||||
}
|
||||
ZenSpacesSearch.init(win);
|
||||
const observer = new win.ResizeObserver(entries => {
|
||||
if (win.closed) {
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
};
|
||||
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("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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const ZenLittleWindow = new nsZenLittleWindow();
|
||||
142
src/zen/little-window/ZenSpacesSearch.sys.mjs
Normal file
142
src/zen/little-window/ZenSpacesSearch.sys.mjs
Normal file
@@ -0,0 +1,142 @@
|
||||
/* 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/. */
|
||||
|
||||
import { ZenSearchPopup } from "resource:///modules/ZenSearchPopup.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
|
||||
});
|
||||
|
||||
/*
|
||||
* Owns the "send to a synced window" split-button that lives in the
|
||||
* nav-bar of a little window. Main click opens the urlbar's current
|
||||
* value in a fresh synced browser window using the active workspace;
|
||||
* the dropdown opens a ZenSearchPopup over #zen-spaces-popup so the
|
||||
* user can pick a different workspace to land in.
|
||||
*/
|
||||
class ZenSpacesSearchService {
|
||||
/**
|
||||
* Per-window setup.
|
||||
* @param {Window} aWindow A little window.
|
||||
*/
|
||||
init(aWindow) {
|
||||
if (!aWindow || aWindow._zenSpacesSearchInited) return;
|
||||
aWindow._zenSpacesSearchInited = true;
|
||||
|
||||
const doc = aWindow.document;
|
||||
const panel = doc.getElementById("zen-spaces-popup");
|
||||
if (!panel) return;
|
||||
|
||||
const popup = new ZenSearchPopup({
|
||||
panel,
|
||||
searchInput: doc.getElementById("zen-spaces-list-search"),
|
||||
list: doc.getElementById("zen-spaces-list"),
|
||||
noResults: doc.getElementById("zen-spaces-search-no-results"),
|
||||
itemSelector: ".zen-spaces-list-item",
|
||||
});
|
||||
|
||||
const parts = this.#injectButton(aWindow);
|
||||
if (!parts) return;
|
||||
const { button, main, dropmarker } = parts;
|
||||
|
||||
main.addEventListener("click", event => {
|
||||
if (event.button !== 0) return;
|
||||
this.#openInWorkspace(aWindow, null);
|
||||
});
|
||||
|
||||
dropmarker.addEventListener("click", event => {
|
||||
if (event.button !== 0) return;
|
||||
event.stopPropagation();
|
||||
this.#openSpacesPopup(aWindow, popup, button);
|
||||
});
|
||||
}
|
||||
|
||||
#injectButton(aWindow) {
|
||||
const doc = aWindow.document;
|
||||
const target = doc.getElementById("nav-bar-customization-target");
|
||||
if (!target) return null;
|
||||
|
||||
const button = doc.createXULElement("hbox");
|
||||
button.id = "zen-little-window-send-to-window";
|
||||
button.setAttribute("removable", "false");
|
||||
|
||||
const main = doc.createXULElement("hbox");
|
||||
main.classList.add("zen-stw-main");
|
||||
|
||||
const prefix = doc.createXULElement("label");
|
||||
prefix.classList.add("zen-stw-prefix");
|
||||
prefix.setAttribute(
|
||||
"data-l10n-id",
|
||||
"zen-little-window-send-to-window-prefix"
|
||||
);
|
||||
|
||||
const spaceName = doc.createXULElement("label");
|
||||
spaceName.classList.add("zen-stw-space-name");
|
||||
spaceName.setAttribute(
|
||||
"value",
|
||||
aWindow.gZenWorkspaces?.getActiveWorkspaceFromCache?.()?.name || ""
|
||||
);
|
||||
|
||||
main.appendChild(prefix);
|
||||
main.appendChild(spaceName);
|
||||
|
||||
const separator = doc.createXULElement("hbox");
|
||||
separator.classList.add("zen-stw-separator");
|
||||
|
||||
const dropmarker = doc.createXULElement("hbox");
|
||||
dropmarker.classList.add("zen-stw-dropmarker");
|
||||
const dropIcon = doc.createXULElement("image");
|
||||
dropIcon.classList.add("zen-stw-dropmarker-icon");
|
||||
dropmarker.appendChild(dropIcon);
|
||||
|
||||
button.appendChild(main);
|
||||
button.appendChild(separator);
|
||||
button.appendChild(dropmarker);
|
||||
|
||||
target.appendChild(button);
|
||||
return { button, main, dropmarker, spaceName };
|
||||
}
|
||||
|
||||
#openSpacesPopup(aWindow, popup, anchor) {
|
||||
const workspaces = lazy.ZenSessionStore.getClonedSpaces();
|
||||
|
||||
popup.populate(
|
||||
workspaces.map(space => ({
|
||||
label: space.name || space.uuid,
|
||||
render: () => {
|
||||
const node = aWindow.document.createXULElement("hbox");
|
||||
const label = aWindow.document.createXULElement("label");
|
||||
label.setAttribute("value", space.name || space.uuid);
|
||||
label.classList.add("zen-spaces-list-item-label");
|
||||
node.appendChild(label);
|
||||
return node;
|
||||
},
|
||||
onPick: () => this.#openInWorkspace(aWindow, space.uuid),
|
||||
}))
|
||||
);
|
||||
popup.open(anchor);
|
||||
}
|
||||
|
||||
#openInWorkspace(aWindow, workspaceUuid) {
|
||||
const url = aWindow.gURLBar?.value?.trim();
|
||||
if (!url) return;
|
||||
|
||||
const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
const urlString = Cc["@mozilla.org/supports-string;1"].createInstance(
|
||||
Ci.nsISupportsString
|
||||
);
|
||||
urlString.data = url;
|
||||
args.appendElement(urlString);
|
||||
|
||||
const opts = { args, zenSyncedWindow: true };
|
||||
if (workspaceUuid) opts.zenInitialWorkspace = workspaceUuid;
|
||||
|
||||
const newWin = aWindow.OpenBrowserWindow(opts);
|
||||
if (newWin) aWindow.close();
|
||||
}
|
||||
}
|
||||
|
||||
export const ZenSpacesSearch = new ZenSpacesSearchService();
|
||||
5
src/zen/little-window/jar.inc.mn
Normal file
5
src/zen/little-window/jar.inc.mn
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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/.
|
||||
|
||||
content/browser/zen-styles/zen-little-window.css (../../zen/little-window/zen-little-window.css)
|
||||
8
src/zen/little-window/moz.build
Normal file
8
src/zen/little-window/moz.build
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES.zen += [
|
||||
"ZenLittleWindow.sys.mjs",
|
||||
"ZenSpacesSearch.sys.mjs",
|
||||
]
|
||||
162
src/zen/little-window/zen-little-window.css
Normal file
162
src/zen/little-window/zen-little-window.css
Normal file
@@ -0,0 +1,162 @@
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Little-window chrome: the URL bar fills the entire window. The vertical
|
||||
* tab strip, sidebar buttons, bookmarks toolbar, and any other chrome are
|
||||
* suppressed so the window acts as a quick search/launch box.
|
||||
*/
|
||||
|
||||
#zen-little-window-send-to-window {
|
||||
align-items: stretch;
|
||||
border-radius: var(--border-radius-small);
|
||||
background: light-dark(white, rgba(0, 0, 0, 0.4));
|
||||
height: 32px;
|
||||
margin-inline: 4px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
align-self: center;
|
||||
box-shadow: 0 0 1px 2px rgba(0, 0, 0, 0.01);
|
||||
|
||||
.zen-stw-main,
|
||||
.zen-stw-dropmarker {
|
||||
align-items: center;
|
||||
padding-inline: 12px;
|
||||
transition: background 80ms ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--toolbarbutton-active-background, color-mix(in srgb, currentColor 12%, transparent));
|
||||
}
|
||||
}
|
||||
|
||||
.zen-stw-prefix {
|
||||
color: color-mix(in srgb, currentColor 60%, transparent);
|
||||
margin-inline-end: 6px;
|
||||
}
|
||||
|
||||
.zen-stw-space-name {
|
||||
color: var(--zen-primary-color, currentColor);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.zen-stw-separator {
|
||||
width: 1px;
|
||||
background: color-mix(in srgb, currentColor 18%, transparent);
|
||||
margin-block: 6px;
|
||||
}
|
||||
|
||||
.zen-stw-dropmarker {
|
||||
padding-inline: 8px;
|
||||
}
|
||||
|
||||
.zen-stw-dropmarker-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
-moz-context-properties: fill;
|
||||
fill: color-mix(in srgb, currentColor 65%, transparent);
|
||||
list-style-image: url("chrome://global/skin/icons/arrow-down.svg");
|
||||
}
|
||||
}
|
||||
|
||||
#zen-spaces-popup {
|
||||
--arrowpanel-padding: 0;
|
||||
--zen-spaces-list-padding: 6px;
|
||||
padding: var(--zen-spaces-list-padding);
|
||||
min-width: 250px;
|
||||
|
||||
.zen-spaces-list-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 6px;
|
||||
border-bottom: 1px solid color-mix(in srgb, currentColor, transparent 90%);
|
||||
}
|
||||
|
||||
.zen-spaces-list-search-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-inline: 4px 6px;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#zen-spaces-list-search {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.zen-spaces-list-item {
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&[selected="true"] {
|
||||
background: var(--toolbarbutton-hover-background);
|
||||
}
|
||||
&[active="true"]::after {
|
||||
content: "•";
|
||||
margin-inline-start: auto;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-spaces-search-no-results {
|
||||
padding: 12px;
|
||||
opacity: 0.7;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
:root[zen-little-window="true"] {
|
||||
toolbarspring[cui-areatype="toolbar"],
|
||||
#nav-bar-customization-target > .toolbarbutton-1[disabled="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[zen-has-empty-tab="true"] {
|
||||
/* Keep in sync with URLBAR_HEIGHT in ZenLittleWindow.sys.mjs */
|
||||
--zen-minimum-window-height: 40px;
|
||||
min-width: unset !important;
|
||||
|
||||
#zen-appcontent-wrapper {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#urlbar[breakout-extend] {
|
||||
min-width: 600px !important;
|
||||
max-width: 600px !important;
|
||||
left: 50% !important;
|
||||
top: 0 !important;
|
||||
transform: translate(10px, 0) !important;
|
||||
visibility: visible;
|
||||
|
||||
& .urlbar-background {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
& .urlbar-input-container {
|
||||
-moz-window-dragging: drag;
|
||||
}
|
||||
|
||||
& .urlbar-input-box {
|
||||
-moz-window-dragging: no-drag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (-moz-platform: macos) {
|
||||
#nav-bar {
|
||||
padding-inline-start: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
#urlbar-container {
|
||||
--border-radius-medium: var(--border-radius-small);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ DIRS += [
|
||||
"common",
|
||||
"drag-and-drop",
|
||||
"glance",
|
||||
"kbs",
|
||||
"little-window",
|
||||
"live-folders",
|
||||
"mods",
|
||||
"tests",
|
||||
|
||||
@@ -206,6 +206,13 @@ class nsZenWindowSync {
|
||||
* @param {Window} aWindow - The browser window that is about to be shown.
|
||||
*/
|
||||
#onWindowBeforeShow(aWindow) {
|
||||
if (aWindow._zenStartupLittleWindow) {
|
||||
aWindow.document.documentElement.setAttribute(
|
||||
"zen-little-window",
|
||||
"true"
|
||||
);
|
||||
delete aWindow._zenStartupLittleWindow;
|
||||
}
|
||||
if (
|
||||
aWindow.gZenWindowSync ||
|
||||
aWindow.document.documentElement.hasAttribute("zen-unsynced-window")
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
DIRS += [
|
||||
"common",
|
||||
"window-control",
|
||||
]
|
||||
|
||||
47
src/zen/toolkit/window-control/ZenWindowControl.cpp
Normal file
47
src/zen/toolkit/window-control/ZenWindowControl.cpp
Normal 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
|
||||
25
src/zen/toolkit/window-control/ZenWindowControl.h
Normal file
25
src/zen/toolkit/window-control/ZenWindowControl.h
Normal 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
|
||||
14
src/zen/toolkit/window-control/components.conf
Normal file
14
src/zen/toolkit/window-control/components.conf
Normal 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,
|
||||
},
|
||||
]
|
||||
26
src/zen/toolkit/window-control/moz.build
Normal file
26
src/zen/toolkit/window-control/moz.build
Normal 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"
|
||||
34
src/zen/toolkit/window-control/nsIZenWindowControl.idl
Normal file
34
src/zen/toolkit/window-control/nsIZenWindowControl.idl
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user