mirror of
https://github.com/zen-browser/desktop.git
synced 2026-04-03 06:09:19 +00:00
995 lines
32 KiB
JavaScript
995 lines
32 KiB
JavaScript
/* 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/. */
|
|
|
|
/* eslint-disable consistent-return */
|
|
|
|
const lazy = {};
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"COMPACT_MODE_FLASH_DURATION",
|
|
"zen.view.compact.toolbar-flash-popup.duration",
|
|
800
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"COMPACT_MODE_FLASH_ENABLED",
|
|
"zen.view.compact.toolbar-flash-popup",
|
|
true
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"COMPACT_MODE_CAN_ANIMATE_SIDEBAR",
|
|
"zen.view.compact.animate-sidebar",
|
|
true
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"COMPACT_MODE_SHOW_SIDEBAR_AND_TOOLBAR_ON_HOVER",
|
|
"zen.view.compact.show-sidebar-and-toolbar-on-hover",
|
|
true
|
|
);
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "mainAppWrapper", () =>
|
|
document.getElementById("zen-main-app-wrapper")
|
|
);
|
|
|
|
window.gZenCompactModeManager = {
|
|
_flashTimeouts: {},
|
|
_eventListeners: [],
|
|
_removeHoverFrames: {},
|
|
|
|
// Delay to avoid flickering when hovering over the sidebar
|
|
HOVER_HACK_DELAY: Services.prefs.getIntPref(
|
|
"zen.view.compact.hover-hack-delay",
|
|
0
|
|
),
|
|
|
|
preInit() {
|
|
this._wasInCompactMode = Services.prefs.getBoolPref(
|
|
"zen.view.compact.enable-at-startup",
|
|
false
|
|
);
|
|
this._canDebugLog = Services.prefs.getBoolPref(
|
|
"zen.view.compact.debug",
|
|
false
|
|
);
|
|
|
|
this.addContextMenu();
|
|
},
|
|
|
|
init() {
|
|
this.addMouseActions();
|
|
|
|
const tabIsRightObserver = this._updateSidebarIsOnRight.bind(this);
|
|
Services.prefs.addObserver(
|
|
"zen.tabs.vertical.right-side",
|
|
tabIsRightObserver
|
|
);
|
|
|
|
window.addEventListener(
|
|
"unload",
|
|
() => {
|
|
Services.prefs.removeObserver(
|
|
"zen.tabs.vertical.right-side",
|
|
tabIsRightObserver
|
|
);
|
|
},
|
|
{ once: true }
|
|
);
|
|
|
|
gZenUIManager.addPopupTrackingAttribute(this.sidebar);
|
|
gZenUIManager.addPopupTrackingAttribute(
|
|
document.getElementById("zen-appcontent-navbar-wrapper")
|
|
);
|
|
|
|
this.addHasPolyfillObserver();
|
|
|
|
// Clear hover states when window state changes (minimize, maximize, etc.)
|
|
window.addEventListener("sizemodechange", () =>
|
|
this._clearAllHoverStates()
|
|
);
|
|
|
|
this._canShowBackgroundTabToast = Services.prefs.getBoolPref(
|
|
"zen.view.compact.show-background-tab-toast",
|
|
true
|
|
);
|
|
|
|
if (AppConstants.platform == "macosx") {
|
|
window.addEventListener("mouseover", event => {
|
|
const buttons = gZenVerticalTabsManager.actualWindowButtons;
|
|
if (event.target.closest(".titlebar-buttonbox-container") === buttons) {
|
|
return;
|
|
}
|
|
this._setElementExpandAttribute(buttons, false);
|
|
});
|
|
}
|
|
|
|
SessionStore.promiseAllWindowsRestored.then(() => {
|
|
this.preference = this._wasInCompactMode;
|
|
});
|
|
},
|
|
|
|
log(...args) {
|
|
if (this._canDebugLog) {
|
|
// eslint-disable-next-line no-console
|
|
console.debug("[Zen Compact Mode]", ...args);
|
|
}
|
|
},
|
|
|
|
get preference() {
|
|
return document.documentElement.getAttribute("zen-compact-mode") === "true";
|
|
},
|
|
|
|
get shouldBeCompact() {
|
|
return !document.documentElement
|
|
.getAttribute("chromehidden")
|
|
?.includes("toolbar");
|
|
},
|
|
|
|
set preference(value) {
|
|
if (!this.shouldBeCompact) {
|
|
value = false;
|
|
}
|
|
this.log("Setting compact mode preference to", value);
|
|
if (
|
|
this.preference === value ||
|
|
document.documentElement.hasAttribute("zen-compact-animating")
|
|
) {
|
|
if (typeof this._wasInCompactMode !== "undefined") {
|
|
// We wont do anything with it anyway, so we remove it
|
|
delete this._wasInCompactMode;
|
|
}
|
|
delete this._ignoreNextHover;
|
|
// We dont want the user to be able to spam the button
|
|
return;
|
|
}
|
|
this.sidebar.removeAttribute("zen-user-show");
|
|
// We use this element in order to make it persis across restarts, by using the XULStore.
|
|
// main-window can't store attributes other than window sizes, so we use this instead
|
|
lazy.mainAppWrapper.setAttribute("zen-compact-mode", value);
|
|
document.documentElement.setAttribute("zen-compact-mode", value);
|
|
if (typeof this._wasInCompactMode === "undefined") {
|
|
Services.prefs.setBoolPref("zen.view.compact.enable-at-startup", value);
|
|
}
|
|
this._updateEvent();
|
|
},
|
|
|
|
get sidebarIsOnRight() {
|
|
if (typeof this._sidebarIsOnRight !== "undefined") {
|
|
return this._sidebarIsOnRight;
|
|
}
|
|
this._sidebarIsOnRight = Services.prefs.getBoolPref(
|
|
"zen.tabs.vertical.right-side"
|
|
);
|
|
return this._sidebarIsOnRight;
|
|
},
|
|
|
|
get sidebar() {
|
|
return gNavToolbox;
|
|
},
|
|
|
|
addHasPolyfillObserver() {
|
|
const attributes = [
|
|
"panelopen",
|
|
"open",
|
|
"breakout-extend",
|
|
"zen-floating-urlbar",
|
|
];
|
|
this.sidebarObserverId = ZenHasPolyfill.observeSelectorExistence(
|
|
this.sidebar,
|
|
[
|
|
{
|
|
selector:
|
|
":is([panelopen='true'], [open='true'], [breakout-extend='true']):not(#urlbar[zen-floating-urlbar='true']):not(tab):not(.zen-compact-mode-ignore)",
|
|
},
|
|
],
|
|
"zen-compact-mode-active",
|
|
attributes
|
|
);
|
|
this.toolbarObserverId = ZenHasPolyfill.observeSelectorExistence(
|
|
document.getElementById("zen-appcontent-navbar-wrapper"),
|
|
[
|
|
{
|
|
selector:
|
|
":is([panelopen='true'], [open='true'], #urlbar:focus-within, [breakout-extend='true']):not(.zen-compact-mode-ignore)",
|
|
},
|
|
],
|
|
"zen-compact-mode-active",
|
|
attributes
|
|
);
|
|
// Always connect this observer, we need it even if compact mode is disabled
|
|
ZenHasPolyfill.connectObserver(this.toolbarObserverId);
|
|
},
|
|
|
|
flashSidebarIfNecessary(aInstant = false) {
|
|
// This function is called after exiting DOM fullscreen mode,
|
|
// so we do a bit of a hack to re-calculate the URL height
|
|
if (aInstant) {
|
|
gZenVerticalTabsManager.recalculateURLBarHeight(true);
|
|
}
|
|
if (
|
|
!aInstant &&
|
|
this.preference &&
|
|
lazy.COMPACT_MODE_FLASH_ENABLED &&
|
|
!gZenGlanceManager._animating
|
|
) {
|
|
this.flashSidebar();
|
|
}
|
|
},
|
|
|
|
addContextMenu() {
|
|
const fragment = window.MozXULElement.parseXULToFragment(`
|
|
<menu id="zen-context-menu-compact-mode" data-l10n-id="zen-toolbar-context-compact-mode">
|
|
<menupopup>
|
|
<menuitem id="zen-context-menu-compact-mode-toggle" data-l10n-id="zen-toolbar-context-compact-mode-enable" type="checkbox" command="cmd_zenCompactModeToggle"/>
|
|
<menuseparator/>
|
|
<menuitem id="zen-context-menu-compact-mode-hide-sidebar" data-l10n-id="zen-toolbar-context-compact-mode-just-tabs" type="radio" />
|
|
<menuitem id="zen-context-menu-compact-mode-hide-toolbar" data-l10n-id="zen-toolbar-context-compact-mode-just-toolbar" type="radio" />
|
|
<menuitem id="zen-context-menu-compact-mode-hide-both" data-l10n-id="zen-toolbar-context-compact-mode-hide-both" type="radio" />
|
|
</menupopup>
|
|
</menu>
|
|
`);
|
|
|
|
const idToAction = {
|
|
"zen-context-menu-compact-mode-hide-sidebar": this.hideSidebar.bind(this),
|
|
"zen-context-menu-compact-mode-hide-toolbar": this.hideToolbar.bind(this),
|
|
"zen-context-menu-compact-mode-hide-both": this.hideBoth.bind(this),
|
|
};
|
|
|
|
for (let menuitem of fragment.querySelectorAll("menuitem")) {
|
|
if (menuitem.id in idToAction) {
|
|
menuitem.addEventListener("command", idToAction[menuitem.id]);
|
|
}
|
|
}
|
|
|
|
document.getElementById("toolbar-context-customize").before(fragment);
|
|
this.updateContextMenu();
|
|
},
|
|
|
|
updateCompactModeContext(isSingleToolbar) {
|
|
const isIllegalState = this.checkIfIllegalState();
|
|
const menuitem = document.getElementById(
|
|
"zen-context-menu-compact-mode-toggle"
|
|
);
|
|
const menu = document.getElementById("zen-context-menu-compact-mode");
|
|
if (!menu) {
|
|
return;
|
|
}
|
|
if (isSingleToolbar) {
|
|
menu.setAttribute("hidden", "true");
|
|
menu.before(menuitem);
|
|
} else {
|
|
menu.removeAttribute("hidden");
|
|
menu.querySelector("menupopup").prepend(menuitem);
|
|
}
|
|
const hideToolbarMenuItem = document.getElementById(
|
|
"zen-context-menu-compact-mode-hide-toolbar"
|
|
);
|
|
if (isIllegalState) {
|
|
hideToolbarMenuItem.setAttribute("disabled", "true");
|
|
} else {
|
|
hideToolbarMenuItem.removeAttribute("disabled");
|
|
}
|
|
},
|
|
|
|
hideSidebar() {
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-tabbar", true);
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-toolbar", false);
|
|
this.callAllEventListeners();
|
|
},
|
|
|
|
hideToolbar() {
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-toolbar", true);
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-tabbar", false);
|
|
this.callAllEventListeners();
|
|
},
|
|
|
|
hideBoth() {
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-tabbar", true);
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-toolbar", true);
|
|
this.callAllEventListeners();
|
|
},
|
|
|
|
/**
|
|
* Check for illegal states and fix them
|
|
*
|
|
* @returns {boolean} If the context menu should just show the "toggle" item
|
|
* instead of a submenu with hide options
|
|
*/
|
|
checkIfIllegalState() {
|
|
// Due to how we layout the sidebar and toolbar, there are some states
|
|
// that are not allowed mainly due to the caption buttons not being accessible
|
|
// at the top left/right of the window.
|
|
const isSidebarExpanded = gZenVerticalTabsManager._prefsSidebarExpanded;
|
|
if (isSidebarExpanded) {
|
|
// Fast exit if the sidebar is expanded, as we dont have illegal states then
|
|
return false;
|
|
}
|
|
const canHideSidebar = this.canHideSidebar;
|
|
const canHideToolbar = this.canHideToolbar;
|
|
const isLeftSideButtons = !gZenVerticalTabsManager.isWindowsStyledButtons;
|
|
const isRightSidebar = gZenVerticalTabsManager._prefsRightSide;
|
|
// on macos: collapsed + left side + only toolbar
|
|
// on windows: collapsed + right side + only toolbar
|
|
const closelyIllegalState =
|
|
(isLeftSideButtons && !isRightSidebar) ||
|
|
(!isLeftSideButtons && isRightSidebar);
|
|
if (closelyIllegalState && canHideToolbar && !canHideSidebar) {
|
|
// This state is illegal
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-tabbar", true);
|
|
Services.prefs.setBoolPref("zen.view.compact.hide-toolbar", false);
|
|
this.callAllEventListeners();
|
|
return true;
|
|
}
|
|
return closelyIllegalState;
|
|
},
|
|
|
|
callAllEventListeners() {
|
|
this._eventListeners.forEach(callback => callback());
|
|
},
|
|
|
|
addEventListener(callback) {
|
|
this._eventListeners.push(callback);
|
|
},
|
|
|
|
removeEventListener(callback) {
|
|
const index = this._eventListeners.indexOf(callback);
|
|
if (index !== -1) {
|
|
this._eventListeners.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
async _updateEvent() {
|
|
const isUrlbarFocused = gURLBar.focused;
|
|
// IF we are animating IN, call the callbacks first so we can calculate the width
|
|
// once the window buttons are shown
|
|
this.updateContextMenu();
|
|
gZenWorkspaces._processingResize = true;
|
|
if (!this.preference) {
|
|
this.callAllEventListeners();
|
|
await this.animateCompactMode();
|
|
} else {
|
|
await this.animateCompactMode();
|
|
this.callAllEventListeners();
|
|
}
|
|
gZenWorkspaces._processingResize = false;
|
|
if (isUrlbarFocused) {
|
|
gURLBar.focus();
|
|
}
|
|
if (this.preference) {
|
|
ZenHasPolyfill.connectObserver(this.sidebarObserverId);
|
|
} else {
|
|
ZenHasPolyfill.disconnectObserver(this.sidebarObserverId);
|
|
}
|
|
window.dispatchEvent(
|
|
new CustomEvent("ZenCompactMode:Toggled", { detail: this.preference })
|
|
);
|
|
},
|
|
|
|
// NOTE: Dont actually use event, it's just so we make sure
|
|
// the caller is from the ResizeObserver
|
|
getAndApplySidebarWidth(event = undefined) {
|
|
if (this._ignoreNextResize) {
|
|
delete this._ignoreNextResize;
|
|
return;
|
|
}
|
|
let sidebarWidth = this.sidebar.getBoundingClientRect().width;
|
|
const shouldRecalculate =
|
|
this.preference ||
|
|
document.documentElement.hasAttribute("zen-creating-workspace");
|
|
const sidebarExpanded = document.documentElement.hasAttribute(
|
|
"zen-sidebar-expanded"
|
|
);
|
|
if (sidebarWidth > 1) {
|
|
if (shouldRecalculate && sidebarExpanded) {
|
|
sidebarWidth = Math.max(sidebarWidth, 150);
|
|
}
|
|
// Second variable to get the genuine width of the sidebar
|
|
this.sidebar.style.setProperty(
|
|
"--actual-zen-sidebar-width",
|
|
`${sidebarWidth}px`
|
|
);
|
|
if (!gZenWorkspaces._processingResize) {
|
|
window.dispatchEvent(new window.Event("resize")); // To recalculate the layout
|
|
}
|
|
if (
|
|
event &&
|
|
shouldRecalculate &&
|
|
sidebarExpanded &&
|
|
!gZenVerticalTabsManager._hadSidebarCollapse
|
|
) {
|
|
return;
|
|
}
|
|
delete gZenVerticalTabsManager._hadSidebarCollapse;
|
|
this.sidebar.style.setProperty(
|
|
"--zen-sidebar-width",
|
|
`${sidebarWidth}px`
|
|
);
|
|
}
|
|
return sidebarWidth;
|
|
},
|
|
|
|
get canHideSidebar() {
|
|
return (
|
|
Services.prefs.getBoolPref("zen.view.compact.hide-tabbar") ||
|
|
gZenVerticalTabsManager._hasSetSingleToolbar
|
|
);
|
|
},
|
|
|
|
get canHideToolbar() {
|
|
return (
|
|
Services.prefs.getBoolPref("zen.view.compact.hide-toolbar") &&
|
|
!gZenVerticalTabsManager._hasSetSingleToolbar
|
|
);
|
|
},
|
|
|
|
animateCompactMode() {
|
|
// Get the splitter width before hiding it (we need to hide it before animating on right)
|
|
document.documentElement.setAttribute("zen-compact-animating", "true");
|
|
return new Promise(resolve => {
|
|
// We need to set the splitter width before hiding it
|
|
let splitterWidth = document
|
|
.getElementById("zen-sidebar-splitter")
|
|
.getBoundingClientRect().width;
|
|
const isCompactMode = this.preference;
|
|
const canHideSidebar = this.canHideSidebar;
|
|
let canAnimate =
|
|
lazy.COMPACT_MODE_CAN_ANIMATE_SIDEBAR &&
|
|
!this.isSidebarPotentiallyOpen();
|
|
if (typeof this._wasInCompactMode !== "undefined") {
|
|
canAnimate = false;
|
|
delete this._wasInCompactMode;
|
|
}
|
|
// Do this so we can get the correct width ONCE compact mode styled have been applied
|
|
if (canAnimate) {
|
|
this.sidebar.setAttribute("animate", "true");
|
|
}
|
|
if (this._ignoreNextHover) {
|
|
this._setElementExpandAttribute(this.sidebar, false);
|
|
}
|
|
this.sidebar.style.removeProperty("margin-right");
|
|
this.sidebar.style.removeProperty("margin-left");
|
|
this.sidebar.style.removeProperty("transform");
|
|
window.requestAnimationFrame(() => {
|
|
delete this._ignoreNextResize;
|
|
let sidebarWidth = this.getAndApplySidebarWidth();
|
|
const elementSeparation = ZenThemeModifier.elementSeparation;
|
|
if (!canAnimate) {
|
|
this.sidebar.removeAttribute("animate");
|
|
document.documentElement.removeAttribute("zen-compact-animating");
|
|
|
|
this.getAndApplySidebarWidth({});
|
|
this._ignoreNextResize = true;
|
|
|
|
delete this._ignoreNextHover;
|
|
|
|
resolve();
|
|
return;
|
|
}
|
|
if (document.documentElement.hasAttribute("zen-sidebar-expanded")) {
|
|
sidebarWidth -= 0.5 * splitterWidth;
|
|
if (elementSeparation < splitterWidth) {
|
|
// Subtract from the splitter width to end up with the correct element separation
|
|
sidebarWidth += 1.5 * splitterWidth - elementSeparation;
|
|
}
|
|
} else {
|
|
sidebarWidth -= elementSeparation;
|
|
}
|
|
if (canHideSidebar && isCompactMode) {
|
|
this._setElementExpandAttribute(this.sidebar, false);
|
|
gZenUIManager.motion
|
|
.animate(
|
|
this.sidebar,
|
|
{
|
|
marginRight: [
|
|
0,
|
|
this.sidebarIsOnRight ? `-${sidebarWidth}px` : 0,
|
|
],
|
|
marginLeft: [
|
|
0,
|
|
this.sidebarIsOnRight ? 0 : `-${sidebarWidth}px`,
|
|
],
|
|
},
|
|
{
|
|
ease: "easeIn",
|
|
type: "spring",
|
|
bounce: 0,
|
|
duration: 0.12,
|
|
}
|
|
)
|
|
.then(() => {
|
|
this.sidebar.style.transition = "none";
|
|
this.sidebar.style.pointEvents = "none";
|
|
const titlebar = document.getElementById("titlebar");
|
|
titlebar.style.visibility = "hidden";
|
|
titlebar.style.transition = "none";
|
|
this.sidebar.removeAttribute("animate");
|
|
document.documentElement.removeAttribute("zen-compact-animating");
|
|
|
|
if (this._ignoreNextHover) {
|
|
setTimeout(() => {
|
|
delete this._ignoreNextHover;
|
|
});
|
|
}
|
|
|
|
this.getAndApplySidebarWidth({});
|
|
this._ignoreNextResize = true;
|
|
|
|
this.sidebar.style.removeProperty("margin-right");
|
|
this.sidebar.style.removeProperty("margin-left");
|
|
this.sidebar.style.removeProperty("transition");
|
|
this.sidebar.style.removeProperty("transform");
|
|
this.sidebar.style.removeProperty("point-events");
|
|
|
|
titlebar.style.removeProperty("visibility");
|
|
titlebar.style.removeProperty("transition");
|
|
|
|
gURLBar.style.removeProperty("visibility");
|
|
|
|
resolve();
|
|
});
|
|
} else if (canHideSidebar && !isCompactMode) {
|
|
// Shouldn't be ever true, but just in case
|
|
delete this._ignoreNextHover;
|
|
document.getElementById("browser").style.overflow = "clip";
|
|
if (this.sidebarIsOnRight) {
|
|
this.sidebar.style.marginRight = `-${sidebarWidth}px`;
|
|
} else {
|
|
this.sidebar.style.marginLeft = `-${sidebarWidth}px`;
|
|
}
|
|
gZenUIManager.motion
|
|
.animate(
|
|
this.sidebar,
|
|
this.sidebarIsOnRight
|
|
? {
|
|
marginRight: [`-${sidebarWidth}px`, 0],
|
|
transform: ["translateX(100%)", "translateX(0)"],
|
|
}
|
|
: { marginLeft: 0 },
|
|
{
|
|
ease: "easeOut",
|
|
type: "spring",
|
|
bounce: 0,
|
|
duration: 0.12,
|
|
}
|
|
)
|
|
.then(() => {
|
|
this.sidebar.removeAttribute("animate");
|
|
document
|
|
.getElementById("browser")
|
|
.style.removeProperty("overflow");
|
|
this.sidebar.style.transition = "none";
|
|
this.sidebar.style.removeProperty("margin-right");
|
|
this.sidebar.style.removeProperty("margin-left");
|
|
this.sidebar.style.removeProperty("transform");
|
|
document.documentElement.removeAttribute("zen-compact-animating");
|
|
setTimeout(() => {
|
|
this.sidebar.style.removeProperty("transition");
|
|
resolve();
|
|
});
|
|
});
|
|
} else {
|
|
this.sidebar.removeAttribute("animate"); // remove the attribute if we are not animating
|
|
document.documentElement.removeAttribute("zen-compact-animating");
|
|
delete this._ignoreNextHover;
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
updateContextMenu() {
|
|
const toggle = document.getElementById(
|
|
"zen-context-menu-compact-mode-toggle"
|
|
);
|
|
if (!toggle) {
|
|
return;
|
|
}
|
|
toggle.toggleAttribute("checked", this.preference);
|
|
|
|
const hideTabBar = this.canHideSidebar;
|
|
const hideToolbar = this.canHideToolbar;
|
|
const hideBoth = hideTabBar && hideToolbar;
|
|
|
|
const idName = "zen-context-menu-compact-mode-hide-";
|
|
const sidebarItem = document.getElementById(idName + "sidebar");
|
|
const toolbarItem = document.getElementById(idName + "toolbar");
|
|
const bothItem = document.getElementById(idName + "both");
|
|
sidebarItem.toggleAttribute("checked", !hideBoth && hideTabBar);
|
|
toolbarItem.toggleAttribute("checked", !hideBoth && hideToolbar);
|
|
bothItem.toggleAttribute("checked", hideBoth);
|
|
},
|
|
|
|
_removeOpenStateOnUnifiedExtensions() {
|
|
// Fix for bug https://github.com/zen-browser/desktop/issues/1925
|
|
const buttons = document.querySelectorAll(
|
|
"toolbarbutton:is(#unified-extensions-button, .webextension-browser-action)"
|
|
);
|
|
for (let button of buttons) {
|
|
button.removeAttribute("open");
|
|
}
|
|
},
|
|
|
|
toggle(ignoreHover = false) {
|
|
// Only ignore the next hover when we are enabling compact mode
|
|
this._ignoreNextHover = ignoreHover && !this.preference;
|
|
return (this.preference = !this.preference);
|
|
},
|
|
|
|
_updateSidebarIsOnRight() {
|
|
this._sidebarIsOnRight = Services.prefs.getBoolPref(
|
|
"zen.tabs.vertical.right-side"
|
|
);
|
|
},
|
|
|
|
toggleSidebar() {
|
|
this.sidebar.toggleAttribute("zen-user-show");
|
|
},
|
|
|
|
get hideAfterHoverDuration() {
|
|
if (this._hideAfterHoverDuration) {
|
|
return this._hideAfterHoverDuration;
|
|
}
|
|
return Services.prefs.getIntPref(
|
|
"zen.view.compact.toolbar-hide-after-hover.duration"
|
|
);
|
|
},
|
|
|
|
get hoverableElements() {
|
|
return [
|
|
{
|
|
element: this.sidebar,
|
|
screenEdge: this.sidebarIsOnRight ? "right" : "left",
|
|
keepHoverDuration: Services.prefs.getIntPref(
|
|
"zen.view.compact.sidebar-keep-hover.duration"
|
|
),
|
|
},
|
|
{
|
|
element: document.getElementById("zen-appcontent-navbar-wrapper"),
|
|
screenEdge: "top",
|
|
},
|
|
{
|
|
element: gZenVerticalTabsManager.actualWindowButtons,
|
|
},
|
|
];
|
|
},
|
|
|
|
flashSidebar(duration = lazy.COMPACT_MODE_FLASH_DURATION) {
|
|
let tabPanels = document.getElementById("tabbrowser-tabpanels");
|
|
if (!tabPanels.matches("[zen-split-view='true']")) {
|
|
this.flashElement(this.sidebar, duration, this.sidebar.id);
|
|
}
|
|
},
|
|
|
|
flashElement(element, duration, id, attrName = "flash-popup") {
|
|
if (this._flashTimeouts[id]) {
|
|
clearTimeout(this._flashTimeouts[id]);
|
|
} else {
|
|
requestAnimationFrame(() =>
|
|
this._setElementExpandAttribute(element, true, attrName)
|
|
);
|
|
}
|
|
this._flashTimeouts[id] = setTimeout(() => {
|
|
window.requestAnimationFrame(() => {
|
|
this._setElementExpandAttribute(element, false, attrName);
|
|
this._flashTimeouts[id] = null;
|
|
});
|
|
}, duration);
|
|
},
|
|
|
|
clearFlashTimeout(id) {
|
|
clearTimeout(this._flashTimeouts[id]);
|
|
this._flashTimeouts[id] = null;
|
|
},
|
|
|
|
_setElementExpandAttribute(element, value, attr = "zen-has-hover") {
|
|
const kVerifiedAttributes = [
|
|
"zen-has-hover",
|
|
"has-popup-menu",
|
|
"zen-compact-mode-active",
|
|
];
|
|
const isToolbar = element.id === "zen-appcontent-navbar-wrapper";
|
|
this.log("Setting", attr, "to", value, "on element", element?.id);
|
|
if (value) {
|
|
if (
|
|
attr === "zen-has-hover" &&
|
|
element !== gZenVerticalTabsManager.actualWindowButtons
|
|
) {
|
|
element.setAttribute("zen-has-implicit-hover", "true");
|
|
if (!lazy.COMPACT_MODE_SHOW_SIDEBAR_AND_TOOLBAR_ON_HOVER) {
|
|
return;
|
|
}
|
|
}
|
|
element.setAttribute(attr, "true");
|
|
if (
|
|
isToolbar &&
|
|
((gZenVerticalTabsManager._hasSetSingleToolbar &&
|
|
(element.hasAttribute("should-hide") ||
|
|
document.documentElement.hasAttribute("zen-has-bookmarks"))) ||
|
|
(this.preference &&
|
|
Services.prefs.getBoolPref("zen.view.compact.hide-toolbar") &&
|
|
!gZenVerticalTabsManager._hasSetSingleToolbar))
|
|
) {
|
|
gBrowser.tabpanels.setAttribute("has-toolbar-hovered", "true");
|
|
}
|
|
} else {
|
|
if (attr === "zen-has-hover") {
|
|
element.removeAttribute("zen-has-implicit-hover");
|
|
}
|
|
element.removeAttribute(attr);
|
|
// Only remove if none of the verified attributes are present
|
|
if (
|
|
isToolbar &&
|
|
!kVerifiedAttributes.some(verifiedAttr =>
|
|
element.hasAttribute(verifiedAttr)
|
|
)
|
|
) {
|
|
gBrowser.tabpanels.removeAttribute("has-toolbar-hovered");
|
|
}
|
|
}
|
|
},
|
|
|
|
addMouseActions() {
|
|
gURLBar.addEventListener("mouseenter", event => {
|
|
this.log("Mouse entered URL bar:", event.target);
|
|
if (event.target.closest("#urlbar[zen-floating-urlbar]")) {
|
|
window.requestAnimationFrame(() => {
|
|
this._setElementExpandAttribute(
|
|
gZenVerticalTabsManager.actualWindowButtons,
|
|
false
|
|
);
|
|
});
|
|
this._hasHoveredUrlbar = true;
|
|
}
|
|
});
|
|
|
|
for (let i = 0; i < this.hoverableElements.length; i++) {
|
|
let target = this.hoverableElements[i].element;
|
|
|
|
// Add the attribute on startup if the mouse is already over the element
|
|
if (target.matches(":hover")) {
|
|
this._setElementExpandAttribute(target, true);
|
|
}
|
|
|
|
const onEnter = event => {
|
|
setTimeout(() => {
|
|
if (event.type === "mouseenter" && !event.target.matches(":hover")) {
|
|
return;
|
|
}
|
|
if (event.target.closest("panel")) {
|
|
return;
|
|
}
|
|
// Dont register the hover if the urlbar is floating and we are hovering over it
|
|
this.clearFlashTimeout("has-hover" + target.id);
|
|
window.requestAnimationFrame(() => {
|
|
if (
|
|
document.documentElement.getAttribute(
|
|
"supress-primary-adjustment"
|
|
) === "true" ||
|
|
this._hasHoveredUrlbar ||
|
|
this._ignoreNextHover ||
|
|
target.hasAttribute("zen-has-hover")
|
|
) {
|
|
return;
|
|
}
|
|
this._setElementExpandAttribute(target, true);
|
|
});
|
|
}, this.HOVER_HACK_DELAY);
|
|
};
|
|
|
|
const onLeave = event => {
|
|
if (AppConstants.platform == "macosx") {
|
|
const buttonRect =
|
|
gZenVerticalTabsManager.actualWindowButtons.getBoundingClientRect();
|
|
const MAC_WINDOW_BUTTONS_X_BORDER = buttonRect.width + buttonRect.x;
|
|
const MAC_WINDOW_BUTTONS_Y_BORDER = buttonRect.height + buttonRect.y;
|
|
if (
|
|
event.clientX < MAC_WINDOW_BUTTONS_X_BORDER &&
|
|
event.clientY < MAC_WINDOW_BUTTONS_Y_BORDER &&
|
|
event.clientX > buttonRect.x &&
|
|
event.clientY > buttonRect.y
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// See bug https://bugzilla.mozilla.org/show_bug.cgi?id=1979340 and issue https://github.com/zen-browser/desktop/issues/7746.
|
|
// If we want the toolbars to be draggable, we need to make sure to check the hover state after a short delay.
|
|
// This is because the mouse is left to be handled natively so firefox thinks the mouse left the window for a split second.
|
|
setTimeout(() => {
|
|
// Let's double check if the mouse is still hovering over the element, see the bug above.
|
|
if (event.target.matches(":hover")) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
event.explicitOriginalTarget?.closest?.(
|
|
"#urlbar[zen-floating-urlbar]"
|
|
) ||
|
|
(document.documentElement.getAttribute(
|
|
"supress-primary-adjustment"
|
|
) === "true" &&
|
|
gZenVerticalTabsManager._hasSetSingleToolbar) ||
|
|
this._hasHoveredUrlbar ||
|
|
this._ignoreNextHover ||
|
|
(event.type === "dragleave" &&
|
|
event.explicitOriginalTarget !== target &&
|
|
target.contains?.(event.explicitOriginalTarget))
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (this._isTabBeingDragged) {
|
|
return;
|
|
}
|
|
|
|
if (this.hoverableElements[i].keepHoverDuration) {
|
|
this.flashElement(
|
|
target,
|
|
this.hoverableElements[i].keepHoverDuration,
|
|
"has-hover" + target.id,
|
|
"zen-has-hover"
|
|
);
|
|
} else {
|
|
this._removeHoverFrames[target.id] = window.requestAnimationFrame(
|
|
() => this._setElementExpandAttribute(target, false)
|
|
);
|
|
}
|
|
}, this.HOVER_HACK_DELAY);
|
|
};
|
|
|
|
target.addEventListener("mouseover", onEnter);
|
|
target.addEventListener("dragover", onEnter);
|
|
|
|
target.addEventListener("mouseleave", onLeave);
|
|
target.addEventListener("dragleave", onLeave);
|
|
}
|
|
|
|
document.documentElement.addEventListener("mouseleave", event => {
|
|
setTimeout(() => {
|
|
const screenEdgeCrossed = this._getCrossedEdge(
|
|
event.pageX,
|
|
event.pageY
|
|
);
|
|
if (!screenEdgeCrossed) {
|
|
return;
|
|
}
|
|
for (let entry of this.hoverableElements) {
|
|
if (screenEdgeCrossed !== entry.screenEdge) {
|
|
continue;
|
|
}
|
|
const target = entry.element;
|
|
const boundAxis =
|
|
entry.screenEdge === "right" || entry.screenEdge === "left"
|
|
? "y"
|
|
: "x";
|
|
if (
|
|
!this._positionInBounds(
|
|
boundAxis,
|
|
target,
|
|
event.pageX,
|
|
event.pageY,
|
|
7
|
|
)
|
|
) {
|
|
continue;
|
|
}
|
|
window.cancelAnimationFrame(this._removeHoverFrames[target.id]);
|
|
|
|
this.flashElement(
|
|
target,
|
|
this.hideAfterHoverDuration,
|
|
"has-hover" + target.id,
|
|
"zen-has-hover"
|
|
);
|
|
document.addEventListener(
|
|
"mousemove",
|
|
() => {
|
|
if (target.matches(":hover")) {
|
|
return;
|
|
}
|
|
this._setElementExpandAttribute(target, false);
|
|
this.clearFlashTimeout("has-hover" + target.id);
|
|
},
|
|
{ once: true }
|
|
);
|
|
}
|
|
}, this.HOVER_HACK_DELAY);
|
|
});
|
|
|
|
gURLBar.addEventListener("mouseleave", () => {
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
requestAnimationFrame(() => {
|
|
delete this._hasHoveredUrlbar;
|
|
});
|
|
}, 10);
|
|
}, 0);
|
|
});
|
|
},
|
|
|
|
_getCrossedEdge(
|
|
posX,
|
|
posY,
|
|
element = document.documentElement,
|
|
maxDistance = 10
|
|
) {
|
|
const targetBox = element.getBoundingClientRect();
|
|
posX = Math.max(targetBox.left, Math.min(posX, targetBox.right));
|
|
posY = Math.max(targetBox.top, Math.min(posY, targetBox.bottom));
|
|
return ["top", "bottom", "left", "right"].find((edge, i) => {
|
|
const distance = Math.abs((i < 2 ? posY : posX) - targetBox[edge]);
|
|
return distance <= maxDistance;
|
|
});
|
|
},
|
|
|
|
_positionInBounds(axis = "x", element, x, y, error = 0) {
|
|
const bBox = element.getBoundingClientRect();
|
|
if (axis === "y") {
|
|
return bBox.top - error < y && y < bBox.bottom + error;
|
|
}
|
|
return bBox.left - error < x && x < bBox.right + error;
|
|
},
|
|
|
|
_clearAllHoverStates() {
|
|
// Clear hover attributes from all hoverable elements
|
|
for (let entry of this.hoverableElements) {
|
|
const target = entry.element;
|
|
if (
|
|
target &&
|
|
!target.matches(":hover") &&
|
|
target.hasAttribute("zen-has-hover")
|
|
) {
|
|
this._setElementExpandAttribute(target, false);
|
|
this.clearFlashTimeout("has-hover" + target.id);
|
|
}
|
|
}
|
|
},
|
|
|
|
isSidebarPotentiallyOpen() {
|
|
if (this._ignoreNextHover) {
|
|
this._setElementExpandAttribute(this.sidebar, false);
|
|
}
|
|
return (
|
|
this.sidebar.hasAttribute("zen-user-show") ||
|
|
this.sidebar.hasAttribute("zen-has-hover") ||
|
|
this.sidebar.hasAttribute("zen-has-empty-tab")
|
|
);
|
|
},
|
|
|
|
async _onTabOpen(tab, inBackground) {
|
|
if (
|
|
inBackground &&
|
|
this.preference &&
|
|
!this.isSidebarPotentiallyOpen() &&
|
|
this._canShowBackgroundTabToast &&
|
|
!gZenGlanceManager._animating &&
|
|
!this._nextTimeWillBeActive
|
|
) {
|
|
gZenUIManager.showToast("zen-background-tab-opened-toast", {
|
|
button: {
|
|
id: "zen-open-background-tab-button",
|
|
command: () => {
|
|
const targetWindow = window.ownerGlobal.parent || window;
|
|
targetWindow.gBrowser.selectedTab = tab;
|
|
},
|
|
},
|
|
});
|
|
}
|
|
delete this._nextTimeWillBeActive;
|
|
},
|
|
};
|
|
|
|
document.addEventListener(
|
|
"MozBeforeInitialXULLayout",
|
|
() => {
|
|
gZenCompactModeManager.preInit();
|
|
},
|
|
{ once: true }
|
|
);
|