Compare commits

...

22 Commits
1.18.5b ... dev

Author SHA1 Message Date
mr. m
4f9a932e77 fix: Fixed unpinned tabs not being cleared when the pref is off, b=closes #12307, c=no-component 2026-02-09 11:37:10 +01:00
mr. m
f2603521e5 chore: Update external patches, b=no-bug, c=folders 2026-02-09 11:34:02 +01:00
mr. m
24f695c452 Merge branch 'dev' of https://github.com/zen-browser/desktop into dev 2026-02-09 09:02:55 +01:00
mr. m
f5effd4dcd feat: Add preference to focus on urlbar on startup, b=no-bug, c=workspaces 2026-02-09 09:02:39 +01:00
Mr. M
b5bb7d7c8b feat: Make sure to use the proper parent reference for XDG paths, b=bug #11917, c=no-component 2026-02-08 20:07:14 +01:00
mr. m
604365dd38 chore: Update external mochitests, b=no-bug, c=tests, scripts, common 2026-02-08 19:20:36 +01:00
TogiFerretFerret
466d089fc0 feat: Add zen identification info, b=closes #12198, p=#12295
* feat: add zen identification to extension runtime for zen-specific features

* chore: add branding to nightly to assist with certain testing (e.g. browser identification)

* test: assert zen info in runtime.getBrowserInfo

* Revert "test: assert zen info in runtime.getBrowserInfo"

This reverts commit 94cfeff29f as the tests fail even without this commit.

* Discard changes to surfer.json

* chore: remove platform from getBrowserInfo (as requested)

---------

Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-02-08 17:48:04 +01:00
Roman
e28a10f6a9 fix: Use optional chaining and nullish coalescing in resetButton, p=#12300
Signed-off-by: Roman <58283566+rmdeuce@users.noreply.github.com>
2026-02-08 13:15:57 +01:00
mr. m
102be6cd90 fix: Remove psueod hidden browser background on close, b=no-bug, c=no-component 2026-02-08 13:15:42 +01:00
mr. m
6e1e1d061b fix: Fixed clicking on the 'add' button not closing the panel, b=no-bug, c=common, workspaces 2026-02-08 11:19:46 +01:00
mr. m
43250d6d37 fix: Fixed bookmarks not being synced with mozilla account, b=bug #12133, c=workspaces 2026-02-08 10:48:12 +01:00
mr. m
12f0c455d1 fix: Fixed 'restore previous tabs' ignored when only pinned sync is enabled, b=closes #12297, c=tabs 2026-02-08 10:35:05 +01:00
Andrey Bochkarev
049c3c6f54 feat: Implement vertical dnd tab splitting, p=#12289 2026-02-07 23:59:28 +01:00
Samuel Akhaze
658ac94334 fix: Fixed Incorrect (Reset pinned tab/Replace pinned url) Text on Essential tab context menu, p=#12283 2026-02-07 23:59:15 +01:00
mr. m
09a90099c7 Merge branch 'dev' of https://github.com/zen-browser/desktop into dev 2026-02-07 23:52:51 +01:00
mr. m
051470f139 feat: Small polishing changes, b=no-bug, c=common, workspaces 2026-02-07 21:25:24 +01:00
dependabot[bot]
8f1cb88c11 chore(deps-dev): bump lodash, p=#12292
Bumps the npm_and_yarn group with 1 update in the / directory: [lodash](https://github.com/lodash/lodash).


Updates `lodash` from 4.17.21 to 4.17.23
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-07 18:49:21 +01:00
mr. m
3ef233a4c2 Merge branch 'dev' of https://github.com/zen-browser/desktop into dev 2026-02-07 18:48:46 +01:00
mr. m
38fcd7e872 fix: Fixed private windows closing when all tabs are cleared, b=closes #12242, c=common, folders, tabs, workspaces 2026-02-07 18:47:06 +01:00
mr. m
76f17c3a57 chore: Automatically import patches from phabricator and librewolf, p=#12271
* chore: Automatically import patches from phabricator and librewolf, b=no-bug, c=workflows, scripts

Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>

* feat: Remove legacy flag, b=no-bug, c=common, configs

---------
2026-02-07 18:02:40 +01:00
mr. m
b5c2451525 fix: Fixed overflowing addons not opening the panel at the correct anchor, b=no-bug, c=common 2026-02-07 16:51:44 +01:00
mr. m
ba2a854784 feat: Overflow web extensions below the urlbar, p=#12273
* feat: Overflow web extensions below the urlbar, b=no-bug, c=common

* chore: Ignore toolbar CSS, b=no-bug, c=no-component
2026-02-06 22:56:11 +01:00
50 changed files with 780 additions and 407 deletions

View File

@@ -73,15 +73,13 @@ jobs:
npm run sync
fi
- name: Run formatter
if: steps.check-upstream-branch.outputs.branch_exists == 'false'
run: |
sudo apt install python3-autopep8
npm run pretty
- name: Install autopep8
run: sudo apt install python3-autopep8
- name: Check if any files changed
id: git-check
run: |
npm run pretty
if [ -n "$(git status --porcelain)" ]; then
echo "files_changed=true" >> $GITHUB_OUTPUT
else
@@ -111,6 +109,15 @@ jobs:
if: steps.git-check.outputs.files_changed == 'true'
run: python3 scripts/import_external_tests.py || true
- name: Import external patches
if: steps.git-check.outputs.files_changed == 'true'
run: python3 scripts/import_external_patches.py || true
- name: Run formatter
if: steps.check-upstream-branch.outputs.branch_exists == 'false'
run: |
npm run pretty
- name: Create pull request
uses: peter-evans/create-pull-request@v7
if: steps.git-check.outputs.files_changed == 'true'

View File

@@ -37,5 +37,6 @@ src/zen/common/ZenEmojis.mjs
src/zen/split-view/zen-decks.css
src/zen/workspaces/zen-workspaces.css
src/zen/common/styles/zen-toolbar.css
*.inc

View File

@@ -9,9 +9,6 @@ ac_add_options --with-app-basename=Zen
# Localization (Must be an absolute path)
ac_add_options --with-l10n-base="${topsrcdir}/browser/locales"
# See https://github.com/zen-browser/desktop/issues/11917 for future plans.
# We should be removing this at some point and start supporting XDG dirs.
ac_add_options --with-user-appdir=".${binName}"
export MOZ_APP_BASENAME=Zen
export MOZ_BRANDING_DIRECTORY=${brandingDir}
export MOZ_OFFICIAL_BRANDING_DIRECTORY=${brandingDir}
@@ -85,6 +82,11 @@ if test "$ZEN_RELEASE"; then
export MOZ_PACKAGE_JSSHELL=1
ac_add_options --disable-crashreporter
# Experimental flag, enabled only on nightly for Firefox.
# Should bring in some nice performance improvements,
# but may cause stability issues.
ac_add_options --enable-replace-malloc
fi
ac_add_options --enable-unverified-updates

View File

@@ -6,7 +6,11 @@ zen-panel-ui-current-profile-text = current profile
unified-extensions-description = Extensions are used to bring more extra functionality into { -brand-short-name }.
tab-context-zen-reset-pinned-tab =
.label = Reset Pinned Tab
.label =
{ $isEssential ->
[true] Reset Essential Tab
*[false] Reset Pinned Tab
}
.accesskey = R
tab-context-zen-add-essential =
.label = Add to Essentials
@@ -16,7 +20,11 @@ tab-context-zen-remove-essential =
.label = Remove from Essentials
.accesskey = R
tab-context-zen-replace-pinned-url-with-current =
.label = Replace Pinned URL with Current
.label =
{ $isEssential ->
[true] Replace Essential URL with Current
*[false] Replace Pinned URL with Current
}
.accesskey = C
tab-context-zen-edit-title =
.label = Change Label...

6
package-lock.json generated
View File

@@ -5610,9 +5610,9 @@
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"dev": true,
"license": "MIT"
},

View File

@@ -54,3 +54,6 @@
- name: zen.view.draggable-sidebar
value: true
- name: zen.view.overflow-webext-toolbar
value: true

View File

@@ -32,6 +32,9 @@
- name: zen.urlbar.show-pip-button
value: false
- name: zen.urlbar.open-on-startup
value: true
# Mark: Zen suggestions controls
- name: zen.urlbar.suggestions.quick-actions

View File

@@ -1,41 +0,0 @@
# 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 os
import sys
import requests
BASE_URI = "https://phabricator.services.mozilla.com"
OUTPUT_DIR = os.path.join(os.getcwd(), "src", "firefox-patches")
def download_phab_patch(phab_id, output_file):
"""Download a Phabricator patch by its ID and save it to output_file."""
patch_url = f"{BASE_URI}/{phab_id}?download=true"
try:
print(f"Downloading patch from {patch_url}")
response = requests.get(patch_url)
response.raise_for_status() # Raise an error for bad responses
with open(output_file, 'wb') as f:
f.write(response.content)
print(f"Patch saved to {output_file}")
except requests.RequestException as e:
print(f"Error downloading patch: {e}")
sys.exit(1)
def main():
if len(sys.argv) < 2:
print("Usage: python download_phab_patch.py <PHABRICATOR_ID> [output_file]", file=sys.stderr)
sys.exit(1)
phab_id = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else f"phab_{phab_id}"
output_file = os.path.join(OUTPUT_DIR, output_file + ".patch")
download_phab_patch(phab_id, output_file)
if __name__ == "__main__":
main()

View File

