Compare commits

...

47 Commits

Author SHA1 Message Date
Ivaon
8274718d0d ft: add shortcut to toggle Firefox Sidebar, p=#12371 2026-02-13 23:42:48 +01:00
mr. m
5511defb1e feat: Dont restore sidebar if window-sync is disabled, b=no-bug, c=no-component 2026-02-13 21:18:13 +01:00
mr. m
894fbd6a4f fix: Fixed selected space not being highlighted, b=closes #12368, c=configs, common 2026-02-13 20:22:49 +01:00
mr. m
5a26cb6601 feat: Correctly start backup before-write session files, b=no-bug, c=folders 2026-02-13 00:01:35 +01:00
mr. m
2eb09f092c fix: Add a flag to change labels when restoring, b=bug #12152, c=no-component 2026-02-12 20:19:02 +01:00
mr. m
66081e97f6 fix: Remove clip overflow from nav-bar and update rust version, b=no-bug, c=workflows, common 2026-02-12 19:58:58 +01:00
mr. m
08873b0ce7 chore: Bump up rustc version, b=no-bug, c=no-component 2026-02-12 18:41:40 +01:00
mr. m
6dce6d60e9 fix: Fixed window caption buttons not having any padding, b=no-bug, c=common 2026-02-12 17:59:18 +01:00
mr. m
302e321da6 Merge branch 'dev' of https://github.com/zen-browser/desktop into dev 2026-02-12 12:05:49 +01:00
mr. m
5d5d51c6f3 fix: Only apply transparent background to windows of type navigator:browser, b=no-bug, c=common 2026-02-12 11:58:42 +01:00
Andrey Vlasenko
262209f5a3 feat: Add search option for URL-like input, p=#11998
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
Co-authored-by: Mr. M <mr.m@tuta.com>
2026-02-11 23:55:53 +01:00
Mr. M
aa4aae9e26 feat: Dont restore content to unsynced windows, b=no-bug, c=no-component 2026-02-11 23:46:59 +01:00
Mr. M
827b89d4e8 feat: Added --blank-window command line argument, b=no-bug, c=common, workspaces 2026-02-11 22:52:32 +01:00
mr. m
a1e7af81cc feat: Take always into account that no normal window may exist when restoring, b=no-bug, c=no-component 2026-02-11 20:35:56 +01:00
mr. m
1db7e19037 Merge branch 'dev' of https://github.com/zen-browser/desktop into dev 2026-02-11 13:47:50 +01:00
mr. m
c7db15fe9d feat: New ignore rules to moz.configure for patch calculations, b=no-bug, c=scripts 2026-02-11 13:47:34 +01:00
franky
502d3f7d94 fix: Restore current page URL when urlbar is emptied and blurred, p=#12322, c=no-component
When users click the address bar, clear all text, and then click away
to blur, the urlbar stays empty instead of restoring the current page
URL. This adds a handleRevert() call on blur when the urlbar content
is empty (non-newtab mode), so the current page URL is properly
restored. Partial input is still preserved on blur as before.
2026-02-11 12:50:30 +01:00
mr. m
bd0651165c fix: Fixed spaces disappearing when clearing all history, b=closes #12326, c=no-component 2026-02-11 12:34:44 +01:00
mr. m
af4f336bf5 fix: Make sure to save the state instantly on some occations, b=bug #12152, c=scripts 2026-02-11 11:59:05 +01:00
mr. m
bffc16ba29 chore: Run module list through a use-moz-source migration, b=no-bug, c=common, glance, workspaces 2026-02-11 00:35:08 +01:00
mr. m
7c53549076 feat: Allow favicons to have white boxes for more result types, b=no-bug, c=common 2026-02-10 13:53:54 +01:00
mr. m
0fe3a4af10 feat: Only allow overflowing webexts for twilight, b=no-bug, c=common, tabs 2026-02-10 13:44:27 +01:00
mr. m
4d1d4ab1b6 feat: Add pref to force synced windows to open on DnD, b=no-bug, c=no-component 2026-02-10 11:50:23 +01:00
mr. m
2990de8366 fix: Fixed wrong capitalized text for MacOS menu bar, b=closes #12213, c=common 2026-02-09 23:38:13 +01:00
mr. m
76966ee1c3 feat: Make sure to always restore tabs from a crash, b=no-bug, c=no-component 2026-02-09 23:14:51 +01:00
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
75 changed files with 1131 additions and 546 deletions

View File

@@ -92,7 +92,6 @@ jobs:
- name: Fix Rust version
run: |
# Install a rust version compatible with LLVM 18
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain $(cat .rust-toolchain)
source $HOME/.cargo/env

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

@@ -1 +1 @@
1.83
1.89

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

@@ -30,17 +30,11 @@ if test "$ZEN_RELEASE"; then
fi
# Disable DMD and ELF hacks, enable linker lld
ac_add_options --disable-dmd
ac_add_options --enable-linker=lld
ac_add_options --disable-elf-hack
ac_add_options --disable-necko-wifi
# Stripping options for release builds
ac_add_options --enable-install-strip
ac_add_options --enable-strip
export STRIP_FLAGS="--strip-debug --strip-unneeded"
fi
# Common options
ac_add_options --enable-alsa
ac_add_options --enable-pulseaudio

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...
@@ -137,3 +145,7 @@ zen-window-sync-migration-dialog-title = Keep Your Windows in Sync
zen-window-sync-migration-dialog-message = Zen now syncs windows on the same device, so changes in one window are reflected across the others instantly.
zen-window-sync-migration-dialog-learn-more = Learn More
zen-window-sync-migration-dialog-accept = Got It
zen-appmenu-new-blank-window =
.label = New blank window

View File

@@ -22,5 +22,5 @@ zen-menubar-appearance-light =
zen-menubar-appearance-dark =
.label = Dark
zen-menubar-new-unsynced-window =
.label = New blank window
zen-menubar-new-blank-window =
.label = New Blank Window

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: "@IS_TWILIGHT@"

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

@@ -11,6 +11,7 @@ npm run import
IGNORE_FILES=(
"shared.nsh"
"ignorePrefs.json"
"moz.configure"
)
# Recursively find all .patch files in the current directory and its subdirectories

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/browser-sets.js b/browser/base/content/browser-sets.js
index 94a05a510204c027e482fda33deaf3dc0d7471c9..79d37a7c7f29739d3ad2d9f6a3b3b8d638836b10 100644
index 94a05a510204c027e482fda33deaf3dc0d7471c9..da7cda8e2708a29cf6cc3bf667240d37ef66ecfb 100644
--- a/browser/base/content/browser-sets.js
+++ b/browser/base/content/browser-sets.js
@@ -266,7 +266,7 @@ document.addEventListener(
@@ -11,3 +11,12 @@ index 94a05a510204c027e482fda33deaf3dc0d7471c9..79d37a7c7f29739d3ad2d9f6a3b3b8d6
const SIDEBAR_REVAMP_PREF = "sidebar.revamp";
const SIDEBAR_REVAMP_ENABLED = Services.prefs.getBoolPref(
SIDEBAR_REVAMP_PREF,
@@ -304,6 +304,8 @@ document.addEventListener(
panel: SidebarController.currentID,
opened: SidebarController._state.launcherExpanded,
});
+ } else {
+ SidebarController.toggle();
}
break;
case "key_gotoHistory":

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