@@ -18,10 +18,18 @@ FILE_PREFIX = """
# This file is autogenerated by scripts/import_external_tests.py
# Do not edit manually.
"""
BROWSER_MANIFEST_LIST_PREFIX = """
BROWSER_CHROME_MANIFESTS += [
"""
XPCSHELL_MANIFESTS_LIST_PREFIX = """
XPCSHELL_TESTS_MANIFESTS += [
"""
FILE_SUFFIX = "]"
VALID_MANIFEST_FILES = ["browser.toml", "xpcshell.toml"]
def get_tests_manifest():
@@ -38,12 +46,17 @@ def validate_tests_path(path, files, ignore_list):
for ignore in ignore_list:
if ignore not in files:
die_with_error(f"Ignore file '{ignore}' not found in tests folder '{path}'")
if "browser.toml" not in files or "browser.js" in ignore_list:
die_with_error(f"'browser.toml' not found in tests folder '{path}'")
if not any(manifest_file in files for manifest_file in VALID_MANIFEST_FILES):
die_with_error(f"None of the valid manifest files {VALID_MANIFEST_FILES} found in tests folder '{path}'")
def disable_and_replace_manifest(manifest, output_path):
toml_file = os.path.join(output_path, "browser.toml")
toml_file = None
for manifest_file in VALID_MANIFEST_FILES:
candidate = os.path.join(output_path, manifest_file)
if os.path.exists(candidate):
toml_file = candidate
break
disabled_tests = manifest.get("disable", [])
with open(toml_file, "r") as f:
data = f.read()
@@ -90,8 +103,17 @@ def write_moz_build_file(manifest):
print(f"Writing moz.build file to '{moz_build_path}'")
with open(moz_build_path, "w") as f:
f.write(FILE_PREFIX)
f.write(BROWSER_MANIFEST_LIST_PREFIX)
for test_suite in manifest.keys():
f.write(f'\t"{test_suite}/browser.toml",\n')
# add for browser.toml first
if not manifest[test_suite].get("xpcshell", False):
f.write(f'\t"{test_suite}/browser.toml",\n')
f.write(FILE_SUFFIX)
f.write(XPCSHELL_MANIFESTS_LIST_PREFIX)
for test_suite in manifest.keys():
# add for xpcshell.toml
if manifest[test_suite].get("xpcshell", False):
f.write(f'\t"{test_suite}/xpcshell.toml",\n')
f.write(FILE_SUFFIX)

View File

@@ -56,7 +56,7 @@ def main():
os.chdir(engine_dir)
def run_mach_with_paths(test_paths):
command = ['./mach', 'mochitest'] + other_args + test_paths
command = ['./mach', 'test'] + other_args + test_paths
# Replace the current process with the mach command
os.execvp(command[0], command)

View File

@@ -0,0 +1,99 @@
# 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 os
import sys
import json
import requests
from json_with_comments import JSONWithCommentsDecoder
BASE_URI = "https://phabricator.services.mozilla.com"
OUTPUT_DIR = os.path.join(os.getcwd(), "src", "external-patches")
def die(message):
print(f"Error: {message}")
sys.exit(1)
def download_phab_patch(phab_id, output_file):
"""Download a Phabricator patch by its ID and save it to output_file."""
patch_url = f"{BASE_URI}/{phab_id}?download=true"
try:
print(f"Downloading patch from {patch_url}")
response = requests.get(patch_url)
response.raise_for_status() # Raise an error for bad responses
with open(output_file, 'wb') as f:
f.write(response.content)
print(f"Patch saved to {output_file}")
except requests.RequestException as e:
die(f"Failed to download patch {phab_id}: {e}")
def download_patch_from_url(url, output_file):
"""Download a patch from a given URL and save it to output_file."""
try:
print(f"Downloading patch from {url}")
response = requests.get(url)
response.raise_for_status() # Raise an error for bad responses
with open(output_file, 'wb') as f:
f.write(response.content)
print(f"Patch saved to {output_file}")
except requests.RequestException as e:
die(f"Failed to download patch from {url}: {e}")
def main():
with open(os.path.join(OUTPUT_DIR, "manifest.json"), 'r') as f:
manifest = json.load(f, cls=JSONWithCommentsDecoder)
expected_files = set()
for patch in manifest:
if patch.get("type") == "phabricator":
phab_id = patch.get("id")
name = patch.get("name")
if not phab_id or not name:
die(f"Patch entry missing 'id' or 'name': {patch}")
name = name.replace(" ", "_").lower()
output_file = os.path.join(OUTPUT_DIR, "firefox", f"{name}.patch")
print(f"Processing Phabricator patch: {phab_id} -> {output_file}")
download_phab_patch(phab_id, output_file)
expected_files.add(output_file)
elif patch.get("type") == "local":
print(f"Local patch: {patch.get('path')}")
expected_files.add(os.path.join(OUTPUT_DIR, patch.get("path")))
elif patch.get("type") == "patch":
url = patch.get("url")
dest = patch.get("dest")
if not url or not dest:
die(f"Patch entry missing 'url' or 'dest': {patch}")
filename = url.split("/")[-1]
output_file = os.path.join(OUTPUT_DIR, dest, filename)
download_patch_from_url(url, output_file)
replaces = patch.get("replaces", {})
for replace in replaces.keys():
value = replaces[replace]
with open(output_file, 'r') as f:
content = f.read()
if replace not in content:
die(f"Replace string '{replace}' not found in {output_file}")
with open(output_file, 'w') as f:
f.write(content.replace(replace, value))
expected_files.add(output_file)
else:
die(f"Unknown patch type: {patch.get('type')}")
# Check for unexpected files in the output directory
# and remove them if they are not in the expected_files set.
for root, dirs, files in os.walk(OUTPUT_DIR):
for file in files:
if file.endswith(".patch"):
file_path = os.path.join(root, file)
if file_path not in expected_files:
print(f"Removing unexpected patch file: {file_path}")
os.remove(file_path)
if __name__ == "__main__":
main()

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index 68c24f730d56f548cf1e286198a04f8363529378..71418c93ce7216d71412f2fa67295322bb73abad 100644
index 68c24f730d56f548cf1e286198a04f8363529378..eb9aa5e77cf549062d8d3770f8057ceafe67c317 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -2,7 +2,7 @@
@@ -22,16 +22,17 @@ index 68c24f730d56f548cf1e286198a04f8363529378..71418c93ce7216d71412f2fa67295322
<toolbar id="TabsToolbar"
class="browser-toolbar browser-titlebar"
fullscreentoolbar="true"
@@ -62,6 +61,8 @@
@@ -62,6 +61,9 @@
<html:sidebar-pins-promo id="drag-to-pin-promo-card"></html:sidebar-pins-promo>
<arrowscrollbox id="pinned-tabs-container" orient="horizontal" clicktoscroll=""></arrowscrollbox>
<splitter orient="vertical" id="vertical-pinned-tabs-splitter" resizebefore="sibling" resizeafter="none" hidden="true"/>
+<html:div id="zen-overflow-extensions-list" skipintoolbarset="true"></html:div>
+<html:div id="zen-essentials" skipintoolbarset="true"></html:div>
+<html:div id="zen-tabs-wrapper">
<hbox class="tab-drop-indicator" hidden="true"/>
<arrowscrollbox id="tabbrowser-arrowscrollbox" orient="horizontal" flex="1" clicktoscroll="" scrolledtostart="" scrolledtoend="">
<tab is="tabbrowser-tab" class="tabbrowser-tab" selected="true" visuallyselected="" fadein=""/>
@@ -81,6 +82,7 @@
@@ -81,6 +83,7 @@
tooltip="dynamic-shortcut-tooltip"
data-l10n-id="tabs-toolbar-new-tab"/>
<html:span id="tabbrowser-tab-a11y-desc" hidden="true"/>
@@ -39,7 +40,7 @@ index 68c24f730d56f548cf1e286198a04f8363529378..71418c93ce7216d71412f2fa67295322
</tabs>
<toolbarbutton id="new-tab-button"
@@ -106,9 +108,10 @@
@@ -106,9 +109,10 @@
#include private-browsing-indicator.inc.xhtml
<toolbarbutton class="content-analysis-indicator toolbarbutton-1 content-analysis-indicator-icon"/>

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/customizableui/CustomizableUI.sys.mjs b/browser/components/customizableui/CustomizableUI.sys.mjs
index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..39e947ce083ce3b293337f5dbb40cd0b46db12e2 100644
index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..c50781a1e8fd1a71baf497ba64d85292fa1347f4 100644
--- a/browser/components/customizableui/CustomizableUI.sys.mjs
+++ b/browser/components/customizableui/CustomizableUI.sys.mjs
@@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
@@ -153,7 +153,17 @@ index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..39e947ce083ce3b293337f5dbb40cd0b
/**
* Add a widget to an area.
* If the area to which you try to add is not known to CustomizableUI,
@@ -7858,7 +7858,7 @@ class OverflowableToolbar {
@@ -7798,7 +7798,9 @@ class OverflowableToolbar {
);
if (webExtList && CustomizableUI.isWebExtensionWidget(child.id)) {
+ if (webExtList.id !== "zen-overflow-extensions-list") {
child.setAttribute("cui-anchorid", webExtButtonID);
+ }
webExtList.insertBefore(child, webExtList.firstElementChild);
} else {
child.setAttribute("cui-anchorid", this.#defaultListButton.id);
@@ -7858,7 +7860,7 @@ class OverflowableToolbar {
) {
continue;
}
@@ -162,7 +172,7 @@ index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..39e947ce083ce3b293337f5dbb40cd0b
if (child != aExceptChild) {
sum += getInlineSize(child);
}
@@ -7882,11 +7882,11 @@ class OverflowableToolbar {
@@ -7882,11 +7884,11 @@ class OverflowableToolbar {
parseFloat(style.paddingLeft) -
parseFloat(style.paddingRight) -
toolbarChildrenWidth;
@@ -176,7 +186,7 @@ index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..39e947ce083ce3b293337f5dbb40cd0b
});
lazy.log.debug(
@@ -7901,7 +7901,14 @@ class OverflowableToolbar {
@@ -7901,7 +7903,14 @@ class OverflowableToolbar {
Math.max(targetWidth, targetChildrenWidth)
);
totalAvailWidth = Math.ceil(totalAvailWidth);
@@ -192,7 +202,7 @@ index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..39e947ce083ce3b293337f5dbb40cd0b
return { isOverflowing, targetContentWidth, totalAvailWidth };
}
@@ -7962,7 +7969,11 @@ class OverflowableToolbar {
@@ -7962,7 +7971,11 @@ class OverflowableToolbar {
return;
}
}
@@ -205,7 +215,7 @@ index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..39e947ce083ce3b293337f5dbb40cd0b
lazy.log.debug(
`Need ${minSize} but width is ${totalAvailWidth} so bailing`
);
@@ -7995,7 +8006,7 @@ class OverflowableToolbar {
@@ -7995,7 +8008,7 @@ class OverflowableToolbar {
}
}
if (!inserted) {
@@ -214,7 +224,27 @@ index 9a98f56d83ee38e0f1aa41467b4ddf215c3d90f7..39e947ce083ce3b293337f5dbb40cd0b
}
child.removeAttribute("cui-anchorid");
child.removeAttribute("overflowedItem");
@@ -8340,7 +8351,7 @@ class OverflowableToolbar {
@@ -8121,6 +8134,9 @@ class OverflowableToolbar {
* if no such list exists.
*/
get #webExtList() {
+ if (this.#toolbar.getAttribute("addon-webext-overflowtarget") !== this.#webExtListRef?.id) {
+ this.#webExtListRef = null;
+ }
if (!this.#webExtListRef) {
let targetID = this.#toolbar.getAttribute("addon-webext-overflowtarget");
if (!targetID) {
@@ -8132,6 +8148,9 @@ class OverflowableToolbar {
let win = this.#toolbar.ownerGlobal;
let { panel } = win.gUnifiedExtensions;
this.#webExtListRef = panel.querySelector(`#${targetID}`);
+ if (!this.#webExtListRef) {
+ this.#webExtListRef = win.document.getElementById(targetID);
+ }
}
return this.#webExtListRef;
}
@@ -8340,7 +8359,7 @@ class OverflowableToolbar {
break;
}
case "mousedown": {

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs
index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96faedf6f0b 100644
index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2061ef7ac60371a563b4e4cd77ceab586f767a5e 100644
--- a/browser/components/sessionstore/SessionStore.sys.mjs
+++ b/browser/components/sessionstore/SessionStore.sys.mjs
@@ -127,6 +127,9 @@ const TAB_EVENTS = [
@@ -79,7 +79,15 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
!lazy.SessionStartup.willRestore()
) {
// We want to split the window up into pinned tabs and unpinned tabs.
@@ -2239,6 +2248,15 @@ var SessionStoreInternal = {
@@ -2211,6 +2220,7 @@ var SessionStoreInternal = {
}
if (newWindowState) {
+ lazy.ZenSessionStore.onRestoringClosedWindow(newWindowState);
// Ensure that the window state isn't hidden
this._restoreCount = 1;
let state = { windows: [newWindowState] };
@@ -2239,6 +2249,15 @@ var SessionStoreInternal = {
});
this._shouldRestoreLastSession = false;
}
@@ -95,7 +103,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
if (this._restoreLastWindow && aWindow.toolbar.visible) {
// always reset (if not a popup window)
@@ -2383,7 +2401,7 @@ var SessionStoreInternal = {
@@ -2383,7 +2402,7 @@ var SessionStoreInternal = {
var tabbrowser = aWindow.gBrowser;
@@ -104,7 +112,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
TAB_EVENTS.forEach(function (aEvent) {
tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
@@ -2434,7 +2452,7 @@ var SessionStoreInternal = {
@@ -2434,7 +2453,7 @@ var SessionStoreInternal = {
let isLastRegularWindow =
Object.values(this._windows).filter(
@@ -113,7 +121,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
).length == 1;
this._log.debug(
`onClose, closing window isLastRegularWindow? ${isLastRegularWindow}`
@@ -2491,8 +2509,8 @@ var SessionStoreInternal = {
@@ -2491,8 +2510,8 @@ var SessionStoreInternal = {
// 2) Flush the window.
// 3) When the flush is complete, revisit our decision to store the window
// in _closedWindows, and add/remove as necessary.
@@ -124,7 +132,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
}
completionPromise = lazy.TabStateFlusher.flushWindow(aWindow).then(() => {
@@ -2512,8 +2530,9 @@ var SessionStoreInternal = {
@@ -2512,8 +2531,9 @@ var SessionStoreInternal = {
// Save non-private windows if they have at
// least one saveable tab or are the last window.
@@ -136,7 +144,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
if (!isLastWindow && winData.closedId > -1) {
this._addClosedAction(
@@ -2589,7 +2608,7 @@ var SessionStoreInternal = {
@@ -2589,7 +2609,7 @@ var SessionStoreInternal = {
* to call this method again asynchronously (for example, after
* a window flush).
*/
@@ -145,7 +153,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// Make sure SessionStore is still running, and make sure that we
// haven't chosen to forget this window.
if (
@@ -2606,6 +2625,7 @@ var SessionStoreInternal = {
@@ -2606,6 +2626,7 @@ var SessionStoreInternal = {
// _closedWindows from a previous call to this function.
let winIndex = this._closedWindows.indexOf(winData);
let alreadyStored = winIndex != -1;
@@ -153,7 +161,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// If sidebar command is truthy, i.e. sidebar is open, store sidebar settings
let shouldStore = hasSaveableTabs || isLastWindow;
@@ -3408,7 +3428,7 @@ var SessionStoreInternal = {
@@ -3408,7 +3429,7 @@ var SessionStoreInternal = {
if (!isPrivateWindow && tabState.isPrivate) {
return;
}
@@ -162,7 +170,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
return;
}
@@ -4129,6 +4149,12 @@ var SessionStoreInternal = {
@@ -4129,6 +4150,12 @@ var SessionStoreInternal = {
Math.min(tabState.index, tabState.entries.length)
);
tabState.pinned = false;
@@ -175,7 +183,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
if (inBackground === false) {
aWindow.gBrowser.selectedTab = newTab;
@@ -4565,6 +4591,8 @@ var SessionStoreInternal = {
@@ -4565,6 +4592,8 @@ var SessionStoreInternal = {
// Append the tab if we're opening into a different window,
tabIndex: aSource == aTargetWindow ? pos : Infinity,
pinned: state.pinned,
@@ -184,7 +192,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
userContextId: state.userContextId,
skipLoad: true,
preferredRemoteType,
@@ -5414,7 +5442,7 @@ var SessionStoreInternal = {
@@ -5414,7 +5443,7 @@ var SessionStoreInternal = {
for (let i = tabbrowser.pinnedTabCount; i < tabbrowser.tabs.length; i++) {
let tab = tabbrowser.tabs[i];
@@ -193,7 +201,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
removableTabs.push(tab);
}
}
@@ -5525,7 +5553,7 @@ var SessionStoreInternal = {
@@ -5525,7 +5554,7 @@ var SessionStoreInternal = {
// collect the data for all windows
for (ix in this._windows) {
@@ -202,7 +210,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// window data is still in _statesToRestore
continue;
}
@@ -5668,11 +5696,12 @@ var SessionStoreInternal = {
@@ -5668,11 +5697,12 @@ var SessionStoreInternal = {
}
let tabbrowser = aWindow.gBrowser;
@@ -216,7 +224,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// update the internal state data for this window
for (let tab of tabs) {
if (tab == aWindow.FirefoxViewHandler.tab) {
@@ -5683,6 +5712,9 @@ var SessionStoreInternal = {
@@ -5683,6 +5713,9 @@ var SessionStoreInternal = {
tabsData.push(tabData);
}
@@ -226,7 +234,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// update tab group state for this window
winData.groups = [];
for (let tabGroup of aWindow.gBrowser.tabGroups) {
@@ -5695,7 +5727,7 @@ var SessionStoreInternal = {
@@ -5695,7 +5728,7 @@ var SessionStoreInternal = {
// a window is closed, point to the first item in the tab strip instead (it will never be the Firefox View tab,
// since it's only inserted into the tab strip after it's selected).
if (aWindow.FirefoxViewHandler.tab?.selected) {
@@ -235,7 +243,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
winData.title = tabbrowser.tabs[0].label;
}
winData.selected = selectedIndex;
@@ -5810,8 +5842,8 @@ var SessionStoreInternal = {
@@ -5810,8 +5843,8 @@ var SessionStoreInternal = {
// selectTab represents.
let selectTab = 0;
if (overwriteTabs) {
@@ -246,7 +254,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
selectTab = Math.min(selectTab, winData.tabs.length);
}
@@ -5833,6 +5865,7 @@ var SessionStoreInternal = {
@@ -5833,6 +5866,7 @@ var SessionStoreInternal = {
if (overwriteTabs) {
for (let i = tabbrowser.browsers.length - 1; i >= 0; i--) {
if (!tabbrowser.tabs[i].selected) {
@@ -254,7 +262,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
tabbrowser.removeTab(tabbrowser.tabs[i]);
}
}
@@ -5866,6 +5899,12 @@ var SessionStoreInternal = {
@@ -5866,6 +5900,12 @@ var SessionStoreInternal = {
savedTabGroup => !openTabGroupIdsInWindow.has(savedTabGroup.id)
);
}
@@ -267,7 +275,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// Move the originally open tabs to the end.
if (initialTabs) {
@@ -6419,6 +6458,25 @@ var SessionStoreInternal = {
@@ -6419,6 +6459,25 @@ var SessionStoreInternal = {
// Most of tabData has been restored, now continue with restoring
// attributes that may trigger external events.
@@ -293,7 +301,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
if (tabData.pinned) {
tabbrowser.pinTab(tab);
@@ -6567,6 +6625,9 @@ var SessionStoreInternal = {
@@ -6567,6 +6626,9 @@ var SessionStoreInternal = {
aWindow.gURLBar.readOnly = false;
}
}
@@ -303,7 +311,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
let promiseParts = Promise.withResolvers();
aWindow.setTimeout(() => {
@@ -7343,7 +7404,7 @@ var SessionStoreInternal = {
@@ -7343,7 +7405,7 @@ var SessionStoreInternal = {
let groupsToSave = new Map();
for (let tIndex = 0; tIndex < window.tabs.length; ) {
@@ -312,7 +320,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// Adjust window.selected
if (tIndex + 1 < window.selected) {
window.selected -= 1;
@@ -7358,7 +7419,7 @@ var SessionStoreInternal = {
@@ -7358,7 +7420,7 @@ var SessionStoreInternal = {
);
// We don't want to increment tIndex here.
continue;

View File

@@ -0,0 +1,3 @@
# Patches imported from LibreWolf
Firefox sometimes makes changes without considering the impact on forks. This is the case of the patches that are imported from LibreWolf, which are already present in LibreWolf but not yet (or never) present in Firefox. Thanks to LibreWolf for making these patches available and for their work in maintaining them. We will keep these patches as long as they are needed, and we will remove them once they are no longer necessary (for example, when they are merged into Firefox or when the issue they solve is no longer present).

View File

@@ -0,0 +1,12 @@
diff --git a/toolkit/moz.configure b/toolkit/moz.configure
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -28,7 +28,7 @@ project_flag(
project_flag(
env="MOZ_APP_UA_NAME",
- default="",
+ default="Firefox",
nargs=1,
help="Application name in the User Agent string",
)

View File

@@ -0,0 +1,24 @@
// 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/.
[
{
"type": "phabricator",
"id": "D279007",
"name": "Fix MacOS Crash on Shutdown Firefox 149"
},
{
"type": "local",
"path": "firefox/no_liquid_glass_icon.patch"
},
{
"type": "patch",
"url": "https://codeberg.org/librewolf/source/raw/branch/main/patches/firefox-in-ua.patch",
"dest": "librewolf",
"replaces": {
// They don't correctly export this patch, so we need to replace
// the parameter's help description with the correct one.
"application": "Application"
}
}
]

View File

@@ -1,13 +0,0 @@
diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build
index d2330003caf35c43d6831fb0d356ece513906f78..76c2faf822ddaf645eb03d93380c58b4e3b510c0 100644
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -227,7 +227,7 @@ LOCAL_INCLUDES += [
"/netwerk/url-classifier",
]
-DEFINES["MOZ_APP_UA_NAME"] = f'"{CONFIG["MOZ_APP_UA_NAME"]}"'
+DEFINES["MOZ_APP_UA_NAME"] = f'"Firefox"'
if CONFIG["MOZ_AUTH_EXTENSION"]:
LOCAL_INCLUDES += [

View File

@@ -1,5 +1,5 @@
diff --git a/toolkit/components/extensions/parent/ext-runtime.js b/toolkit/components/extensions/parent/ext-runtime.js
index 0d7a3e505b6bd30548c6dda1504dd343a517b083..54400def5e02e886765fab68c3854a6b3c24ef2b 100644
index 0d7a3e505b6bd30548c6dda1504dd343a517b083..fb6c6b4ef3eae24995a02f708ec41afd31d812ef 100644
--- a/toolkit/components/extensions/parent/ext-runtime.js
+++ b/toolkit/components/extensions/parent/ext-runtime.js
@@ -333,7 +333,7 @@ this.runtime = class extends ExtensionAPIPersistent {
@@ -7,7 +7,7 @@ index 0d7a3e505b6bd30548c6dda1504dd343a517b083..54400def5e02e886765fab68c3854a6b
getBrowserInfo: function () {
const { name, vendor, version, appBuildID } = Services.appinfo;
- const info = { name, vendor, version, buildID: appBuildID };
+ const info = { name: 'firefox', vendor, version: AppConstants.ZEN_FIREFOX_VERSION, buildID: appBuildID };
+ const info = { name: 'Firefox', vendor, version: AppConstants.ZEN_FIREFOX_VERSION, buildID: appBuildID, zen: {version: AppConstants.MOZ_APP_VERSION_DISPLAY} };
return Promise.resolve(info);
},

View File

@@ -0,0 +1,40 @@
diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
index 64456439499d449ce7f2861b1a5addbeecd61721..d0acdb3082b4805e2b8903f8044c97ddf29419bb 100644
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -1317,9 +1317,11 @@ nsresult nsXREDirProvider::AppendFromAppData(nsIFile* aFile, bool aIsDotted) {
// Similar to nsXREDirProvider::AppendProfilePath.
// TODO: Bug 1990407 - Evaluate if refactoring might be required there in the
// future?
- if (gAppData->profile) {
+ // Use aIsDotted for a different purpose here, will probably break in the future
+ if (gAppData->profile && aIsDotted) {
nsAutoCString profile;
profile = gAppData->profile;
+ profile = "."_ns + nsDependentCString(gAppData->profile);
MOZ_TRY(aFile->AppendRelativeNativePath(profile));
} else {
nsAutoCString vendor;
@@ -1329,8 +1331,6 @@ nsresult nsXREDirProvider::AppendFromAppData(nsIFile* aFile, bool aIsDotted) {
ToLowerCase(vendor);
ToLowerCase(appName);
- MOZ_TRY(aFile->AppendRelativeNativePath(aIsDotted ? ("."_ns + vendor)
- : vendor));
MOZ_TRY(aFile->AppendRelativeNativePath(appName));
}
@@ -1498,13 +1498,8 @@ nsresult nsXREDirProvider::GetLegacyOrXDGHomePath(const char* aHomeDir,
// If the build was made against a specific profile name, MOZ_APP_PROFILE=
// then make sure we respect this and dont move to XDG directory
- if (gAppData->profile) {
- MOZ_TRY(NS_NewNativeLocalFile(nsDependentCString(aHomeDir),
- getter_AddRefs(localDir)));
- } else {
MOZ_TRY(GetLegacyOrXDGConfigHome(aHomeDir, getter_AddRefs(localDir)));
MOZ_TRY(localDir->Clone(getter_AddRefs(parentDir)));
- }
MOZ_TRY(AppendFromAppData(localDir, false));
}

View File

@@ -25,7 +25,7 @@
content/browser/zen-styles/zen-panel-ui.css (../../zen/common/styles/zen-panel-ui.css)
content/browser/zen-styles/zen-single-components.css (../../zen/common/styles/zen-single-components.css)
content/browser/zen-styles/zen-sidebar.css (../../zen/common/styles/zen-sidebar.css)
content/browser/zen-styles/zen-toolbar.css (../../zen/common/styles/zen-toolbar.css)
* content/browser/zen-styles/zen-toolbar.css (../../zen/common/styles/zen-toolbar.css)
content/browser/zen-styles/zen-browser-container.css (../../zen/common/styles/zen-browser-container.css)
content/browser/zen-styles/zen-omnibox.css (../../zen/common/styles/zen-omnibox.css)
content/browser/zen-styles/zen-popup.css (../../zen/common/styles/zen-popup.css)

View File

@@ -1055,7 +1055,7 @@ window.gZenVerticalTabsManager = {
if (!this._hasSetSingleToolbar) {
height = AppConstants.platform == "macosx" ? 34 : 32;
} else if (gURLBar.getAttribute("breakout-extend") !== "true") {
height = 40;
height = 38;
}
if (typeof height !== "undefined") {
gURLBar.style.setProperty("--urlbar-height", `${height}px`);
@@ -1275,6 +1275,15 @@ window.gZenVerticalTabsManager = {
appContentNavbarContaienr.append(windowButtons);
}
if (
this._hasSetSingleToolbar &&
Services.prefs.getBoolPref("zen.view.overflow-webext-toolbar", true)
) {
topButtons.setAttribute("addon-webext-overflowtarget", "zen-overflow-extensions-list");
} else {
topButtons.setAttribute("addon-webext-overflowtarget", "overflowed-extensions-list");
}
gZenCompactModeManager.updateCompactModeContext(isSingleToolbar);
// Always move the splitter next to the sidebar

View File

@@ -132,6 +132,7 @@
}
#nav-bar {
overflow: clip;
border-top-color: transparent !important;
:root[zen-single-toolbar="true"] & {

View File

@@ -80,7 +80,7 @@
#urlbar:not([breakout-extend="true"]) {
&:hover .urlbar-background {
background-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.2)) !important;
background-color: var(--zen-toolbar-element-bg-hover) !important;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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/.
*/
#zen-overflow-extensions-list:not(:empty) {
--uei-icon-size: 14px;
display: flex;
gap: 8px;
padding: 8px 2px;
padding-bottom: 0px;
& .unified-extensions-item {
flex: 1;
margin: 0;
}
& .toolbarbutton-badge-stack {
margin: 0;
width: 100%;
height: 100%;
padding: 8px 0;
justify-content: center;
align-items: center;
}
& .unified-extensions-item-action-button {
background-color: var(--zen-toolbar-element-bg);
height: 30px;
margin: 0;
justify-content: center;
align-items: center;
border-radius: var(--border-radius-medium);
overflow: clip;
padding: 0;
&:hover {
background-color: var(--zen-toolbar-element-bg-hover);
}
}
.unified-extensions-item-contents,
.unified-extensions-item-menu-button,
unified-extensions-item-messagebar-wrapper {
display: none;
}
}

View File

@@ -177,6 +177,7 @@
--toolbarbutton-border-radius: var(--tab-border-radius);
--toolbarbutton-inner-padding: 6px;
--toolbarbutton-outer-padding: 2px;
color: color-mix(in srgb, currentColor 60%, transparent);
transition:
background-color 0.1s,
@@ -191,6 +192,10 @@
}
}
#zen-sidebar-top-buttons toolbarbutton {
padding: 0;
}
.zen-interactive-button {
background: color-mix(in srgb, currentColor 6%, transparent) !important;
transition:

View File

@@ -9,7 +9,7 @@
*/
:host(:is(.anonymous-content-host, notification-message)),
:root {
:root:not([windowtype^="Browser:"]) {
/* Default values */
--zen-border-radius: 7px;
--zen-primary-color: AccentColor;
@@ -141,14 +141,11 @@
--zen-button-padding: 0.6rem 1.2rem;
--zen-toolbar-element-bg: light-dark(
color-mix(in oklch, var(--toolbox-textcolor) 10%, transparent),
color-mix(in oklch, var(--toolbox-textcolor) 8%, transparent),
color-mix(in oklch, var(--toolbox-textcolor) 15%, transparent)
);
--zen-toolbar-element-bg-hover: light-dark(
color-mix(in srgb, var(--zen-toolbar-element-bg) 75%, transparent),
color-mix(in srgb, var(--zen-toolbar-element-bg) 60%, transparent)
);
--zen-toolbar-element-bg-hover: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.2));
/* Toolbar */
--tab-selected-color-scheme: inherit !important;
@@ -222,7 +219,7 @@
--input-border-color: var(--zen-input-border-color) !important;
--zen-themed-toolbar-bg-transparent: light-dark(var(--zen-branding-bg), #171717);
--zen-workspace-indicator-height: 46px;
--zen-workspace-indicator-height: 44px;
&:not([zen-sidebar-expanded='true']) {
--zen-workspace-indicator-height: 38px;
@@ -231,10 +228,7 @@
--toolbar-field-color: var(--toolbox-textcolor) !important;
&[zen-private-window='true'] {
--zen-main-browser-background: linear-gradient(130deg,
color-mix(in srgb, rgb(10, 6, 11) 80%, var(--zen-themed-toolbar-bg-transparent)) 0%,
color-mix(in srgb, rgb(19, 7, 22) 80%, var(--zen-themed-toolbar-bg-transparent)) 100%
);
--zen-main-browser-background: color-mix(in srgb, rgb(11, 10, 11) 90%, var(--zen-themed-toolbar-bg-transparent));
--zen-main-browser-background-toolbar: var(--zen-main-browser-background);
--zen-primary-color: light-dark(rgb(93, 42, 107), rgb(110, 48, 125)) !important;
--toolbox-textcolor: color-mix(in srgb, currentColor 95%, transparent) !important;

View File

@@ -3,6 +3,7 @@
* 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/.
*/
#nav-bar,
#zen-sidebar-top-buttons {
background: transparent;
@@ -11,3 +12,5 @@
:root[inDOMFullscreen="true"] #zen-appcontent-navbar-wrapper {
visibility: collapse;
}
%include zen-overflowing-addons.css

View File

@@ -121,8 +121,11 @@ export const ZenCustomizableUI = new (class {
#initCreateNewButton(window) {
const button = window.document.getElementById("zen-create-new-button");
button.addEventListener("command", (event) => {
if (window.gZenWorkspaces.privateWindowOrDisabled) {
// If we use "mousedown" event for private windows (which open a new tab on "click"), we might end up with
// the urlbar flicking and therefore we use "command" event to avoid that.
let isPrivateMode = window.gZenWorkspaces.privateWindowOrDisabled;
button.addEventListener(isPrivateMode ? "command" : "mousedown", (event) => {
if (isPrivateMode) {
window.document.getElementById("cmd_newNavigatorTab").doCommand();
return;
}

View File

@@ -962,7 +962,8 @@
dropElement = dragData.dropElement;
dropBefore = dragData.dropBefore;
}
if (!dropElement) {
// Essentials should be properly handled by ::animateVerticalPinnedGridDragOver
if (!dropElement || dropElement.hasAttribute("zen-essential")) {
this.clearDragOverVisuals();
return null;
}

View File

@@ -21,7 +21,7 @@ export class nsZenFolder extends MozTabbrowserTabGroup {
static rawIcon = new DOMParser().parseFromString(
`
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="28" height="28" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient gradientUnits="userSpaceOnUse" x1="14" y1="5.625" x2="14" y2="22.375" id="gradient-0">
<stop offset="0" style="stop-color: rgb(255, 255, 255)"/>
@@ -242,7 +242,7 @@ export class nsZenFolder extends MozTabbrowserTabGroup {
}
get resetButton() {
return this.labelElement.parentElement.querySelector(".tab-reset-button");
return this.labelElement.parentElement?.querySelector(".tab-reset-button") ?? null;
}
unloadAllTabs(event) {

View File

@@ -52,6 +52,9 @@ class nsZenSidebarObject {
}
set data(data) {
if (typeof data !== "object") {
throw new Error("Sidebar data must be an object");
}
this.#sidebar = data;
}
}
@@ -384,6 +387,25 @@ export class nsZenSessionManager {
return initialState;
}
onRestoringClosedWindow(aWinData) {
// We only want to save all pinned tabs if the user preference allows it.
// See https://github.com/zen-browser/desktop/issues/12307
if (this.#shouldRestoreOnlyPinned && aWinData?.tabs?.length) {
this.log("Restoring only pinned tabs for closed window");
this.#filterUnpinnedTabs(aWinData);
}
}
/**
* Filters out all unpinned tabs and groups from the given window data object.
*
* @param {object} aWindow - The window data object to filter.
*/
#filterUnpinnedTabs(aWindow) {
aWindow.tabs = aWindow.tabs.filter((tab) => tab.pinned);
aWindow.groups = aWindow.groups?.filter((group) => group.pinned);
}
/**
* Determines if a given window data object is saveable.
*
@@ -410,10 +432,11 @@ export class nsZenSessionManager {
this.#collectWindowData(windows);
// This would save the data to disk asynchronously or when
// quitting the app.
this.#file.data = this.#sidebar;
let sidebar = this.#sidebar;
this.#file.data = sidebar;
this.#file.saveSoon();
this.#debounceRegeneration();
this.log(`Saving Zen session data with ${this.#sidebar.tabs?.length || 0} tabs`);
this.log(`Saving Zen session data with ${sidebar.tabs?.length || 0} tabs`);
}
/**
@@ -514,6 +537,13 @@ export class nsZenSessionManager {
this.#sidebar = sidebarData;
}
/**
* Filters out tabs that are not useful to restore, such as empty tabs with no group association.
* If removeUnpinnedTabs is true, it also filters out unpinned tabs.
*
* @param {Array} tabs - The array of tab data objects to filter.
* @returns {Array} The filtered array of tab data objects.
*/
#filterUnusedTabs(tabs) {
return tabs.filter((tab) => {
// We need to ignore empty tabs with no group association
@@ -568,7 +598,10 @@ export class nsZenSessionManager {
// as they should be the same as the ones in the sidebar.
if (lazy.gSyncOnlyPinnedTabs) {
let pinnedTabs = (sidebar.tabs || []).filter((tab) => tab.pinned);
let unpinedWindowTabs = (aWindowData.tabs || []).filter((tab) => !tab.pinned);
let unpinedWindowTabs = [];
if (!this.#shouldRestoreOnlyPinned) {
unpinedWindowTabs = (aWindowData.tabs || []).filter((tab) => !tab.pinned);
}
aWindowData.tabs = [...pinnedTabs, ...unpinedWindowTabs];
// We restore ALL the split view data in the sidebar, if the group doesn't exist in the window,
@@ -613,18 +646,19 @@ export class nsZenSessionManager {
);
let windowToClone = windows[0] || {};
let newWindow = Cu.cloneInto(windowToClone, {});
let shouldRestoreOnlyPinned = !lazy.gWindowSyncEnabled || lazy.gSyncOnlyPinnedTabs;
if (windows.length < 2) {
// We only want to restore the sidebar object if we found
// only one normal window to clone from (which is the one
// we are opening).
this.log("Restoring sidebar data into new window");
this.#restoreWindowData(newWindow);
shouldRestoreOnlyPinned ||= this.#shouldRestoreOnlyPinned;
}
newWindow.tabs = this.#filterUnusedTabs(newWindow.tabs || []);
if (!lazy.gWindowSyncEnabled || lazy.gSyncOnlyPinnedTabs) {
if (shouldRestoreOnlyPinned) {
// Don't bring over any unpinned tabs if window sync is disabled or if syncing only pinned tabs.
newWindow.tabs = newWindow.tabs.filter((tab) => tab.pinned);
newWindow.groups = newWindow.groups?.filter((group) => group.pinned);
this.#filterUnpinnedTabs(newWindow);
}
// These are window-specific from the previous window state that

View File

@@ -327,7 +327,7 @@ class nsZenWindowSync {
return;
}
if (INSTANT_EVENTS.includes(aEvent.type)) {
this.#handleNextEvent(aEvent);
this.#handleNextEventInternal(aEvent);
return;
}
if (this.#eventHandlingContext.window && this.#eventHandlingContext.window !== window) {
@@ -374,30 +374,31 @@ class nsZenWindowSync {
this.#syncHandlers.delete(aHandler);
}
#handleNextEventInternal(aEvent) {
const handler = `on_${aEvent.type}`;
if (typeof this[handler] !== "function") {
throw new Error(`No handler for event type: ${aEvent.type}`);
}
return this[handler](aEvent);
}
/**
* Handles the next event by calling the appropriate handler method.
*
* @param {Event} aEvent - The event to handle.
*/
#handleNextEvent(aEvent) {
const handler = `on_${aEvent.type}`;
async #handleNextEvent(aEvent) {
try {
if (typeof this[handler] === "function") {
let promise = this[handler](aEvent) || Promise.resolve();
promise.then(() => {
for (let syncHandler of this.#syncHandlers) {
try {
syncHandler(aEvent);
} catch (e) {
console.error(e);
}
}
});
return promise;
}
throw new Error(`No handler for event type: ${aEvent.type}`);
await this.#handleNextEventInternal(aEvent);
} catch (e) {
return Promise.reject(e);
console.error(e);
}
for (let syncHandler of this.#syncHandlers) {
try {
syncHandler(aEvent);
} catch (e) {
console.error(e);
}
}
}
@@ -790,39 +791,41 @@ class nsZenWindowSync {
#styleSwapedBrowsers(aOurTab, aOtherTab, callback = undefined, promiseToWait = null) {
const ourBrowser = aOurTab.linkedBrowser;
const otherBrowser = aOtherTab.linkedBrowser;
return new Promise((resolve) => {
aOurTab.ownerGlobal.requestAnimationFrame(async () => {
if (callback) {
const browserBlob = await aOtherTab.ownerGlobal.PageThumbs.captureToBlob(
aOtherTab.linkedBrowser,
{
fullScale: true,
fullViewport: true,
}
);
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve) => {
if (callback) {
const browserBlob = await aOtherTab.ownerGlobal.PageThumbs.captureToBlob(
aOtherTab.linkedBrowser,
{
fullScale: true,
fullViewport: true,
}
);
let mySrc = await new Promise((r, re) => {
const reader = new FileReader();
reader.readAsDataURL(browserBlob);
reader.onloadend = function () {
// result includes identifier 'data:image/png;base64,' plus the base64 data
r(reader.result);
};
reader.onerror = function () {
re(new Error("Failed to read blob as data URL"));
};
});
this.#createPseudoImageForBrowser(otherBrowser, mySrc);
otherBrowser.setAttribute("zen-pseudo-hidden", "true");
await promiseToWait;
callback();
}
let mySrc = await new Promise((r, re) => {
const reader = new FileReader();
reader.readAsDataURL(browserBlob);
reader.onloadend = function () {
// result includes identifier 'data:image/png;base64,' plus the base64 data
r(reader.result);
};
reader.onerror = function () {
re(new Error("Failed to read blob as data URL"));
};
});
await promiseToWait;
this.#createPseudoImageForBrowser(otherBrowser, mySrc);
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
ourBrowser.removeAttribute("zen-pseudo-hidden");
resolve();
});
otherBrowser.setAttribute("zen-pseudo-hidden", "true");
callback();
} else {
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
ourBrowser.removeAttribute("zen-pseudo-hidden");
}
resolve();
});
}
@@ -924,7 +927,9 @@ class nsZenWindowSync {
// Ignore previous tabs that are still "active". These scenarios could happen for example,
// when selecting on a split view tab that was already active.
if (aPreviousTab?._zenContentsVisible && !activeTabs.includes(aPreviousTab)) {
let tabsToSwap = aPreviousTab.splitView ? aPreviousTab.group.tabs : [aPreviousTab];
let tabsToSwap = aPreviousTab.group?.hasAttribute("split-view-group")
? aPreviousTab.group.tabs
: [aPreviousTab];
for (const tab of tabsToSwap) {
const otherTabToShow = this.#getActiveTabFromOtherWindows(aWindow, tab.id, (t) =>
t?.splitView ? t.group.tabs.some((st) => st.selected) : t?.selected

View File

@@ -208,6 +208,10 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
if (typeof groupIndex === "undefined") {
groupIndex = this._data.findIndex((group) => group.tabs.includes(tab));
}
// If groupIndex === -1, so `this._data.findIndex` couldn't find the split group
if (groupIndex < 0) {
return;
}
const group = this._data[groupIndex];
const tabIndex = group.tabs.indexOf(tab);
group.tabs.splice(tabIndex, 1);
@@ -259,6 +263,33 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
return element;
}
_calculateDropSide(event, panelsRect) {
const { width, height } = panelsRect;
const { clientX, clientY } = event;
// TODO(octaviusz): Maybe we should add this as preference
// `zen.splitView.tab-drop-treshold`
const quarterWidth = width / 4;
const quarterHeight = height / 4;
const edges = [
{ side: "left", dist: clientX - panelsRect.left, threshold: quarterWidth },
{ side: "right", dist: panelsRect.right - clientX, threshold: quarterWidth },
{ side: "top", dist: clientY - panelsRect.top, threshold: quarterHeight },
{ side: "bottom", dist: panelsRect.bottom - clientY, threshold: quarterHeight },
];
let closestEdge = null;
let minDist = Infinity;
for (const edge of edges) {
if (edge.dist < edge.threshold && edge.dist < minDist) {
minDist = edge.dist;
closestEdge = edge;
}
}
return closestEdge ? closestEdge.side : null;
}
// eslint-disable-next-line complexity
onBrowserDragOverToSplit(event) {
gBrowser.tabContainer.tabDragAndDrop.clearSpaceSwitchTimer();
@@ -303,6 +334,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
const panelsRect = gBrowser.tabbox.getBoundingClientRect();
const panelsWidth = panelsRect.width;
const panelsHeight = panelsRect.height;
if (
event.clientX > panelsRect.left + panelsWidth - 10 ||
event.clientX < panelsRect.left + 10 ||
@@ -311,11 +343,17 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
) {
return;
}
const dropSide = this._calculateDropSide(event, panelsRect);
if (!dropSide) {
return;
}
// first quarter or last quarter of the screen, but not the middle
if (
!(
event.clientX < panelsRect.left + panelsWidth / 4 ||
event.clientX > panelsRect.left + (panelsWidth / 4) * 3
event.clientX > panelsRect.left + (panelsWidth / 4) * 3 ||
event.clientY < panelsRect.top + panelsHeight / 4 ||
event.clientY > panelsRect.top + (panelsHeight / 4) * 3
)
) {
return;
@@ -336,93 +374,113 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
this._canDrop = true;
// eslint-disable-next-line mozilla/valid-services
Services.zen.playHapticFeedback();
{
this._draggingTab = draggedTab;
gBrowser.selectedTab = oldTab;
this._hasAnimated = true;
this.tabBrowserPanel.setAttribute("dragging-split", "true");
// Add a min width to all the browser elements to prevent them from resizing
// eslint-disable-next-line no-shadow
const panelsWidth = gBrowser.tabbox.getBoundingClientRect().width;
let numOfTabsToDivide = 2;
if (currentView) {
numOfTabsToDivide = currentView.tabs.length + 1;
this._draggingTab = draggedTab;
gBrowser.selectedTab = oldTab;
this._hasAnimated = true;
this.tabBrowserPanel.setAttribute("dragging-split", "true");
this._animateDropEdge(dropSide, currentView, draggedTab, oldTab);
}
_animateDropEdge(dropSide, currentView, draggedTab, oldTab) {
// Add a min width to all the browser elements to prevent them from resizing
// eslint-disable-next-line no-shadow
const { height, width } = gBrowser.tabbox.getBoundingClientRect();
let numOfTabsToDivide = 2;
if (currentView) {
numOfTabsToDivide = currentView.tabs.length + 1;
}
const halfWidth = width / numOfTabsToDivide;
const halfHeight = height / numOfTabsToDivide;
const side = dropSide;
for (const browser of gBrowser.browsers) {
if (!browser) {
continue;
}
const halfWidth = panelsWidth / numOfTabsToDivide;
let threshold =
gNavToolbox.getBoundingClientRect().width *
(gZenVerticalTabsManager._prefsRightSide ? 0 : 1);
if (gZenCompactModeManager.preference) {
threshold = 0;
const { width: browserWidth, height: browserHeight } = browser.getBoundingClientRect();
// Only apply it to the left side because if we add it to the right side,
// we wont be able to move the element to the left.
// FIXME: This is a workaround, we should find a better way to do this
switch (side) {
case "left":
browser.style.minWidth = `${browserWidth}px`;
break;
case "top":
browser.style.minHeight = `${browserHeight}px`;
break;
}
const side = event.clientX - threshold > halfWidth ? "right" : "left";
for (const browser of gBrowser.browsers) {
if (!browser) {
continue;
}
this.fakeBrowser = document.createXULElement("vbox");
window.addEventListener("dragend", this.onBrowserDragEndToSplit, { once: true });
const padding = ZenThemeModifier.elementSeparation;
this.fakeBrowser.setAttribute("flex", "1");
this.fakeBrowser.id = "zen-split-view-fake-browser";
if (oldTab.splitView) {
this.fakeBrowser.setAttribute("has-split-view", "true");
}
gBrowser.tabbox.appendChild(this.fakeBrowser);
this.fakeBrowser.setAttribute("side", side);
let animateTabBox = null;
let animateFakeBrowser = null;
switch (side) {
case "left":
animateTabBox = {
padding: [0, `0 0 0 ${halfWidth}px`],
};
animateFakeBrowser = {
width: [0, `${halfWidth - padding}px`],
margin: [0, `0 0 0 ${-halfWidth}px`],
};
break;
case "right":
animateTabBox = {
padding: [0, `0 ${halfWidth}px 0 0`],
};
animateFakeBrowser = {
width: [0, `${halfWidth - padding}px`],
};
break;
case "top":
animateTabBox = {
padding: [0, `${halfHeight}px 0 0 0`],
};
animateFakeBrowser = {
height: [0, `${halfHeight - padding}px`],
margin: [0, `${-halfHeight}px 0 0 0`],
};
break;
case "bottom":
animateTabBox = {
padding: [0, `0 0 ${halfHeight}px 0`],
};
animateFakeBrowser = {
height: [0, `${halfHeight - padding}px`],
};
break;
}
this._finishAllAnimatingPromise = Promise.all([
gZenUIManager.motion.animate(gBrowser.tabbox, animateTabBox, {
duration: 0.1,
easing: "ease-out",
}),
gZenUIManager.motion.animate(this.fakeBrowser, animateFakeBrowser, {
duration: 0.1,
easing: "ease-out",
}),
]);
if (this._finishAllAnimatingPromise) {
this._finishAllAnimatingPromise.then(() => {
if (draggedTab !== oldTab) {
draggedTab.linkedBrowser.docShellIsActive = false;
draggedTab.linkedBrowser
.closest(".browserSidebarContainer")
.classList.remove("deck-selected");
}
const width = browser.getBoundingClientRect().width;
// Only apply it to the left side because if we add it to the right side,
// we wont be able to move the element to the left.
// FIXME: This is a workaround, we should find a better way to do this
if (side === "left") {
browser.style.minWidth = `${width}px`;
}
}
this.fakeBrowser = document.createXULElement("vbox");
window.addEventListener("dragend", this.onBrowserDragEndToSplit, { once: true });
const padding = ZenThemeModifier.elementSeparation;
this.fakeBrowser.setAttribute("flex", "1");
this.fakeBrowser.id = "zen-split-view-fake-browser";
if (oldTab.splitView) {
this.fakeBrowser.setAttribute("has-split-view", "true");
}
gBrowser.tabbox.appendChild(this.fakeBrowser);
this.fakeBrowser.setAttribute("side", side);
this._finishAllAnimatingPromise = Promise.all([
gZenUIManager.motion.animate(
gBrowser.tabbox,
side === "left"
? {
paddingLeft: [0, `${halfWidth}px`],
paddingRight: 0,
}
: {
paddingRight: [0, `${halfWidth}px`],
paddingLeft: 0,
},
{
duration: 0.1,
easing: "ease-out",
}
),
gZenUIManager.motion.animate(
this.fakeBrowser,
{
width: [0, `${halfWidth - padding}px`],
...(side === "left"
? {
marginLeft: [0, `${-halfWidth}px`],
}
: {}),
},
{
duration: 0.1,
easing: "ease-out",
}
),
]);
if (this._finishAllAnimatingPromise) {
this._finishAllAnimatingPromise.then(() => {
if (draggedTab !== oldTab) {
draggedTab.linkedBrowser.docShellIsActive = false;
draggedTab.linkedBrowser
.closest(".browserSidebarContainer")
.classList.remove("deck-selected");
}
this.fakeBrowser.addEventListener("dragleave", this.onBrowserDragEndToSplit);
this._canDrop = true;
});
}
this.fakeBrowser.addEventListener("dragleave", this.onBrowserDragEndToSplit);
this._canDrop = true;
});
}
}
@@ -447,12 +505,14 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
return;
}
const panelsWidth = panelsRect.width;
const panelsHeight = panelsRect.height;
let numOfTabsToDivide = 2;
const currentView = this._data[this._lastOpenedTab.splitViewValue];
if (currentView) {
numOfTabsToDivide = currentView.tabs.length + 1;
}
const halfWidth = panelsWidth / numOfTabsToDivide;
const halfHeight = panelsHeight / numOfTabsToDivide;
const padding = ZenThemeModifier.elementSeparation;
if (!this.fakeBrowser) {
return;
@@ -464,39 +524,60 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
...gBrowser.tabContainer.tabDragAndDrop.originalDragImageArgs
);
this._canDrop = false;
Promise.all([
gZenUIManager.motion.animate(
gBrowser.tabbox,
side === "left"
? {
paddingLeft: [`${halfWidth}px`, 0],
}
: {
paddingRight: [`${halfWidth}px`, 0],
},
{
duration: 0.1,
easing: "ease-out",
}
),
gZenUIManager.motion.animate(
this.fakeBrowser,
{
width: [`${halfWidth - padding * 2}px`, 0],
...(side === "left"
? {
marginLeft: [`${-halfWidth}px`, 0],
}
: {}),
},
{
duration: 0.1,
easing: "ease-out",
}
),
]).finally(() => {
this._maybeRemoveFakeBrowser();
});
let animateTabBox = null;
let animateFakeBrowser = null;
switch (side) {
case "left":
animateTabBox = {
padding: [`0 0 0 ${halfWidth}px`, 0],
};
animateFakeBrowser = {
width: [`${halfWidth - padding}px`, 0],
margin: [`0 0 0 ${-halfWidth}px`, 0],
};
break;
case "right":
animateTabBox = {
padding: [`0 ${halfWidth}px 0 0`, 0],
};
animateFakeBrowser = {
width: [`${halfWidth - padding}px`, 0],
};
break;
case "top":
animateTabBox = {
padding: [`${halfHeight}px 0 0 0`, 0],
};
animateFakeBrowser = {
height: [`${halfHeight - padding}px`, 0],
margin: [`${-halfHeight}px 0 0 0`, 0],
};
break;
case "bottom":
animateTabBox = {
padding: [`0 0 ${halfHeight}px 0`, 0],
};
animateFakeBrowser = {
height: [`${halfHeight - padding}px`, 0],
};
break;
}
this._finishAllAnimatingPromise = Promise.all([
gZenUIManager.motion.animate(gBrowser.tabbox, animateTabBox, {
duration: 0.1,
easing: "ease-out",
}),
gZenUIManager.motion.animate(this.fakeBrowser, animateFakeBrowser, {
duration: 0.1,
easing: "ease-out",
}),
]);
if (this._finishAllAnimatingPromise) {
this._finishAllAnimatingPromise.then(() => {
this._maybeRemoveFakeBrowser();
});
}
}
/**
@@ -1845,12 +1926,24 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
const dropSide = this.fakeBrowser?.getAttribute("side");
const containerRect = this.fakeBrowser.getBoundingClientRect();
const padding = ZenThemeModifier.elementSeparation;
const dropTarget = document.elementFromPoint(
dropSide === "left"
? containerRect.left + containerRect.width + padding + 5
: containerRect.left - padding - 5,
event.clientY
);
let targetX = event.clientX;
let targetY = event.clientY;
switch (dropSide) {
case "left":
targetX = containerRect.left + containerRect.width + padding + 5;
break;
case "right":
targetX = containerRect.left - padding - 5;
break;
case "top":
targetY = containerRect.top + containerRect.height + padding + 5;
break;
case "bottom":
targetY = containerRect.top - padding - 5;
break;
}
const dropTarget = document.elementFromPoint(targetX, targetY);
const browser =
dropTarget?.closest("browser") ??
dropTarget?.closest(".browserSidebarContainer")?.querySelector("browser");
@@ -1862,7 +1955,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
let droppedOnTab = gZenGlanceManager.getTabOrGlanceParent(gBrowser.getTabForBrowser(browser));
if (droppedOnTab === this._draggingTab) {
this.createEmptySplit(dropSide == "right");
this.createEmptySplit(dropSide);
return true;
}
@@ -1919,29 +2012,21 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
}
}
const droppedOnSplitNode = this.getSplitNodeFromTab(droppedOnTab);
const parentNode = droppedOnSplitNode.parent;
// Then add the tab to the split view
group.tabs.push(draggedTab);
// If dropping on a side, create a new split in that direction
// If dropping on a side, wrap entire layout in a new split at the root level
if (hoverSide !== "center") {
const splitDirection = hoverSide === "left" || hoverSide === "right" ? "row" : "column";
if (parentNode.direction !== splitDirection) {
this.splitIntoNode(
droppedOnSplitNode,
new nsSplitLeafNode(draggedTab, 50),
hoverSide,
0.5
);
const rootNode = group.layoutTree;
const prepend = hoverSide === "left" || hoverSide === "top";
if (rootNode.direction === splitDirection) {
// Root has the same direction, add as a new child of the root
this.addTabToSplit(draggedTab, rootNode, prepend);
} else {
this.addTabToSplit(
draggedTab,
parentNode,
/* prepend = */ hoverSide === "left" || hoverSide === "top"
);
// Different direction, wrap root in a new split node
this.splitIntoNode(rootNode, new nsSplitLeafNode(draggedTab, 50), hoverSide, 0.5);
}
} else {
this.addTabToSplit(draggedTab, group.layoutTree);
@@ -1951,13 +2036,14 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
} else {
// Create new split view with layout based on drop position
let gridType = "vsep";
const gridType = dropSide === "top" || dropSide === "bottom" ? "hsep" : "vsep";
const topOrLeft = dropSide === "top" || dropSide === "left";
// Put tabs always as if it was dropped from the left
this.splitTabs(
dropSide == "left" ? [draggedTab, droppedOnTab] : [droppedOnTab, draggedTab],
topOrLeft ? [draggedTab, droppedOnTab] : [droppedOnTab, draggedTab],
gridType,
dropSide == "left" ? 0 : 1
topOrLeft ? 0 : 1
);
}
@@ -2192,14 +2278,16 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
}
createEmptySplit(rightSide = true) {
createEmptySplit(side = "right") {
const selectedTab = gBrowser.selectedTab;
const emptyTab = gZenWorkspaces._emptyTab;
let tabs = rightSide ? [selectedTab, emptyTab] : [emptyTab, selectedTab];
const gridType = side === "top" || side === "bottom" ? "hsep" : "vsep";
const topOrLeft = side === "top" || side === "left";
let tabs = topOrLeft ? [emptyTab, selectedTab] : [selectedTab, emptyTab];
const data = {
tabs,
gridType: "grid",
layoutTree: this.calculateLayoutTree(tabs, "grid"),
gridType,
layoutTree: this.calculateLayoutTree(tabs, gridType),
};
this.#withoutSplitViewTransition(() => {
this._data.push(data);
@@ -2234,9 +2322,9 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
gBrowser.selectedTab = selectedTab;
this.resetTabState(emptyTab, false);
this.splitTabs(
rightSide ? [selectedTab, newSelectedTab] : [newSelectedTab, selectedTab],
"grid",
rightSide ? 1 : 0
topOrLeft ? [newSelectedTab, selectedTab] : [selectedTab, newSelectedTab],
gridType,
topOrLeft ? 0 : 1
);
} else {
cleanup();

View File

@@ -44,12 +44,12 @@
#zen-splitview-dropzone {
position: absolute !important;
margin: var(--zen-split-column-gap) var(--zen-split-row-gap) !important;
margin-bottom: 0 !important;
margin-left: 0 !important;
margin: var(--zen-split-column-gap) var(--zen-split-row-gap);
margin-bottom: 0;
margin-left: 0;
&.browserSidebarContainer:not([zen-split='true']) {
margin-top: 0 !important;
margin-top: 0;
visibility: hidden;
}
}
@@ -204,6 +204,15 @@
overflow: hidden;
will-change: width, margin-left;
&[side='top'],
&[side='bottom'] {
width: 100%;
&[has-split-view='true'] {
width: calc(100% - var(--zen-element-separation));
}
}
&[side='right'] {
right: 0;
@@ -211,6 +220,10 @@
right: var(--zen-element-separation);
}
}
&[side='bottom'] {
bottom: 0;
}
}
#zen-split-view-drag-image {

View File

@@ -531,8 +531,16 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
const isVisible = contextTab.pinned && !contextTab.multiselected;
const isEssential = contextTab.getAttribute("zen-essential");
const zenAddEssential = document.getElementById("context_zen-add-essential");
document.getElementById("context_zen-reset-pinned-tab").hidden = !isVisible;
document.getElementById("context_zen-replace-pinned-url-with-current").hidden = !isVisible;
const zenResetPinnedTab = document.getElementById("context_zen-reset-pinned-tab");
const zenReplacePinnedUrl = document.getElementById(
"context_zen-replace-pinned-url-with-current"
);
[zenResetPinnedTab, zenReplacePinnedUrl].forEach((element) => {
if (element) {
element.hidden = !isVisible;
document.l10n.setArgs(element, { isEssential });
}
});
zenAddEssential.hidden = isEssential || !!contextTab.group;
document.l10n
.formatValue("tab-context-zen-add-essential-badge", {

View File

@@ -209,7 +209,7 @@
}
:root[zen-unsynced-window="true"] & {
transform: translateY(-4px);
display: none !important;
}
}

View File

@@ -6,9 +6,12 @@
# This file is autogenerated by scripts/import_external_tests.py
# Do not edit manually.
BROWSER_CHROME_MANIFESTS += [
"safebrowsing/browser.toml",
"sandbox/browser.toml",
"shell/browser.toml",
"tooltiptext/browser.toml",
]
XPCSHELL_TESTS_MANIFESTS += [
]

View File

@@ -682,13 +682,6 @@ async function testFileAccessWindowsOnly() {
let tests = [];
let extDir = GetPerUserExtensionDir();
// We used to unconditionally create this directory from Firefox, but that
// was dropped in bug 2001887. The value of this directory is questionable;
// the test was added in Firefox 56 (bug 1403744) to cover legacy add-ons,
// but legacy add-on support was discontinued in Firefox 57, and we stopped
// sideloading add-ons from this directory on all builds except ESR in
// Firefox 74 (bug 1602840).
await IOUtils.makeDirectory(extDir.path);
tests.push({
desc: "per-user extensions dir",
ok: true,

View File

@@ -22,13 +22,6 @@ add_setup(async function setup() {
const xdgConfigHome = Services.env.get("XDG_CONFIG_HOME");
Assert.greater(xdgConfigHome.length, 1, "XDG_CONFIG_HOME is defined");
// Verify the profile directory is inside XDG_CONFIG_HOME
const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
Assert.ok(
profileDir.path.startsWith(xdgConfigHome),
`Profile directory (${profileDir.path}) should be inside XDG_CONFIG_HOME (${xdgConfigHome})`
);
// If it is there, do actual testing
sanityChecks();
});

View File

@@ -12,14 +12,11 @@ support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
]
test-directories = [
"/tmp/.xdg_default_test",
"/tmp/.xdg_default_test/.config/mozilla/firefox/xdg_default_profile",
]
# .config needs to exists for the sandbox to properly add it
test-directories = ["/tmp/.xdg_default_test", "/tmp/.xdg_default_test/.config"]
environment = [
"HOME=/tmp/.xdg_default_test",
]
profile-path = "/tmp/.xdg_default_test/.config/mozilla/firefox/xdg_default_profile"
["browser_content_sandbox_fs_xdg_default.js"]
run-if = ["os == 'linux'"]

View File

@@ -12,17 +12,12 @@ support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
]
test-directories = [
"/tmp/.xdg_mozLegacyHome_test/.config",
"/tmp/.xdg_config_home_test",
"/tmp/.xdg_mozLegacyHome_test/.mozilla/firefox/xdg_mozLegacyHome_profile",
]
test-directories = ["/tmp/.xdg_mozLegacyHome_test/.config", "/tmp/.xdg_config_home_test"]
environment = [
"XDG_CONFIG_HOME=/tmp/.xdg_config_home_test",
"HOME=/tmp/.xdg_mozLegacyHome_test",
"MOZ_LEGACY_HOME=1",
]
profile-path = "/tmp/.xdg_mozLegacyHome_test/.mozilla/firefox/xdg_mozLegacyHome_profile"
["browser_content_sandbox_fs_xdg_mozLegacyHome.js"]
run-if = ["os == 'linux'"]

View File

@@ -11,15 +11,11 @@ support-files = [
"browser_content_sandbox_utils.js",
"browser_content_sandbox_fs_tests.js",
]
test-directories = [
"/tmp/.xdg_config_home_test",
"/tmp/.xdg_config_home_test/mozilla/firefox/xdg_config_home_profile",
]
test-directories = "/tmp/.xdg_config_home_test"
environment = [
"XDG_CONFIG_HOME=/tmp/.xdg_config_home_test",
"MOZ_LEGACY_HOME=0",
]
profile-path = "/tmp/.xdg_config_home_test/mozilla/firefox/xdg_config_home_profile"
["browser_content_sandbox_fs_xdg_xdgConfigHome.js"]
run-if = [

View File

@@ -57,12 +57,6 @@ window.ZenWorkspaceBookmarksStorage = {
CREATE INDEX IF NOT EXISTS idx_bookmarks_workspaces_changes
ON zen_bookmarks_workspaces_changes(bookmark_guid, workspace_uuid)
`);
// Before, workspace_uuid was a FOREIGN KEY, not anymore, so we need to drop the constraint
// This is a no-op if the constraint doesn't exist
await db.execute(`
PRAGMA foreign_keys = OFF;
`);
}
);
},

View File

@@ -231,7 +231,7 @@ class nsZenWorkspaces {
}
}
async selectEmptyTab(newTabTarget = null, selectURLBar = true) {
async selectEmptyTab(newTabTarget = null) {
// Validate browser state first
if (!this._validateBrowserState()) {
console.warn("Browser state invalid for empty tab selection");
@@ -251,30 +251,6 @@ class nsZenWorkspaces {
!this._emptyTab.ownerGlobal.closed &&
gZenVerticalTabsManager._canReplaceNewTab
) {
// Only set up URL bar selection if we're switching to a different tab
if (gBrowser.selectedTab !== this._emptyTab && selectURLBar) {
const tabSelectListener = () => {
// Remove the event listener first to prevent any chance of multiple executions
window.removeEventListener("TabSelect", tabSelectListener);
// Use requestAnimationFrame to ensure DOM is updated
requestAnimationFrame(() => {
// Then use setTimeout to ensure browser has time to process tab switch
setTimeout(() => {
if (gURLBar) {
try {
gURLBar.select();
} catch (e) {
console.warn("Error selecting URL bar:", e);
}
}
}, 50);
});
};
window.addEventListener("TabSelect", tabSelectListener, { once: true });
}
// Safely switch to the empty tab using our debounced method
const success = await this._safelySelectTab(this._emptyTab);
if (!success) {
@@ -1044,6 +1020,8 @@ class nsZenWorkspaces {
delete this._initialTab;
}
showed &&= Services.prefs.getBoolPref("zen.urlbar.open-on-startup", true);
// Wait for the next event loop to ensure that the startup focus logic by
// firefox has finished doing it's thing.
setTimeout(() => {
@@ -1130,7 +1108,7 @@ class nsZenWorkspaces {
return (
!window.toolbar.visible ||
Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab") ||
this.privateWindowOrDisabled
(this.privateWindowOrDisabled && !this.isPrivateWindow)
);
}
@@ -2436,7 +2414,10 @@ class nsZenWorkspaces {
if (!this.currentWindowIsSyncing) {
containerTabId = parseInt(gBrowser.selectedTab.getAttribute("usercontextid")) || 0;
let label = ContextualIdentityService.getUserContextLabel(containerTabId) || "Default";
name = this.isPrivateWindow ? "Private " + name : label;
name = this.isPrivateWindow ? "Incognito" : label;
if (this.isPrivateWindow) {
icon = gZenEmojiPicker.getSVGURL("eye.svg");
}
}
let workspace = {
uuid: gZenUIManager.generateUuidv4(),

View File

@@ -156,7 +156,7 @@
/* Mark workspaces indicator */
.zen-current-workspace-indicator {
--indicator-gap: calc(var(--toolbarbutton-inner-padding) - 1px);
padding: calc(2px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
padding: calc(3px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
font-weight: 500;
position: relative;
max-height: var(--zen-workspace-indicator-height);
@@ -177,10 +177,10 @@
pointer-events: none;
content: '';
position: absolute;
top: var(--zen-toolbox-padding);
top: 4px;
left: calc(var(--zen-toolbox-padding) + 2px);
width: calc(100% - var(--zen-toolbox-padding) * 3);
height: calc(100% - var(--zen-toolbox-padding) * 2);
width: calc(100% - var(--zen-toolbox-padding) * 2.5);
height: calc(100% - 8px);
}
:root[zen-private-window] & {