@@ -0,0 +1,41 @@
diff --git a/browser/components/BrowserContentHandler.sys.mjs b/browser/components/BrowserContentHandler.sys.mjs
index 57a37b5c0d1982ecfff8d94e05fea89895713a58..062dcb0d1de00982bd777c3dc5d62a88a304bc68 100644
--- a/browser/components/BrowserContentHandler.sys.mjs
+++ b/browser/components/BrowserContentHandler.sys.mjs
@@ -601,6 +601,28 @@ nsBrowserContentHandler.prototype = {
}
}
+ try {
+ let blankWindowParam = cmdLine.handleFlagWithParam("blank-window", false);
+ if (blankWindowParam !== null) {
+ let { uri, principal } = resolveURIInternal(cmdLine, blankWindowParam);
+ if (shouldLoadURI(uri)) {
+ let win = openBrowserWindow(cmdLine, principal, uri.spec);
+ win._zenStartupSyncFlag = 'unsynced';
+ cmdLine.preventDefault = true;
+ }
+ }
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_INVALID_ARG) {
+ throw e;
+ }
+ if (cmdLine.handleFlag("blank-window", false)) {
+ let win = openBrowserWindow(cmdLine, null, null);
+ win._zenStartupSyncFlag = 'unsynced';
+ cmdLine.preventDefault = true;
+ }
+ }
+
+
var searchParam = cmdLine.handleFlagWithParam("search", false);
if (searchParam) {
doSearch(searchParam, cmdLine);
@@ -670,6 +692,7 @@ nsBrowserContentHandler.prototype = {
" --new-window <url> Open <url> in a new window.\n" +
" --new-tab <url> Open <url> in a new tab.\n" +
" --private-window [<url>] Open <url> in a new private window.\n";
+ info += " --blank-window [<url>] Open the new blank window.\n";
if (AppConstants.platform == "win") {
info += " --preferences Open Options dialog.\n";
} else {

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/SessionStartup.sys.mjs b/browser/components/sessionstore/SessionStartup.sys.mjs
index 86600ffb5178599ab23270a964064ca657a3283f..e7014e5bce547d37ec92377a95bad5be4d52152e 100644
index 86600ffb5178599ab23270a964064ca657a3283f..70d7b5c7d69d3700475d663b595052264c67d351 100644
--- a/browser/components/sessionstore/SessionStartup.sys.mjs
+++ b/browser/components/sessionstore/SessionStartup.sys.mjs
@@ -40,6 +40,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
@@ -28,7 +28,16 @@ index 86600ffb5178599ab23270a964064ca657a3283f..e7014e5bce547d37ec92377a95bad5be
if (this._initialState == null) {
// No valid session found.
this._sessionType = this.NO_SESSION;
@@ -336,12 +339,7 @@ export var SessionStartup = {
@@ -276,6 +279,8 @@ export var SessionStartup = {
`Previous shutdown ok? ${this._previousSessionCrashed}, reason: ${previousSessionCrashedReason}`
);
+ lazy.ZenSessionStore.onCrashCheckpoints(this._initialState);
+
Services.obs.addObserver(this, "sessionstore-windows-restored", true);
if (this.sessionType == this.NO_SESSION) {
@@ -336,12 +341,7 @@ export var SessionStartup = {
isAutomaticRestoreEnabled() {
if (this._resumeSessionEnabled === null) {
this._resumeSessionEnabled =
@@ -42,3 +51,13 @@ index 86600ffb5178599ab23270a964064ca657a3283f..e7014e5bce547d37ec92377a95bad5be
}
return this._resumeSessionEnabled;
@@ -354,8 +354,7 @@ export var SessionStartup = {
*/
willRestore() {
return (
- this.sessionType == this.RECOVER_SESSION ||
- this.sessionType == this.RESUME_SESSION
+ true
);
},

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..53465df6060e6765e1e4564d340bba5fbefff5ce 100644
--- a/browser/components/sessionstore/SessionStore.sys.mjs
+++ b/browser/components/sessionstore/SessionStore.sys.mjs
@@ -127,6 +127,9 @@ const TAB_EVENTS = [
@@ -63,6 +63,15 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// perform additional initialization when the first window is loading
if (lazy.RunState.isStopped) {
@@ -2088,7 +2097,7 @@ var SessionStoreInternal = {
// to disk to NOW() to enforce a full interval before the next write.
lazy.SessionSaver.updateLastSaveTime();
- if (isPrivateWindow || isTaskbarTab) {
+ if (isPrivateWindow || isTaskbarTab || aWindow.document.documentElement.hasAttribute("zen-unsynced-window")) {
this._log.debug(
"initializeWindow, the window is private or a web app. Saving SessionStartup.state for possibly restoring later"
);
@@ -2131,6 +2140,7 @@ var SessionStoreInternal = {
null,
"sessionstore-one-or-no-tab-restored"
@@ -79,7 +88,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 +112,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 +121,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 +130,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 +141,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 +153,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 +162,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 +170,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 +179,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 +192,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 +201,19 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
userContextId: state.userContextId,
skipLoad: true,
preferredRemoteType,
@@ -5414,7 +5442,7 @@ var SessionStoreInternal = {
@@ -5055,9 +5084,10 @@ var SessionStoreInternal = {
if (activePageData.title && activePageData.title != activePageData.url) {
win.gBrowser.setInitialTabTitle(tab, activePageData.title, {
isContentTitle: true,
+ _zenChangeLabelFlag: true,
});
} else {
- win.gBrowser.setInitialTabTitle(tab, activePageData.url);
+ win.gBrowser.setInitialTabTitle(tab, activePageData.url, { _zenChangeLabelFlag: true });
}
}
@@ -5414,7 +5444,7 @@ var SessionStoreInternal = {
for (let i = tabbrowser.pinnedTabCount; i < tabbrowser.tabs.length; i++) {
let tab = tabbrowser.tabs[i];
@@ -193,7 +222,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
removableTabs.push(tab);
}
}
@@ -5525,7 +5553,7 @@ var SessionStoreInternal = {
@@ -5525,7 +5555,7 @@ var SessionStoreInternal = {
// collect the data for all windows
for (ix in this._windows) {
@@ -202,7 +231,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// window data is still in _statesToRestore
continue;
}
@@ -5668,11 +5696,12 @@ var SessionStoreInternal = {
@@ -5668,11 +5698,12 @@ var SessionStoreInternal = {
}
let tabbrowser = aWindow.gBrowser;
@@ -216,7 +245,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 +5714,9 @@ var SessionStoreInternal = {
tabsData.push(tabData);
}
@@ -226,7 +255,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 +5729,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 +264,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
winData.title = tabbrowser.tabs[0].label;
}
winData.selected = selectedIndex;
@@ -5810,8 +5842,8 @@ var SessionStoreInternal = {
@@ -5810,8 +5844,8 @@ var SessionStoreInternal = {
// selectTab represents.
let selectTab = 0;
if (overwriteTabs) {
@@ -246,7 +275,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
selectTab = Math.min(selectTab, winData.tabs.length);
}
@@ -5833,6 +5865,7 @@ var SessionStoreInternal = {
@@ -5833,6 +5867,7 @@ var SessionStoreInternal = {
if (overwriteTabs) {
for (let i = tabbrowser.browsers.length - 1; i >= 0; i--) {
if (!tabbrowser.tabs[i].selected) {
@@ -254,7 +283,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
tabbrowser.removeTab(tabbrowser.tabs[i]);
}
}
@@ -5866,6 +5899,12 @@ var SessionStoreInternal = {
@@ -5866,6 +5901,12 @@ var SessionStoreInternal = {
savedTabGroup => !openTabGroupIdsInWindow.has(savedTabGroup.id)
);
}
@@ -267,7 +296,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// Move the originally open tabs to the end.
if (initialTabs) {
@@ -6419,6 +6458,25 @@ var SessionStoreInternal = {
@@ -6419,6 +6460,25 @@ var SessionStoreInternal = {
// Most of tabData has been restored, now continue with restoring
// attributes that may trigger external events.
@@ -293,7 +322,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
if (tabData.pinned) {
tabbrowser.pinTab(tab);
@@ -6567,6 +6625,9 @@ var SessionStoreInternal = {
@@ -6567,6 +6627,9 @@ var SessionStoreInternal = {
aWindow.gURLBar.readOnly = false;
}
}
@@ -303,7 +332,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
let promiseParts = Promise.withResolvers();
aWindow.setTimeout(() => {
@@ -7343,7 +7404,7 @@ var SessionStoreInternal = {
@@ -7343,7 +7406,7 @@ var SessionStoreInternal = {
let groupsToSave = new Map();
for (let tIndex = 0; tIndex < window.tabs.length; ) {
@@ -312,7 +341,7 @@ index 2a055f0c5f34f0a2667f659185120c07d38f4e41..2f63071f78782dbc30bde25b3bcaa96f
// Adjust window.selected
if (tIndex + 1 < window.selected) {
window.selected -= 1;
@@ -7358,7 +7419,7 @@ var SessionStoreInternal = {
@@ -7358,7 +7421,7 @@ var SessionStoreInternal = {
);
// We don't want to increment tIndex here.
continue;

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..d21c461c4e05fa5b28bc0806b07f9256ca3c9175 100644
index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..db1c57c8eb4b3d614b5e5aa820871d66b00ec9a4 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -398,6 +398,7 @@
@@ -186,11 +186,13 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..d21c461c4e05fa5b28bc0806b07f9256
// If focus is on the old tab, move it to the new tab.
if (activeEl == oldTab) {
newTab.focus();
@@ -1959,6 +2039,11 @@
@@ -1958,7 +2038,12 @@
return this._setTabLabel(aTab, aLabel);
}
_setTabLabel(aTab, aLabel, { beforeTabOpen, isContentTitle, isURL } = {}) {
+ if (!aTab._zenContentsVisible && !aTab._zenChangeLabelFlag && !aTab._labelIsInitialTitle && !gZenWorkspaces.privateWindowOrDisabled) {
- _setTabLabel(aTab, aLabel, { beforeTabOpen, isContentTitle, isURL } = {}) {
+ _setTabLabel(aTab, aLabel, { beforeTabOpen, isContentTitle, isURL, _zenChangeLabelFlag } = {}) {
+ if (!aTab._zenContentsVisible && !aTab._zenChangeLabelFlag && !aTab._labelIsInitialTitle && !gZenWorkspaces.privateWindowOrDisabled && !_zenChangeLabelFlag) {
+ return false;
+ }
+ aLabel = (typeof aTab.zenStaticLabel === "string" && aTab.zenStaticLabel) ? aTab.zenStaticLabel : aLabel;
@@ -777,7 +779,7 @@ index 0eaca7a58e0026237b71b2ad515efe84d9e8c779..d21c461c4e05fa5b28bc0806b07f9256
options,
aTab
);
+ win._zenStartupSyncFlag = zenForceSync ? 'synced' : 'unsynced';
+ win._zenStartupSyncFlag = (zenForceSync || !Services.prefs.getBoolPref("zen.tabs.dnd-open-blank-window", true)) ? 'synced' : 'unsynced';
+ return win;
}

View File

@@ -1,8 +1,17 @@
diff --git a/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs b/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs
index c12d875172650dddfe7de623a776149517c83302..66e923ed2c84e350dd7ba7a20e362666cec8f307 100644
index c12d875172650dddfe7de623a776149517c83302..de24df54f510c44acda8c64584bf483a2cb5650a 100644
--- a/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs
+++ b/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs
@@ -854,6 +854,7 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
@@ -849,11 +849,16 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
result.providerName == "UrlbarProviderHeuristicFallback" &&
state.context.heuristicResult?.providerName !=
"UrlbarProviderHeuristicFallback"
+ && !(
+ result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
+ state.context.heuristicResult?.type == UrlbarUtils.RESULT_TYPE.URL
+ )
) {
return false;
}
if (result.providerName == "UrlbarProviderTabToSearch") {
@@ -10,7 +19,7 @@ index c12d875172650dddfe7de623a776149517c83302..66e923ed2c84e350dd7ba7a20e362666
// Discard the result if a tab-to-search result was added already.
if (!state.canAddTabToSearch) {
return false;
@@ -1500,7 +1501,9 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
@@ -1500,7 +1505,9 @@ class MuxerUnifiedComplete extends UrlbarMuxer {
usedLimits.maxResultCount++;
}

View File

@@ -0,0 +1,42 @@
diff --git a/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs b/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs
index 7a60a871022df44c0d26745f0035fce988cc5f92..0b89f9b66573d4c5ffb67983d728bb593e5cb524 100644
--- a/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs
+++ b/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs
@@ -75,22 +75,26 @@ export class UrlbarProviderHeuristicFallback extends UrlbarProvider {
// Since we can't tell if this is a real URL and whether the user wants
// to visit or search for it, we provide an alternative searchengine
// match if the string looks like an alphanumeric origin or an e-mail.
- let str = queryContext.searchString;
- if (!URL.canParse(str)) {
- if (
- lazy.UrlbarPrefs.get("keyword.enabled") &&
- (lazy.UrlUtils.looksLikeOrigin(str, {
- noIp: true,
- noPort: true,
- }) ||
- lazy.UrlUtils.REGEXP_COMMON_EMAIL.test(str))
- ) {
+ let trimmedSearchString = queryContext.trimmedSearchString;
+ let [searchCandidate] = UrlbarUtils.stripPrefixAndTrim(
+ trimmedSearchString,
+ {
+ trimSlash: true,
+ }
+ );
+ if (
+ lazy.UrlbarPrefs.get("keyword.enabled") &&
+ (lazy.UrlUtils.looksLikeUrl(searchCandidate) ||
+ lazy.UrlUtils.looksLikeOrigin(searchCandidate, {
+ allowPartialNumericalTLDs: true,
+ }) !== lazy.UrlUtils.LOOKS_LIKE_ORIGIN.NONE ||
+ lazy.UrlUtils.REGEXP_COMMON_EMAIL.test(trimmedSearchString))
+ ) {
let searchResult = await this._engineSearchResult({ queryContext });
if (instance != this.queryInstance) {
return;
}
addCallback(this, searchResult);
- }
}
return;
}

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs
index b97fa81cef276e4bb22414b7c182e00b4bb835dc..1f489eceead0aa331ace59281cb777db5e635366 100644
index a4ed35032c0c60f230d95f451b3f615232784cad..2c92860acb2ddc9829eef006d93431e871241c3a 100644
--- a/browser/components/urlbar/UrlbarUtils.sys.mjs
+++ b/browser/components/urlbar/UrlbarUtils.sys.mjs
@@ -81,6 +81,7 @@ export var UrlbarUtils = {

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs
index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e56a72eb3 100644
index edb0482f0bfb22c70a585b0770e5b0437983779e..109e9eb18b90c650caff1287caef04dce857e122 100644
--- a/browser/components/urlbar/content/UrlbarInput.mjs
+++ b/browser/components/urlbar/content/UrlbarInput.mjs
@@ -68,6 +68,13 @@ const lazy = XPCOMUtils.declareLazy({
@@ -122,14 +122,17 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
// Enable the animation only after the first extend call to ensure it
// doesn't run when opening a new window.
if (!this.hasAttribute("breakout-extend-animate")) {
@@ -2562,6 +2627,24 @@ export class UrlbarInput extends HTMLElement {
@@ -2562,6 +2627,27 @@ export class UrlbarInput extends HTMLElement {
return;
}
+ if (this._zenHandleUrlbarClose) {
+ this._zenHandleUrlbarClose();
+ } else if (!this._untrimmedValue) {
+ // Restore the current page URL when the urlbar is empty on blur
+ this.handleRevert();
+ }
+
+
+ // Arc like URLbar: Blur the input on exit
+ const zenToolbox = this.document.getElementById("navigator-toolbox");
+ this.window.document.documentElement.setAttribute("supress-primary-adjustment", !(
@@ -147,7 +150,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
this.removeAttribute("breakout-extend");
this.#updateTextboxPosition();
}
@@ -2592,7 +2675,7 @@ export class UrlbarInput extends HTMLElement {
@@ -2592,7 +2678,7 @@ export class UrlbarInput extends HTMLElement {
forceUnifiedSearchButtonAvailable = false
) {
let prevState = this.getAttribute("pageproxystate");
@@ -156,7 +159,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
this.setAttribute("pageproxystate", state);
this._inputContainer.setAttribute("pageproxystate", state);
this._identityBox?.setAttribute("pageproxystate", state);
@@ -2846,10 +2929,12 @@ export class UrlbarInput extends HTMLElement {
@@ -2846,10 +2932,12 @@ export class UrlbarInput extends HTMLElement {
return;
}
this.style.top = px(
@@ -169,7 +172,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
);
}
@@ -2908,9 +2993,10 @@ export class UrlbarInput extends HTMLElement {
@@ -2908,9 +2996,10 @@ export class UrlbarInput extends HTMLElement {
return;
}
@@ -181,7 +184,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
);
this.style.setProperty(
"--urlbar-height",
@@ -3344,6 +3430,7 @@ export class UrlbarInput extends HTMLElement {
@@ -3344,6 +3433,7 @@ export class UrlbarInput extends HTMLElement {
}
_toggleActionOverride(event) {
@@ -189,7 +192,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
if (
event.keyCode == KeyEvent.DOM_VK_SHIFT ||
event.keyCode == KeyEvent.DOM_VK_ALT ||
@@ -3447,8 +3534,8 @@ export class UrlbarInput extends HTMLElement {
@@ -3447,8 +3537,8 @@ export class UrlbarInput extends HTMLElement {
if (!this.#isAddressbar) {
return val;
}
@@ -200,7 +203,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
: val;
// Only trim value if the directionality doesn't change to RTL and we're not
// showing a strikeout https protocol.
@@ -3754,6 +3841,7 @@ export class UrlbarInput extends HTMLElement {
@@ -3754,6 +3844,7 @@ export class UrlbarInput extends HTMLElement {
resultDetails = null,
browser = this.window.gBrowser.selectedBrowser
) {
@@ -208,7 +211,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
if (this.#isAddressbar) {
this.#prepareAddressbarLoad(
url,
@@ -3861,6 +3949,10 @@ export class UrlbarInput extends HTMLElement {
@@ -3861,6 +3952,10 @@ export class UrlbarInput extends HTMLElement {
}
reuseEmpty = true;
}
@@ -219,7 +222,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
if (
where == "tab" &&
reuseEmpty &&
@@ -3868,6 +3960,9 @@ export class UrlbarInput extends HTMLElement {
@@ -3868,6 +3963,9 @@ export class UrlbarInput extends HTMLElement {
) {
where = "current";
}
@@ -229,7 +232,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
return where;
}
@@ -4122,6 +4217,7 @@ export class UrlbarInput extends HTMLElement {
@@ -4122,6 +4220,7 @@ export class UrlbarInput extends HTMLElement {
this.setResultForCurrentValue(null);
this.handleCommand();
this.controller.clearLastQueryContextCache();
@@ -237,7 +240,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
this._suppressStartQuery = false;
});
@@ -4129,7 +4225,6 @@ export class UrlbarInput extends HTMLElement {
@@ -4129,7 +4228,6 @@ export class UrlbarInput extends HTMLElement {
contextMenu.addEventListener("popupshowing", () => {
// Close the results pane when the input field contextual menu is open,
// because paste and go doesn't want a result selection.
@@ -245,7 +248,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
let controller =
this.document.commandDispatcher.getControllerForCommand("cmd_paste");
@@ -4239,7 +4334,11 @@ export class UrlbarInput extends HTMLElement {
@@ -4239,7 +4337,11 @@ export class UrlbarInput extends HTMLElement {
if (!engineName && !source && !this.hasAttribute("searchmode")) {
return;
}
@@ -258,7 +261,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
if (this._searchModeIndicatorTitle) {
this._searchModeIndicatorTitle.textContent = "";
this._searchModeIndicatorTitle.removeAttribute("data-l10n-id");
@@ -4549,6 +4648,7 @@ export class UrlbarInput extends HTMLElement {
@@ -4549,6 +4651,7 @@ export class UrlbarInput extends HTMLElement {
this.document.l10n.setAttributes(
this.inputField,
@@ -266,7 +269,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
l10nId,
l10nId == "urlbar-placeholder-with-name"
? { name: engineName }
@@ -4662,6 +4762,11 @@ export class UrlbarInput extends HTMLElement {
@@ -4662,6 +4765,11 @@ export class UrlbarInput extends HTMLElement {
}
_on_click(event) {
@@ -278,7 +281,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
switch (event.target) {
case this.inputField:
case this._inputContainer:
@@ -4740,7 +4845,7 @@ export class UrlbarInput extends HTMLElement {
@@ -4740,7 +4848,7 @@ export class UrlbarInput extends HTMLElement {
}
}
@@ -287,7 +290,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
this.view.autoOpen({ event });
} else {
if (this._untrimOnFocusAfterKeydown) {
@@ -4780,9 +4885,16 @@ export class UrlbarInput extends HTMLElement {
@@ -4780,9 +4888,16 @@ export class UrlbarInput extends HTMLElement {
}
_on_mousedown(event) {
@@ -305,7 +308,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
if (
event.composedTarget != this.inputField &&
event.composedTarget != this._inputContainer
@@ -4792,6 +4904,10 @@ export class UrlbarInput extends HTMLElement {
@@ -4792,6 +4907,10 @@ export class UrlbarInput extends HTMLElement {
this.focusedViaMousedown = !this.focused;
this._preventClickSelectsAll = this.focused;
@@ -316,7 +319,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
// Keep the focus status, since the attribute may be changed
// upon calling this.focus().
@@ -4827,7 +4943,7 @@ export class UrlbarInput extends HTMLElement {
@@ -4827,7 +4946,7 @@ export class UrlbarInput extends HTMLElement {
}
// Don't close the view when clicking on a tab; we may want to keep the
// view open on tab switch, and the TabSelect event arrived earlier.
@@ -325,7 +328,7 @@ index edb0482f0bfb22c70a585b0770e5b0437983779e..545f65db4fe8b807b388d5552842cd9e
break;
}
@@ -5155,7 +5271,7 @@ export class UrlbarInput extends HTMLElement {
@@ -5155,7 +5274,7 @@ export class UrlbarInput extends HTMLElement {
// When we are in actions search mode we can show more results so
// increase the limit.
let maxResults =

View File

@@ -1,5 +1,5 @@
diff --git a/browser/extensions/newtab/lib/ActivityStream.sys.mjs b/browser/extensions/newtab/lib/ActivityStream.sys.mjs
index ae3c01650b3023a692cd4c50e33e3c3c39770c47..27a5fb0a630b9b79566be08cd5f7c9466ca30381 100644
index 0f73c33c3237413131a2f4d2d1f3be857e533437..d7e9fcadc7744651fbe14952589df4603e7db592 100644
--- a/browser/extensions/newtab/lib/ActivityStream.sys.mjs
+++ b/browser/extensions/newtab/lib/ActivityStream.sys.mjs
@@ -295,7 +295,7 @@ export const PREFS_CONFIG = new Map([

View File

@@ -485,7 +485,7 @@
}
#zen-site-data-icon-button {
padding: 0 6px !important;
padding: 0 4px !important;
border-radius: var(--urlbar-icon-border-radius) !important;
& image {

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,8 +1,8 @@
diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp
index c0ca05afc1e8eb5cb7115a22c39dcf45285fdfb7..84b3fec2c7b2bc671e7004d8571dc428b8ee011d 100644
index 2c4aca4a5984fc3201a2c643f8d26b8c4f4ef3bd..4c21cee90e5442d45cdb2bcf3f91200f0441218f 100644
--- a/layout/generic/nsIFrame.cpp
+++ b/layout/generic/nsIFrame.cpp
@@ -11958,6 +11958,11 @@ gfx::Matrix nsIFrame::ComputeWidgetTransform() const {
@@ -11956,6 +11956,11 @@ gfx::Matrix nsIFrame::ComputeWidgetTransform() const {
gfx::Matrix4x4 matrix = nsStyleTransformMatrix::ReadTransforms(
uiReset->mMozWindowTransform, refBox, float(appUnitsPerDevPixel));

View File

@@ -1,8 +1,8 @@
diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp
index 3c84ea049b01c20c6a7e9ff717e19d1d9e86248b..ed6b15dffa571348d1c13bf31f3b2e1638151e8f 100644
index 25357c6a506b969c5049190c29e4e411ee5f4c31..bef909ddfc28d084cf87cae3fbd050bbde21468d 100644
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3217,6 +3217,9 @@ nsStyleUIReset::nsStyleUIReset()
@@ -3270,6 +3270,9 @@ nsStyleUIReset::nsStyleUIReset()
mWindowShadow(StyleWindowShadow::Auto),
mWindowOpacity(1.0),
mMozWindowInputRegionMargin(StyleLength::Zero()),
@@ -12,7 +12,7 @@ index 3c84ea049b01c20c6a7e9ff717e19d1d9e86248b..ed6b15dffa571348d1c13bf31f3b2e16
mTransitions(
nsStyleAutoArray<StyleTransition>::WITH_SINGLE_INITIAL_ELEMENT),
mTransitionTimingFunctionCount(1),
@@ -3261,6 +3264,7 @@ nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource)
@@ -3314,6 +3317,7 @@ nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource)
mWindowOpacity(aSource.mWindowOpacity),
mMozWindowInputRegionMargin(aSource.mMozWindowInputRegionMargin),
mMozWindowTransform(aSource.mMozWindowTransform),

View File

@@ -1,8 +1,8 @@
diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h
index 20d0dc30020ee7fcd27f689cb6c0754224b003c5..dcca0efd2c9822937c994dffeb63d2263fbb6331 100644
index ca11ab168761a6da23811d802a87f6c31af6cca9..cb74679626620a12d37fb1671c373ea9793ae004 100644
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2080,6 +2080,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUIReset {
@@ -2094,6 +2094,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUIReset {
// The margin of the window region that should be transparent to events.
mozilla::StyleLength mMozWindowInputRegionMargin;
mozilla::StyleTransform mMozWindowTransform;

View File

@@ -1,8 +1,8 @@
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index cc7fd419035a972bfab1b0e7631b8856e846ce81..b4e6d699f99e7164e62ce20792919420a8e73810 100644
index 0986e53121fdb821bca4a7c30ff707bfdf2dbad7..2a1865098a00282aa1839df245cb472cdd64fdec 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -19738,6 +19738,7 @@
@@ -19748,6 +19748,7 @@
mirror: always
#endif

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

@@ -1,8 +1,8 @@
diff --git a/toolkit/modules/AppConstants.sys.mjs b/toolkit/modules/AppConstants.sys.mjs
index 5465ed3fb01e4214029b44f338d5e1e1580c3aab..a4d6b42916014d5e0f643b35a7b4feb281f59023 100644
index e2a22ddd6c6ba36491f37ed22bc2e1fbc149790c..920d8649ef48d685891223a834110ad9a059001a 100644
--- a/toolkit/modules/AppConstants.sys.mjs
+++ b/toolkit/modules/AppConstants.sys.mjs
@@ -172,6 +172,8 @@ export var AppConstants = Object.freeze({
@@ -170,6 +170,8 @@ export var AppConstants = Object.freeze({
MOZ_UPDATE_CHANNEL: "@MOZ_UPDATE_CHANNEL@",
MOZ_WIDGET_TOOLKIT: "@MOZ_WIDGET_TOOLKIT@",

View File

@@ -1,5 +1,5 @@
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
index abc630955c18de629d3ffff3e8f9aa29f57950be..97d6d5f25d0859684c405c659e0eef26007027c4 100644
index 114a8c3e2e36e57cb2334a893a11f1619831a6e7..9c9ae37b6462d00c93d8f3319cea2960b3cd9816 100644
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -281,6 +281,7 @@ for var in (

View File

@@ -0,0 +1,40 @@
diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
index 5eae0bca990fea9e995bd2edf5e879361f89391c..3f8bfedb83ddae07b1e82715fc7f6b2109a39e33 100644
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -1322,9 +1322,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;
@@ -1334,8 +1336,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));
}
@@ -1503,13 +1503,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

@@ -112,7 +112,7 @@ export class nsZenMenuBar {
const openUnsyncedWindowItem = window.MozXULElement.parseXULToFragment(
`<toolbarbutton id="appMenu-new-zen-unsynced-window-button"
class="subviewbutton"
data-l10n-id="zen-menubar-new-unsynced-window"
data-l10n-id="zen-appmenu-new-blank-window"
key="zen-new-unsynced-window"
command="cmd_zenNewNavigatorUnsynced"/>`
).querySelector("toolbarbutton");
@@ -123,7 +123,7 @@ export class nsZenMenuBar {
window.MozXULElement.parseXULToFragment(`
<menuitem id="menu_new_zen_unsynced_window"
class="subviewbutton"
data-l10n-id="zen-menubar-new-unsynced-window"
data-l10n-id="zen-menubar-new-blank-window"
key="zen-new-unsynced-window"
command="cmd_zenNewNavigatorUnsynced"/>`)
);

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

@@ -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;
}
}
@@ -209,7 +209,7 @@
margin: 0;
:root[zen-single-toolbar="true"] & {
padding: 6px !important;
padding: 5px !important;
width: unset !important;
height: unset !important;
}
@@ -396,7 +396,7 @@
#urlbar #tracking-protection-icon-container,
#urlbar:not([breakout-extend="true"]) #identity-box:is(:not(.chromeUI), [pageproxystate="invalid"]) #identity-icon-box {
border-radius: var(--urlbar-icon-border-radius) !important;
min-width: 28px;
min-width: 26px;
}
/* Notification Stack */
@@ -649,7 +649,7 @@
}
&[selected] {
--zen-selected-bg: color-mix(in srgb, var(--zen-primary-color) 50%, light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.2)) 50%);
--zen-selected-bg: color-mix(in srgb, var(--zen-primary-color) 55%, light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.2)) 50%);
--zen-selected-color: color-mix(in srgb, var(--zen-selected-bg), black 30%);
background-color: var(--zen-selected-bg) !important;
@@ -667,7 +667,7 @@
color: var(--zen-selected-color) !important;
}
&:is([type="switchtab"], [type="dynamic"]) .urlbarView-favicon,
&:is([type="switchtab"], [type="dynamic"], [type="history_serp"], [type="autofill_origin"], [type="top_site"]) .urlbarView-favicon,
& .urlbarView-shortcutContent {
fill: var(--zen-selected-color) !important;
background-color: rgba(255, 255, 255, 0.9) !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

@@ -172,11 +172,14 @@
}
}
.toolbarbutton-1:not(#tabs-newtab-button) {
.toolbarbutton-1:not(#tabs-newtab-button),
.urlbar-page-action,
.identity-box-button {
--tab-border-radius: 6px;
--toolbarbutton-border-radius: var(--tab-border-radius);
--toolbarbutton-inner-padding: 6px;
--toolbarbutton-outer-padding: 2px;
--toolbarbutton-outer-padding: 1px;
--toolbarbutton-icon-fill: color-mix(in srgb, currentColor 70%, transparent);
transition:
background-color 0.1s,
@@ -381,6 +384,7 @@
&:hover {
background-color: color-mix(in srgb, currentColor 10%, transparent);
}
.zen-site-data-section:hover & {
opacity: 0.5;
}
@@ -536,7 +540,7 @@
0px 2px 4px rgba(0, 0, 0, 0.1),
inset 0px 1px 0px light-dark(transparent, rgba(255, 255, 255, 0.15));
border-radius: 6px;
--base-border-color: light-dark(rgba(0, 0, 0, 0.175), rgba(255, 255, 255, 0.1));
--base-border-color: light-dark(rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0.1));
border: 1px solid light-dark(var(--base-border-color), rgb(21, 21, 21));
}

View File

@@ -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.08), rgba(255, 255, 255, 0.1));
/* 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;
@@ -294,7 +288,7 @@
}
}
#main-window:not([chromehidden~='toolbar']) {
#main-window[windowtype="navigator:browser"]:not([chromehidden~='toolbar']) {
min-height: 495px !important;
@media (-moz-windows-mica) or (-moz-platform: macos) or ((-moz-platform: linux) and

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

@@ -98,6 +98,7 @@ zen-folder {
& svg {
filter: contrast(125%);
transform: translate(-1px, -1px);
:root:not([zen-sidebar-expanded]) & {
transition: transform 0.1s ease;

View File

@@ -34,7 +34,7 @@
box-shadow: 0 0 12px 1px rgba(0, 0, 0, 0.07);
padding: 8px;
opacity: 1 !important;
color: var(--zen-primary-color);
color: var(--zen-primary-color) !important;
&:hover {
background: light-dark(rgb(41, 41, 41), rgb(204, 204, 204));

View File

@@ -10,6 +10,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
gWindowSyncEnabled: "resource:///modules/zen/ZenWindowSync.sys.mjs",
gSyncOnlyPinnedTabs: "resource:///modules/zen/ZenWindowSync.sys.mjs",
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
@@ -29,6 +30,10 @@ XPCOMUtils.defineLazyPreferenceGetter(
3
);
const SHOULD_LOG_TAB_ENTRIES = Services.prefs.getBoolPref(
"zen.session-store.log-tab-entries",
false
);
const SHOULD_BACKUP_FILE = Services.prefs.getBoolPref("zen.session-store.backup-file", true);
const FILE_NAME = "zen-sessions.jsonlz4";
@@ -52,6 +57,9 @@ class nsZenSidebarObject {
}
set data(data) {
if (typeof data !== "object") {
throw new Error("Sidebar data must be an object");
}
this.#sidebar = data;
}
}
@@ -76,14 +84,14 @@ export class nsZenSessionManager {
init() {
this.log("Initializing session manager");
let backupFile = null;
let backupTo = null;
if (SHOULD_BACKUP_FILE) {
backupFile = PathUtils.join(this.#backupFolderPath, FILE_NAME);
backupTo = PathUtils.join(this.#backupFolderPath, "recovery.baklz4");
}
this.#file = new JSONFile({
path: this.#storeFilePath,
compression: "lz4",
backupFile,
backupTo,
});
this.log("Session file path:", this.#file.path);
this.#deferredBackupTask = new lazy.DeferredTask(async () => {
@@ -94,18 +102,16 @@ export class nsZenSessionManager {
log(...args) {
if (lazy.gShouldLog) {
// eslint-disable-next-line no-console
console.log("ZenSessionManager:", ...args);
console.debug("ZenSessionManager:", ...args);
}
}
get #storeFilePath() {
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
return PathUtils.join(profileDir, FILE_NAME);
return PathUtils.join(PathUtils.profileDir, FILE_NAME);
}
get #backupFolderPath() {
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
return PathUtils.join(profileDir, "zen-sessions-backup");
return PathUtils.join(PathUtils.profileDir, "zen-sessions-backup");
}
/**
@@ -219,6 +225,11 @@ export class nsZenSessionManager {
this._shouldRunMigration = true;
await this.#getDataFromDBForMigration();
}
if (SHOULD_LOG_TAB_ENTRIES) {
for (const tab of this.#sidebar.tabs || []) {
this.log("Tab entry in session file:", tab);
}
}
}
get #shouldRestoreOnlyPinned() {
@@ -228,6 +239,13 @@ export class nsZenSessionManager {
);
}
get #shouldRestoreFromCrash() {
return (
lazy.SessionStartup.previousSessionCrashed &&
Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash")
);
}
/**
* Called when the session file is read. Restores the sidebar data
* into all windows.
@@ -253,23 +271,53 @@ export class nsZenSessionManager {
}
return initialState;
}
const allowRestoreUnsynced = Services.prefs.getBoolPref(
"zen.session-store.restore-unsynced-windows",
true
);
if (initialState?.windows?.length && !allowRestoreUnsynced) {
initialState.windows = initialState.windows.filter((win) => {
if (win.isZenUnsynced) {
this.log("Skipping unsynced window during restore");
}
return !win.isZenUnsynced;
});
}
// If there are no windows, we create an empty one. By default,
// firefox would create simply a new empty window, but we want
// to make sure that the sidebar object is properly initialized.
// This would happen on first run after having a single private window
// open when quitting the app, for example.
if (!initialState?.windows?.length) {
let normalWindowsExist = initialState?.windows?.some(
(win) => !win.isPrivate && !win.isPopup && !win.isTaskbarTab && !win.isZenUnsynced
);
if (!initialState?.windows?.length || !normalWindowsExist) {
this.log("No windows found in initial state, creating an empty one");
initialState ||= {};
initialState.windows = [
{
tabs: [],
},
];
initialState.windows ||= [];
initialState.windows.push({
tabs: [],
});
}
return initialState;
}
/**
* Called after @onFileRead, when session startup has crash checkpoint information available.
* Restores the sidebar data into all windows, and runs any crash checkpoint related logic,
* such as restoring only pinned tabs if the previous session was not crashed and the user
* preference is set to do so.
*
* @param {object} initialState
* The initial session state read from the session file, possibly modified by onFileRead.
*/
onCrashCheckpoints(initialState) {
if (!lazy.gWindowSyncEnabled) {
return;
}
// When we don't have browser.startup.page set to resume session,
// we only want to restore the pinned tabs into the new windows.
if (this.#shouldRestoreOnlyPinned && this.#sidebar?.tabs) {
if (this.#shouldRestoreOnlyPinned && !this.#shouldRestoreFromCrash && this.#sidebar?.tabs) {
this.log("Restoring only pinned tabs into windows");
const sidebar = this.#sidebar;
sidebar.tabs = (sidebar.tabs || []).filter((tab) => tab.pinned);
@@ -278,30 +326,20 @@ export class nsZenSessionManager {
// Restore all windows with the same sidebar object, this will
// guarantee that all tabs, groups, folders and split view data
// are properly synced across all windows.
const allowRestoreUnsynced = Services.prefs.getBoolPref(
"zen.session-store.restore-unsynced-windows",
true
);
if (!this._shouldRunMigration) {
this.log(`Restoring Zen session data into ${initialState.windows?.length || 0} windows`);
for (let i = 0; i < initialState.windows.length; i++) {
let winData = initialState.windows[i];
if (winData.isZenUnsynced) {
if (!allowRestoreUnsynced) {
// We don't wan't to restore any unsynced windows with the sidebar data.
this.log("Skipping restore of unsynced window");
delete initialState.windows[i];
}
if (winData.isZenUnsynced || winData.isPrivate || winData.isPopup || winData.isTaskbarTab) {
continue;
}
this.#restoreWindowData(winData);
}
} else if (initialState) {
this.log("Saving windata state after migration");
this.saveState(Cu.cloneInto(initialState, {}));
this.saveState(Cu.cloneInto(initialState, {}), true);
}
delete this._shouldRunMigration;
return initialState;
}
get #sidebar() {
@@ -384,6 +422,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.
*
@@ -398,8 +455,11 @@ export class nsZenSessionManager {
* Saves the current session state. Collects data and writes to disk.
*
* @param {object} state The current session state.
* @param {boolean} soon Whether to save the file soon or immediately.
* If true, the file will be saved asynchronously or when quitting
* the app. If false, the file will be saved immediately.
*/
saveState(state) {
saveState(state, soon = false) {
let windows = state?.windows || [];
windows = windows.filter((win) => this.#isWindowSaveable(win));
if (!windows.length) {
@@ -408,12 +468,16 @@ export class nsZenSessionManager {
return;
}
this.#collectWindowData(windows);
// This would save the data to disk asynchronously or when
// quitting the app.
this.#file.data = this.#sidebar;
this.#file.saveSoon();
// This would save the data to disk asynchronously or when quitting the app.
let sidebar = this.#sidebar;
this.#file.data = sidebar;
if (soon) {
this.#file.saveSoon();
} else {
this.#file._save();
}
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`);
}
/**
@@ -465,12 +529,12 @@ export class nsZenSessionManager {
// Now we need to check if we have exceeded the maximum
// number of backups allowed, and delete the oldest ones
// if needed.
let prefix = PathUtils.join(backupFolder, "zen-sessions-");
let files = await IOUtils.getChildren(backupFolder);
files = files.filter((file) => file.startsWith("zen-sessions-")).sort();
files = files.filter((file) => file.startsWith(prefix)).sort();
for (let i = 0; i < files.length - lazy.gMaxSessionBackups; i++) {
const fileToDelete = PathUtils.join(backupFolder, files[i].name);
this.log(`Deleting old backup file ${files[i].name}`);
await IOUtils.remove(fileToDelete);
this.log(`Deleting old backup file ${files[i]}`);
await IOUtils.remove(files[i]);
}
} catch (e) {
console.error("ZenSessionManager: Failed to create session file backups", e);
@@ -492,7 +556,7 @@ export class nsZenSessionManager {
return;
}
this.log("Saving closed window session data into Zen session store");
this.saveState({ windows: [aWinData] });
this.saveState({ windows: [aWinData] }, true);
}
/**
@@ -514,6 +578,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 +639,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 +687,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

@@ -28,7 +28,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
XPCOMUtils.defineLazyPreferenceGetter(lazy, "gShouldLog", "zen.window-sync.log", true);
const OBSERVING = ["browser-window-before-show", "sessionstore-windows-restored"];
const INSTANT_EVENTS = ["SSWindowClosing"];
const INSTANT_EVENTS = ["SSWindowClosing", "TabSelect", "focus"];
const UNSYNCED_WINDOW_EVENTS = ["TabOpen"];
const EVENTS = [
"TabClose",
@@ -50,9 +50,6 @@ const EVENTS = [
"ZenTabRemovedFromSplit",
"ZenSplitViewTabsSplit",
"TabSelect",
"focus",
...INSTANT_EVENTS,
...UNSYNCED_WINDOW_EVENTS,
];
@@ -81,6 +78,13 @@ class nsZenWindowSync {
lastHandlerPromise: Promise.resolve(),
};
/**
* Promise that resolves when the current docshell swap operation is finished.
* Used to avoid multiple simultaneous swap operations that could interfere with each other.
* For example, when focusing a window AND selecting a tab at the same time.
*/
#docShellSwitchPromise = Promise.resolve();
/**
* Map of sync handlers for different event types.
* Each handler is a function that takes the event as an argument.
@@ -169,7 +173,7 @@ class nsZenWindowSync {
log(...args) {
if (lazy.gShouldLog) {
// eslint-disable-next-line no-console
console.info("ZenWindowSync:", ...args);
console.debug("ZenWindowSync:", ...args);
}
}
@@ -327,7 +331,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 +378,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 +795,43 @@ 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"));
};
});
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);
let promise = this.#createPseudoImageForBrowser(otherBrowser, mySrc);
await Promise.all([promiseToWait, promise]);
aOurTab.ownerGlobal.requestAnimationFrame(() => {
otherBrowser.setAttribute("zen-pseudo-hidden", "true");
await promiseToWait;
callback();
}
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
ourBrowser.removeAttribute("zen-pseudo-hidden");
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
});
callback();
} else {
ourBrowser.removeAttribute("zen-pseudo-hidden");
resolve();
});
this.#maybeRemovePseudoImageForBrowser(ourBrowser);
}
resolve();
});
}
@@ -834,10 +843,25 @@ class nsZenWindowSync {
*/
#createPseudoImageForBrowser(aBrowser, aSrc) {
const doc = aBrowser.ownerDocument;
const win = aBrowser.ownerGlobal;
const img = doc.createElement("img");
img.className = "zen-pseudo-browser-image";
img.src = aSrc;
let promise = new Promise((resolve) => {
if (img.complete) {
resolve();
return;
}
let finish = () => {
win.requestAnimationFrame(() => {
resolve();
});
};
img.onload = finish;
img.onerror = finish;
});
aBrowser.after(img);
return promise;
}
/**
@@ -911,20 +935,16 @@ class nsZenWindowSync {
*
* @param {Window} aWindow - The window that triggered the event.
* @param {object} aPreviousTab - The previously selected tab.
* @param {boolean} ignoreSameTab - Indicates if the same tab should be ignored.
*/
async #onTabSwitchOrWindowFocus(aWindow, aPreviousTab = null, ignoreSameTab = false) {
// On some occasions, such as when closing a window, this
// function might be called multiple times for the same tab.
if (aWindow.gBrowser.selectedTab === this.#lastSelectedTab && !ignoreSameTab) {
return;
}
async #onTabSwitchOrWindowFocus(aWindow, aPreviousTab = null) {
let activeBrowsers = aWindow.gBrowser.selectedBrowsers;
let activeTabs = activeBrowsers.map((browser) => aWindow.gBrowser.getTabForBrowser(browser));
// 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
@@ -1209,11 +1229,12 @@ class nsZenWindowSync {
});
}
on_focus(aEvent) {
async on_focus(aEvent) {
if (typeof aEvent.target !== "object") {
return;
}
const { ownerGlobal: window } = aEvent.target;
await this.#docShellSwitchPromise;
const window = Services.focus.activeWindow;
if (
!window?.gBrowser ||
this.#lastFocusedWindow?.deref() === window ||
@@ -1224,17 +1245,21 @@ class nsZenWindowSync {
}
this.#lastFocusedWindow = new WeakRef(window);
this.#lastSelectedTab = new WeakRef(window.gBrowser.selectedTab);
return this.#onTabSwitchOrWindowFocus(window);
return (this.#docShellSwitchPromise = this.#onTabSwitchOrWindowFocus(window));
}
on_TabSelect(aEvent) {
async on_TabSelect(aEvent) {
await this.#docShellSwitchPromise;
const tab = aEvent.target;
if (this.#lastSelectedTab?.deref() === tab) {
return;
}
this.#lastSelectedTab = new WeakRef(tab);
const previousTab = aEvent.detail.previousTab;
return this.#onTabSwitchOrWindowFocus(aEvent.target.ownerGlobal, previousTab);
return (this.#docShellSwitchPromise = this.#onTabSwitchOrWindowFocus(
aEvent.target.ownerGlobal,
previousTab
));
}
on_SSWindowClosing(aEvent) {
@@ -1254,21 +1279,26 @@ class nsZenWindowSync {
for (let browser of aBrowsers) {
const tab = this.#swapedTabsEntriesForWC.get(browser.permanentKey);
if (tab) {
let win = tab.ownerGlobal;
this.log(`Finalizing swap for tab ${tab.id} on window close`);
lazy.TabStateCache.update(
tab.linkedBrowser.permanentKey,
lazy.TabStateCache.get(browser.permanentKey)
);
let tabData = this.#getTabEntriesFromCache(tab);
let activePageData = tabData.entries[tabData.index - 1] || null;
try {
let win = tab.ownerGlobal;
this.log(`Finalizing swap for tab ${tab.id} on window close`);
lazy.TabStateCache.update(
tab.linkedBrowser.permanentKey,
lazy.TabStateCache.get(browser.permanentKey)
);
let tabData = this.#getTabEntriesFromCache(tab);
let activePageData = tabData.entries[tabData.index - 1] || null;
// If the page has a title, set it. When doing a swap and we still didn't
// flush the tab state, the title might not be correct.
if (activePageData) {
win.gBrowser.setInitialTabTitle(tab, activePageData.title, {
isContentTitle: activePageData.title && activePageData.title != activePageData.url,
});
// If the page has a title, set it. When doing a swap and we still didn't
// flush the tab state, the title might not be correct.
if (activePageData && win?.gBrowser) {
win.gBrowser.setInitialTabTitle(tab, activePageData.title, {
isContentTitle: activePageData.title && activePageData.title != activePageData.url,
});
}
} catch (e) {
// We might have already closed the window at this point, so just ignore any error.
console.error(e);
}
}
}
@@ -1370,7 +1400,7 @@ class nsZenWindowSync {
return new Promise((resolve) => {
lazy.setTimeout(() => {
this.#onTabSwitchOrWindowFocus(window, null, /* ignoreSameTab = */ true).finally(resolve);
this.#onTabSwitchOrWindowFocus(window, null).finally(resolve);
}, 0);
});
}

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;
}
}
@@ -297,7 +297,7 @@
--tab-block-margin: 2px;
--tab-selected-bgcolor: light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.18));
--tab-selected-shadow: 0 1px 1px 1px light-dark(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.05)) !important;
--tab-selected-shadow: 0 1px 1px 0px light-dark(rgba(0, 0, 0, 0.09), rgba(0, 0, 0, 0.05)) !important;
grid-gap: 0 !important;
&[overflow]::after,

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

@@ -144,6 +144,10 @@ export class nsZenWorkspace extends MozXULElement {
this.pinnedTabsContainer.scrollbox = this.scrollbox;
this.#initialPinnedElementChildrenCount = this.pinnedTabsContainer.children.length;
if (document.documentElement.hasAttribute("zen-unsynced-window")) {
this.indicator.removeAttribute("context");
}
this.indicator
.querySelector(".zen-workspaces-actions")
.addEventListener("click", this.onActionsCommand.bind(this));

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);
@@ -173,14 +173,13 @@
&::before {
border-radius: var(--border-radius-medium);
background: transparent;
transition: background 0.1s;
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] & {

View File

@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.18.5b",
"displayVersion": "1.18.7b",
"github": {
"repo": "zen-browser/desktop"
},
@@ -54,4 +54,4 @@
"licenseType": "MPL-2.0"
},
"updateHostname": "updates.zen-browser.app"
}
}