Compare commits

...

31 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
cbd9381997 Merge remote-tracking branch 'origin/dev' into little-zen
# Conflicts:
#	src/zen/kbs/ZenKeyboardShortcuts.sys.mjs

Co-authored-by: mr-cheffy <91018726+mr-cheffy@users.noreply.github.com>
2026-05-07 18:40:04 +00:00
mr. m
9c88e3a84f no-bug: Sync upstream Firefox to version 150.0.2 (gh-13601) 2026-05-07 18:28:25 +02:00
mr. m
b999a932ac no-bug: Improve swipe feel and avoid big jumps while swiping (gh-13603) 2026-05-07 17:08:50 +02:00
dependabot[bot]
e9a0beae20 no-bug: bump axios from 1.15.0 to 1.16.0 (gh-13590)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-06 23:12:02 +02:00
Daniel Richard G.
b93c2054c4 no-bug: A few quality-of-life tweaks (build tooling) (gh-13565) 2026-05-06 21:38:16 +02:00
mr. m
dcf272f620 gh-13516: Show the loading indicator ontop of the webview for toolbar mode (gh-13588) 2026-05-06 16:23:05 +02:00
mr. m
3ffdf6b299 gh-13583: Fixed Previous Space in menu bar not working (gh-13587) 2026-05-06 16:10:02 +02:00
mr. m
b052c1d804 gh-13584: Add click tab to split config (gh-13586) 2026-05-06 16:02:41 +02:00
mr. m
818c448dfc gh-10649: Update upstream patchs for session store (gh-13581) 2026-05-06 12:26:53 +02:00
mr. m
5b16bb36d9 no-bug: Dont recalculate urlbar overflow if its opened (gh-13571) 2026-05-05 11:59:50 +02:00
mr. m
5b790a431a gh-12699: Fixed switching windoww focus causing blinks (gh-13568) 2026-05-05 11:37:46 +02:00
Zander Otavka
149419d1fd no-bug: Add shortcut to create a new Space (gh-13552)
Co-authored-by: Zander Otavka <AlexanderOtavka@users.noreply.github.com>
2026-05-05 00:14:24 +02:00
mr. m
60a77413df no-bug: Use a different blur for the urlbar, depending on the theme (gh-13562) 2026-05-05 00:13:14 +02:00
mr. m
ce1ee9228b Merge commit from fork 2026-05-04 20:20:47 +02:00
sporocyst
1bea3d401c gh-13545: Fix regression from gh-13483 (gh-13554)
Co-authored-by: mr. m <mr.m@tuta.com>
2026-05-04 12:15:19 +02:00
Zander Otavka
b6624612cd no-bug: Submit workspace creation form on Enter and cancel on Escape (gh-13549)
Co-authored-by: Zander Otavka <AlexanderOtavka@users.noreply.github.com>
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-05-04 10:21:43 +02:00
mr. m
aa463e2cb0 no-bug: Fix patch sets (gh-13557) 2026-05-04 10:07:22 +02:00
Zander Otavka
6552f0b3c3 no-bug: Add focus indicators to space creation form (gh-13550)
Co-authored-by: Zander Otavka <AlexanderOtavka@users.noreply.github.com>
2026-05-04 02:35:10 +02:00
mr. m
9950679f2c no-bug: Boosts optimization and enable acrylic elements for twilight (gh-13551) 2026-05-04 02:34:41 +02:00
mr. m
a9a582afae gh-13522: Fixed search engines not being visible in settings (gh-13547) 2026-05-03 23:35:14 +02:00
mr. m
607551f394 Merge commit from fork 2026-05-03 22:39:44 +02:00
mr. m
3278a43751 gh-13294: Fix macos crash with native popovers (gh-13546) 2026-05-03 22:09:17 +02:00
mr. m
c82d314913 no-bug: Make addons appear on the urlbar (gh-13535) 2026-05-03 14:53:37 +02:00
mr. m
a71a66c00b no-bug: Force boosts colors to fit in 32 bit ints (gh-13532) 2026-05-02 11:36:15 +02:00
mr. m
c0cd45bf1d gh-13530: Apply translations to desktop files (gh-13531) 2026-05-02 11:08:08 +02:00
mr. m
685cddf7c2 no-bug: Add space button 2026-05-01 19:11:12 +02:00
mr. m
27f40393d5 no-bug: Simplify stylings 2026-04-29 20:11:05 +02:00
mr. m
f99b8af86d no-bug: Collapse top buttons for little zen 2026-04-29 12:04:32 +02:00
mr. m
e3b0295e36 no-bug: Continue work 2026-04-29 02:21:22 +02:00
mr. m
d15f5331ff Merge branch 'dev' into little-zen 2026-04-28 00:38:39 +02:00
mr. m
cccbcf662e no-bug: Start working on little zen 2026-04-28 00:38:09 +02:00
111 changed files with 4140 additions and 1161 deletions

View File

@@ -34,8 +34,8 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
### Firefox Versions
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `150.0.1`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 150.0.1`!
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `150.0.2`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 150.0.2`!
### Contributing

View File

@@ -1,31 +1,698 @@
[Desktop Entry]
Name=Zen Browser
Comment=Experience tranquillity while browsing the web without people tracking you!
Comment=A fast, private and secure web browser built to improve your day-to-day experience.
Exec=zen %u
Icon=zen
Type=Application
MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;application/x-xpinstall;application/pdf;application/json;
MimeType=application/json;application/pdf;application/rdf+xml;application/rss+xml;application/x-xpinstall;application/xhtml+xml;application/xml;audio/flac;audio/ogg;audio/webm;image/avif;image/gif;image/jpeg;image/png;image/svg+xml;image/webp;text/html;text/xml;video/ogg;video/webm;x-scheme-handler/chrome;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/mailto;
StartupWMClass=zen
Categories=Network;WebBrowser;
StartupNotify=true
Terminal=false
X-MultipleArgs=false
GenericName=Web Browser
GenericName[ach]=Web Browser
GenericName[af]=Web Browser
GenericName[an]=Web Browser
GenericName[ar]=متصفح الوِب
GenericName[ast]=Web Browser
GenericName[az]=Web Browser
GenericName[be]=Вэб-браўзер
GenericName[bg]=Уеб браузър
GenericName[bn]=ওয়েব ব্রাউজার
GenericName[bqi]=گشت گر وب
GenericName[br]=Merdeer Web
GenericName[brx]=Web Browser
GenericName[bs]=Web pretraživač
GenericName[ca]=Navegador web
GenericName[ca_valencia]=Web Browser
GenericName[cak]=Web Okik'amaya'l
GenericName[ckb]=Web Browser
GenericName[cs]=Webový prohlížeč
GenericName[cy]=Porwr Gwe
GenericName[da]=Webbrowser
GenericName[de]=Internet-Browser
GenericName[dsb]=Webwobglědowak
GenericName[el]=Πρόγραμμα περιήγησης
GenericName[en_CA]=Web Browser
GenericName[en_GB]=Web Browser
GenericName[eo]=Retumilo
GenericName[es_AR]=Navegador web
GenericName[es_CL]=Navegador Web
GenericName[es_ES]=Navegador web
GenericName[es_MX]=Navegador Web
GenericName[et]=Web Browser
GenericName[eu]=Web nabigatzailea
GenericName[fa]=مرورگر وب
GenericName[ff]=Web Browser
GenericName[fi]=Verkkoselain
GenericName[fr]=Navigateur web
GenericName[fur]=Navigadôr Web
GenericName[fy_NL]=Webbrowser
GenericName[ga_IE]=Web Browser
GenericName[gd]=Brabhsair-lìn
GenericName[gl]=Navegador web
GenericName[gn]=Ñanduti Kundahára
GenericName[gu_IN]=Web Browser
GenericName[he]=דפדפן אינטרנט
GenericName[hi_IN]=Web Browser
GenericName[hr]=Web preglednik
GenericName[hsb]=Webwobhladowak
GenericName[hu]=Webböngésző
GenericName[hy_AM]=Վեբ դիտարկիչ
GenericName[hye]=Web Browser
GenericName[ia]=Navigator web
GenericName[id]=Peramban Web
GenericName[is]=Vafri
GenericName[it]=Browser web
GenericName[ja]=ウェブブラウザー
GenericName[ka]=ბრაუზერი
GenericName[kab]=Iminig web
GenericName[kk]=Веб-браузері
GenericName[km]=Web Browser
GenericName[kn]=Web Browser
GenericName[ko]=웹 브라우저
GenericName[lij]=Navegatô Web
GenericName[lo]=ຕົວ​ທ່ອງ​ເວັບ​ເວັບ​ໄຊ​ຕ​໌
GenericName[lt]=Web Browser
GenericName[ltg]=Web Browser
GenericName[lv]=Tīmekļa pārlūks
GenericName[meh]=Web Browser
GenericName[mk]=Web Browser
GenericName[ml]=ഗോളാന്തരവല അന്വേഷിയന്ത്രം
GenericName[mr]=Web Browser
GenericName[ms]=Web Browser
GenericName[my]=Web Browser
GenericName[nb_NO]=Nettleser
GenericName[ne_NP]=वेब ब्राउजर
GenericName[nl]=Webbrowser
GenericName[nn_NO]=Nettlesar
GenericName[oc]=Navegador Web
GenericName[pa_IN]=ਵੈੱਬ ਬਰਾਊਜ਼ਰ
GenericName[pl]=Przeglądarka internetowa
GenericName[pt_BR]=Navegador web
GenericName[pt_PT]=Navegador Web
GenericName[rm]=Navigatur web
GenericName[ro]=Browser web
GenericName[ru]=Веб-браузер
GenericName[sat]=ᱣᱮᱵᱽ ᱵᱽᱨᱟᱣᱡᱚᱨ
GenericName[sc]=Navigadore web
GenericName[sco]=Web Browser
GenericName[si]=වියමන අතිරික්සුව
GenericName[sk]=Webový prehliadač
GenericName[skr]=ویب براؤزر
GenericName[sl]=Spletni brskalnik
GenericName[son]=Web Browser
GenericName[sq]=Shfletues
GenericName[sr]=Веб прегледач
GenericName[sv_SE]=Webbläsare
GenericName[szl]=Web Browser
GenericName[ta]=Web Browser
GenericName[te]=జాల విహారిణి
GenericName[tg]=Браузери веб
GenericName[th]=เว็บเบราว์เซอร์
GenericName[tl]=Web Browser
GenericName[tr]=Web Tarayıcısı
GenericName[trs]=Web riña gāchē nu
GenericName[uk]=Браузер
GenericName[ur]=Web Browser
GenericName[uz]=Web Browser
GenericName[vi]=Trình duyệt web
GenericName[wo]=Web Browser
GenericName[xh]=Web Browser
GenericName[zh_CN]=Web 浏览器
GenericName[zh_TW]=網頁瀏覽器
Keywords=Internet;WWW;Browser;Web;Explorer;
Keywords[ach]=Internet;WWW;Browser;Web;Explorer;
Keywords[af]=Internet;WWW;Browser;Web;Explorer;
Keywords[an]=Internet;WWW;Browser;Web;Explorer;
Keywords[ar]=إنترنت;WWW;متصفح;ويب;مستكشف;
Keywords[ast]=Internet;WWW;Browser;Web;Explorer;
Keywords[az]=Internet;WWW;Browser;Web;Explorer;
Keywords[be]=Internet;WWW;Browser;Web;Explorer;
Keywords[bg]=Internet;WWW;Browser;Web;Explorer;
Keywords[bn]=ইন্টারনেট;WWW;ব্রাউজার;ওয়েব;এক্সপ্লোরার;
Keywords[bqi]=Internet;WWW;Browser;Web;Explorer;
Keywords[br]=Internet;WWW;Merdeer;Web;Ergerzhout;
Keywords[brx]=Internet;WWW;Browser;Web;Explorer;
Keywords[bs]=Internet;WWW;Pretraživač;Web;Istraživač;
Keywords[ca]=Internet;WWW;Browser;Web;Explorador;Navegador;
Keywords[ca_valencia]=Internet;WWW;Browser;Web;Explorer;
Keywords[cak]=K'amaya'l;WWW;Okik'amaya'l;Kanob'äl;
Keywords[ckb]=Internet;WWW;Browser;Web;Explorer;
Keywords[cs]=internet;WWW;prohlížeč;web;
Keywords[cy]=Rhyngrwyd;WWW;Porwr;Gwe;Archwiliwr;
Keywords[da]=Internet;WWW;Browser;Nettet;Explorer;
Keywords[de]=Internet;WWW;Browser;Web;Explorer;
Keywords[dsb]=Internet;WWW;wobglědowak;Web;Explorer;
Keywords[el]=Internet;WWW;Browser;Web;Explorer;Διαδίκτυο;Ιστός;Ίντερνετ;
Keywords[en_CA]=Internet;WWW;Browser;Web;Explorer;
Keywords[en_GB]=Internet;WWW;Browser;Web;Explorer;
Keywords[eo]=Interreto;Retumilo;TTT;Teksaĵo;Reto;Internet;WWW;Browser;Web;Explorer;
Keywords[es_AR]=Internet;WWW;Navegador;Web;Explorador;
Keywords[es_CL]=Internet;WWW;Navegador;Web;Explorador;
Keywords[es_ES]=Internet;WWW;Navegador;Web;Explorador;
Keywords[es_MX]=Internet;WWW;Navegador;Web;Explorador;
Keywords[et]=Internet;WWW;Browser;Web;Explorer;
Keywords[eu]=Internet;WWW;Nabigatzailea;Web;Arakatzailea;
Keywords[fa]=Internet;WWW;Browser;Web;Explorer;
Keywords[ff]=Internet;WWW;Browser;Web;Explorer;
Keywords[fi]=Internet;WWW;Browser;Web;Explorer;netti;webbi;selain;
Keywords[fr]=Internet;WWW;Navigateur;Web;Explorer;
Keywords[fur]=Internet;WWW;Browser;Navigadôr;Web;Esploradôr;Explorer;
Keywords[fy_NL]=Ynternet;WWW;Browser;Web;Ferkenner;
Keywords[ga_IE]=Internet;WWW;Browser;Web;Explorer;
Keywords[gd]=Internet;WWW;Browser;Web;Explorer;eadar-lìon;brabhsair;brobhsair;lìon;taisgealaiche;
Keywords[gl]=Internet;WWW;Navegador;Web;Explorador;
Keywords[gn]=Internet;WWW;Browser;Web;Explorer;
Keywords[gu_IN]=Internet;WWW;Browser;Web;Explorer;
Keywords[he]=אינטרנט;WWW;דפדפן;רשת;סייר;מרשתת;
Keywords[hi_IN]=Internet;WWW;Browser;Web;Explorer;
Keywords[hr]=Internet;WWW;Preglednik;Web;Istraživač;
Keywords[hsb]=Internet;WWW;wobhladowak;Web;Explorer;
Keywords[hu]=Internet;WWW;Böngésző;Web;Világháló;
Keywords[hy_AM]=Համացանց,WWW,Զննիչ,Վեբ,Ցանցախույզ:
Keywords[hye]=Internet;WWW;Browser;Web;Explorer;
Keywords[ia]=Internet;WWW;Navigator;Web;Explorator;
Keywords[id]=Internet;WWW;Browser;Web;Explorer;
Keywords[is]=Internet;WWW; Vafri; Vefur; Explorer;
Keywords[it]=Internet;WWW;Browser;Web;Explorer;Navigatore;
Keywords[ja]=Internet;WWW;Browser;Web;Explorer;インターネット;ブラウザー;ウェブ;
Keywords[ka]=ინტერნეტი;WWW;ბრაუზერი;ქსელი;ქსელთან წვდომა;
Keywords[kab]=Internet;WWW;Browser;Web;Explorer;
Keywords[kk]=Internet;WWW;Browser;Web;Explorer;Интернет;Ғаламтор;Браузер;Желі;Шолғыш;
Keywords[km]=Internet;WWW;Browser;Web;Explorer;
Keywords[kn]=Internet;WWW;Browser;Web;Explorer;
Keywords[ko]=인터넷;브라우저;웹;탐색기;Internet;WWW;Browser;Web;Explorer;
Keywords[lij]=Internet;WWW;Browser;Web;Explorer;Navegatô;
Keywords[lo]=Internet;WWW;Browser;Web;Explorer;
Keywords[lt]=Internet;WWW;Browser;Web;Explorer;
Keywords[ltg]=Internet;WWW;Browser;Web;Explorer;
Keywords[lv]=Internets;WWW;Pārlūkprogramma;Tīmeklis;
Keywords[meh]=Internet;WWW;Browser;Web;Explorer;
Keywords[mk]=Internet;WWW;Browser;Web;Explorer;
Keywords[ml]=ഗോളാന്തരവല;WWW;അന്വേഷിയന്ത്രം;ഗോളാന്തരവല;ആരായൻ;
Keywords[mr]=Internet;WWW;Browser;Web;Explorer;
Keywords[ms]=Internet;WWW;Browser;Web;Explorer;
Keywords[my]=Internet;WWW;Browser;Web;Explorer;
Keywords[nb_NO]=Internett;WWW;Nettleser;Web;Utforsker;
Keywords[ne_NP]=Internet;WWW;Browser;Web;Explorer;
Keywords[nl]=Internet;WWW;Browser;Web;Verkenner;
Keywords[nn_NO]=Internett;WWW;Nettlesar;Web;Utforskar;
Keywords[oc]=Internet;WWW;Navegador;Navigador;Navegator;Navigator;Web;Explorer;
Keywords[pa_IN]=ਇੰਟਰਨੈੱਟ;WWW;ਬਰਾਊਜ਼ਰ;ਵੈੱਬ;ਐਕਸਪਲਰੋਰ;ਵੈਬ;ਇੰਟਰਨੈਟ;
Keywords[pl]=Internet;WWW;Przeglądarka;Browser;Wyszukiwarka;Web;Sieć;Explorer;Eksplorer;Strony;Witryny;internetowe;
Keywords[pt_BR]=Internet;WWW;Browser;Web;Explorer;Navegador;
Keywords[pt_PT]=Internet;WWW;Navegador;Web;Explorador;
Keywords[rm]=Internet;WWW;Browser;Web;Explorer;navigatur;
Keywords[ro]=Internet; WWW; Browser; Web; Explorer;
Keywords[ru]=Сеть;Интернет;Браузер;Доступ в Интернет;
Keywords[sat]=Internet;WWW;Browser;Web;Explorer;
Keywords[sc]=Internet;WWW;Navigadore;Web;Explorer;
Keywords[sco]=Internet;WWW;Browser;Web;Explorer;
Keywords[si]=අන්තර්ජාලය;අතිරික්සුව;පිරික්සන්න;ගවේශකය;Internet;WWW;Browser;Web;Explorer;
Keywords[sk]=Internet;WWW;Prehliadač;Web;Prieskumník;
Keywords[skr]=Internet;WWW;Browser;Web;Explorer;
Keywords[sl]=internet;www;brskalnik;splet;
Keywords[son]=Internet;WWW;Browser;Web;Explorer;
Keywords[sq]=Internet;WWW;Shfletues;Web;Eksplorues;
Keywords[sr]=Internet;WWW;Browser;Web;Explorer;интернет;њњњ;веб;мрежа;прегледач;експлорер;internet;pregledač;veb;mreža;pregledač;eksplorer;
Keywords[sv_SE]=Internet;WWW;Webbläsare;Webb;Utforskare;
Keywords[szl]=Internet;WWW;Browser;Web;Explorer;
Keywords[ta]=Internet;WWW;Browser;Web;Explorer;
Keywords[te]=Internet;WWW;Browser;Web;Explorer;
Keywords[tg]=Интернет;WWW;Браузер;Сомона;Ҷустуҷӯгар;
Keywords[th]=อินเทอร์เน็ต;เบราว์เซอร์;เว็บ;Internet;WWW;Browser;Web;Explorer;
Keywords[tl]=Internet;WWW;Browser;Web;Explorer;
Keywords[tr]=Internet;WWW;Browser;Web;Explorer;İnternet;Tarayıcı;
Keywords[trs]=Internet;WWW;Browser;Web;Explorer;
Keywords[uk]=Інтернет;WWW;Браузер;Веб;Переглядач;
Keywords[ur]=Internet;WWW;Browser;Web;Explorer;
Keywords[uz]=Internet;WWW;Browser;Web;Explorer;
Keywords[vi]=Internet;WWW;Trình duyệt;Web;Duyệt web;
Keywords[wo]=Internet;WWW;Browser;Web;Explorer;
Keywords[xh]=Internet;WWW;Browser;Web;Explorer;
Keywords[zh_CN]=Internet;WWW;Browser;Web;Explorer;
Keywords[zh_TW]=網際網路;網路;瀏覽器;網頁;上網;Internet;WWW;Browser;Web;Explorer;
Actions=new-window;new-blank-window;new-private-window;profilemanager;
X-AppImage-Version=$VERSION
[Desktop Action new-window]
Name=Open a New Window
Exec=zen %u
Name=New Window
Name[ach]=New Window
Name[af]=New Window
Name[an]=New Window
Name[ar]=نافذة جديدة
Name[ast]=New Window
Name[az]=New Window
Name[be]=Новае акно
Name[bg]=Нов прозорец
Name[bn]=নতুন উইন্ডো
Name[bqi]=نیمدری نۊ
Name[br]=Prenestr nevez
Name[brx]=New Window
Name[bs]=Novi prozor
Name[ca]=Finestra nova
Name[ca_valencia]=New Window
Name[cak]=K'ak'a' Tzuwäch
Name[ckb]=New Window
Name[cs]=Nové okno
Name[cy]=Ffenestr Newydd
Name[da]=Nyt vindue
Name[de]=Neues Fenster
Name[dsb]=Nowe wokno
Name[el]=Νέο παράθυρο
Name[en_CA]=New Window
Name[en_GB]=New Window
Name[eo]=Nova fenestro
Name[es_AR]=Nueva ventana
Name[es_CL]=Nueva ventana
Name[es_ES]=Nueva ventana
Name[es_MX]=Nueva ventana
Name[et]=New Window
Name[eu]=Leiho berria
Name[fa]=پنجره جدید‌
Name[ff]=New Window
Name[fi]=Uusi ikkuna
Name[fr]=Nouvelle fenêtre
Name[fur]=Gnûf barcon
Name[fy_NL]=Nij finster
Name[ga_IE]=New Window
Name[gd]=Uinneag ùr
Name[gl]=Nova xanela
Name[gn]=Ovetã pyahu
Name[gu_IN]=New Window
Name[he]=חלון חדש
Name[hi_IN]=New Window
Name[hr]=Novi prozor
Name[hsb]=Nowe wokno
Name[hu]=Új ablak
Name[hy_AM]=Նոր պատուհան
Name[hye]=New Window
Name[ia]=Nove fenestra
Name[id]=Jendela Baru
Name[is]=Nýr gluggi
Name[it]=Nuova finestra
Name[ja]=新しいウィンドウ
Name[ka]=ახალი ფანჯარა
Name[kab]=Asfaylu amaynut
Name[kk]=Жаңа терезе
Name[km]=បង្អួច​​​ថ្មី
Name[kn]=New Window
Name[ko]=새 창
Name[lij]=Neuvo Barcon
Name[lo]=ວິນໂດໃໝ່
Name[lt]=New Window
Name[ltg]=New Window
Name[lv]=Jauns logs
Name[meh]=New Window
Name[mk]=New Window
Name[ml]=പുതിയ ജാലകം
Name[mr]=New Window
Name[ms]=New Window
Name[my]=New Window
Name[nb_NO]=Nytt vindu
Name[ne_NP]=नयाँ सञ्झ्याल
Name[nl]=Nieuw venster
Name[nn_NO]=Nytt vindauge
Name[oc]=Fenèstra novèla
Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ
Name[pl]=Nowe okno
Name[pt_BR]=Nova janela
Name[pt_PT]=Nova janela
Name[rm]=Nova fanestra
Name[ro]=Fereastră nouă
Name[ru]=Новое окно
Name[sat]=ᱱᱟᱶᱟ ᱣᱤᱱᱰᱳ
Name[sc]=Ventana noa
Name[sco]=New Window
Name[si]=නව කවුළුව
Name[sk]=Nové okno
Name[skr]=نویں ونڈو
Name[sl]=Novo okno
Name[son]=New Window
Name[sq]=Dritare e Re
Name[sr]=Нови прозор
Name[sv_SE]=Nytt fönster
Name[szl]=New Window
Name[ta]=New Window
Name[te]=కొత్త కిటికీ
Name[tg]=Равзанаи нав
Name[th]=หน้าต่างใหม่
Name[tl]=New Window
Name[tr]=Yeni pencere
Name[trs]=Bēntanâ nākàa
Name[uk]=Нове вікно
Name[ur]=New Window
Name[uz]=New Window
Name[vi]=Cửa sổ mới
Name[wo]=New Window
Name[xh]=New Window
Name[zh_CN]=新建窗口
Name[zh_TW]=開新視窗
[Desktop Action new-blank-window]
Name=Open a New Blank Window
Exec=zen --blank-window %u
Name=New Blank Window
Name[ach]=New Blank Window
Name[af]=New Blank Window
Name[an]=New Blank Window
Name[ar]=نافذة فارغة جديدة
Name[ast]=New Blank Window
Name[az]=New Blank Window
Name[be]=Новае пустое акно
Name[bg]=Нов празен прозорец
Name[bn]=নতুন ফাঁকা উইন্ডো
Name[bqi]=نیمدری نۊ خالی
Name[br]=Prenestr goulloù nevez
Name[brx]=New Blank Window
Name[bs]=Novi prazni prozor
Name[ca]=Finestra en blanc nova
Name[ca_valencia]=New Blank Window
Name[cak]=K'ak'a' Tzuwäch K'axk'ol
Name[ckb]=New Blank Window
Name[cs]=Nové prázdné okno
Name[cy]=Ffenestr Wag Newydd
Name[da]=Nyt tomt vindue
Name[de]=Neues leeres Fenster
Name[dsb]=Nowe prázdne wokno
Name[el]=Νέο κενό παράθυρο
Name[en_CA]=New Blank Window
Name[en_GB]=New Blank Window
Name[eo]=Nova malplena fenestro
Name[es_AR]=Nueva ventana en blanco
Name[es_CL]=Nueva ventana en blanco
Name[es_ES]=Nueva ventana en blanco
Name[es_MX]=Nueva ventana en blanco
Name[et]=New Blank Window
Name[eu]=Leiho huts berri
Name[fa]=پنجره خالی جدید
Name[ff]=New Blank Window
Name[fi]=Uusi tyhjä ikkuna
Name[fr]=Nouvelle fenêtre vierge
Name[fur]=Gnûf barcon vuot
Name[fy_NL]=Nij leeg finster
Name[ga_IE]=New Blank Window
Name[gd]=Uinneag bhàn ùr
Name[gl]=Nova xanela en branco
Name[gn]=Ovetã ñemi pyahu
Name[gu_IN]=New Blank Window
Name[he]=חלון ריק חדש
Name[hi_IN]=New Blank Window
Name[hr]=Novi prazni prozor
Name[hsb]=Nowe prázdne wokno
Name[hu]=Új üres ablak
Name[hy_AM]=Նոր դատարկ պատուհան
Name[hye]=New Blank Window
Name[ia]=Nove fenestra vacue
Name[id]=Jendela Kosong Baru
Name[is]=Nýr tómur gluggi
Name[it]=Nuova finestra vuota
Name[ja]=新しい空白のウィンドウ
Name[ka]=ახალი ცარიელი ფანჯარა
Name[kab]=Asfaylu amaynut n tunigin tusligt
Name[kk]=Жаңа бос терезе
Name[km]=បង្អួច​ថ្មី​ឯកជន
Name[kn]=New Blank Window
Name[ko]=새 빈 창
Name[lij]=Neuvo Barcon Vuot
Name[lo]=ວິນໂດແອ່ງໃໝ່
Name[lt]=New Blank Window
Name[ltg]=New Blank Window
Name[lv]=Jauns tukšais logs
Name[meh]=New Blank Window
Name[mk]=New Blank Window
Name[ml]=പുതിയ ശൂന്യ ജാലകം
Name[mr]=New Blank Window
Name[ms]=New Blank Window
Name[my]=New Blank Window
Name[nb_NO]=Nytt tomt vindu
Name[ne_NP]=नयाँ खाली सञ्झ्याल
Name[nl]=Nieuw leeg venster
Name[nn_NO]=Nytt tomt vindauge
Name[oc]=Fenèstra en blanc novèla
Name[pa_IN]=ਨਵੀਂ ਖਾਲੀ ਵਿੰਡੋ
Name[pl]=Nowe puste okno
Name[pt_BR]=Nova janela em branco
Name[pt_PT]=Nova janela em branco
Name[rm]=Nova fanestra vacue
Name[ro]=Fereastră nouă și goală
Name[ru]=Новое пустое окно
Name[sat]=ᱱᱟᱶᱟ ᱣᱤᱱᱰᱳ ᱵᱽᱨᱟᱣᱡᱚᱨ
Name[sc]=Ventana en blanc noa
Name[sco]=New Blank Window
Name[si]=නව හිස් කවුළුව
Name[sk]=Nové prázdné okno
Name[skr]=نویں خالی ونڈو
Name[sl]=Novo prazno okno
Name[son]=New Blank Window
Name[sq]=Dritare e Re e Pafajshme
Name[sr]=Нови празни прозор
Name[sv_SE]=Nytt tomt fönster
Name[szl]=New Blank Window
Name[ta]=New Blank Window
Name[te]=కొత్త ఖాళీ కిటికీ
Name[tg]=Равзанаи холӣ нав
Name[th]=หน้าต่างว่างเปล่าใหม่
Name[tl]=New Blank Window
Name[tr]=Yeni boş pencere
Name[trs]=Bēntanâ huì nākàa
Name[uk]=Нове порожнє вікно
Name[ur]=New Blank Window
Name[uz]=New Blank Window
Name[vi]=Cửa sổ trống mới
Name[wo]=New Blank Window
Name[xh]=New Blank Window
Name[zh_CN]=新建空白窗口
Name[zh_TW]=開新空白視窗
[Desktop Action new-private-window]
Name=Open a New Private Window
Exec=zen --private-window %u
Name=New Private Window
Name[ach]=New Private Window
Name[af]=New Private Window
Name[an]=New Private Window
Name[ar]=نافذة خاصة جديدة
Name[ast]=New Private Window
Name[az]=New Private Window
Name[be]=Новае прыватнае акно
Name[bg]=Нов личен прозорец
Name[bn]=নতুন ব্যক্তিগত উইন্ডো
Name[bqi]=نیمدری سیخومی نۊ
Name[br]=Prenestr prevez nevez
Name[brx]=New Private Window
Name[bs]=Novi privatni prozor
Name[ca]=Finestra privada nova
Name[ca_valencia]=New Private Window
Name[cak]=K'ak'a' Ichinan Tzuwäch
Name[ckb]=New Private Window
Name[cs]=Nové anonymní okno
Name[cy]=Ffenestr Breifat Newydd
Name[da]=Nyt privat vindue
Name[de]=Neues privates Fenster
Name[dsb]=Nowe priwatne wokno
Name[el]=Νέο ιδιωτικό παράθυρο
Name[en_CA]=New Private Window
Name[en_GB]=New Private Window
Name[eo]=Nova privata fenestro
Name[es_AR]=Nueva ventana privada
Name[es_CL]=Nueva ventana privada
Name[es_ES]=Nueva ventana privada
Name[es_MX]=Nueva ventana privada
Name[et]=New Private Window
Name[eu]=Leiho pribatu berria
Name[fa]=پنجره ناشناس جدید
Name[ff]=New Private Window
Name[fi]=Uusi yksityinen ikkuna
Name[fr]=Nouvelle fenêtre privée
Name[fur]=Gnûf barcon privât
Name[fy_NL]=Nij priveefinster
Name[ga_IE]=New Private Window
Name[gd]=Uinneag phrìobhaideach ùr
Name[gl]=Nova xanela privada
Name[gn]=Ovetã ñemi pyahu
Name[gu_IN]=New Private Window
Name[he]=חלון פרטי חדש
Name[hi_IN]=New Private Window
Name[hr]=Novi privatni prozor
Name[hsb]=Nowe priwatne wokno
Name[hu]=Új privát ablak
Name[hy_AM]=Նոր գաղտնի պատուհան
Name[hye]=New Private Window
Name[ia]=Nove fenestra private
Name[id]=Jendela Mode Pribadi Baru
Name[is]=Nýr huliðsgluggi
Name[it]=Nuova finestra anonima
Name[ja]=新しいプライベートウィンドウ
Name[ka]=ახალი პირადი ფანჯარა
Name[kab]=Asfaylu amaynut n tunigin tusligt
Name[kk]=Жаңа жекелік терезе
Name[km]=បង្អួច​ឯកជន​ថ្មី
Name[kn]=New Private Window
Name[ko]=새 사생활 보호 창
Name[lij]=Neuvo Barcon Privòu
Name[lo]=ວິນໂດສ່ວນຕົວໃໝ່
Name[lt]=New Private Window
Name[ltg]=New Private Window
Name[lv]=Jauns privātais logs
Name[meh]=New Private Window
Name[mk]=New Private Window
Name[ml]=പുതിയ സ്വകാര്യ ജാലകം
Name[mr]=New Private Window
Name[ms]=New Private Window
Name[my]=New Private Window
Name[nb_NO]=Nytt privat vindu
Name[ne_NP]=नयाँ निजी सञ्झ्याल
Name[nl]=Nieuw privévenster
Name[nn_NO]=Nytt privat vindauge
Name[oc]=Fenèstra privada novèla
Name[pa_IN]=ਨਵੀਂ ਪ੍ਰਾਈਵੇਟ ਵਿੰਡੋ
Name[pl]=Nowe okno prywatne
Name[pt_BR]=Nova janela privativa
Name[pt_PT]=Nova janela privada
Name[rm]=Nova fanestra privata
Name[ro]=Fereastră privată nouă
Name[ru]=Новое приватное окно
Name[sat]=ᱱᱟᱶᱟ ᱱᱤᱡᱮᱨᱟᱜ ᱣᱤᱱᱰᱳ
Name[sc]=Ventana privada noa
Name[sco]=New Private Window
Name[si]=නව පෞද්. කවුළුව
Name[sk]=Nové súkromné okno
Name[skr]=نویں نجی ونڈو
Name[sl]=Novo zasebno okno
Name[son]=New Private Window
Name[sq]=Dritare e Re Private
Name[sr]=Нови приватни прозор
Name[sv_SE]=Nytt privat fönster
Name[szl]=New Private Window
Name[ta]=New Private Window
Name[te]=కొత్త ఆంతరంగిక కిటికీ
Name[tg]=Равзанаи хусусии нав
Name[th]=หน้าต่างส่วนตัวใหม่
Name[tl]=New Private Window
Name[tr]=Yeni gizli pencere
Name[trs]=Bēntanâ huì nākàa
Name[uk]=Приватне вікно
Name[ur]=New Private Window
Name[uz]=New Private Window
Name[vi]=Cửa sổ riêng tư mới
Name[wo]=New Private Window
Name[xh]=New Private Window
Name[zh_CN]=新建隐私窗口
Name[zh_TW]=開新隱私視窗
[Desktop Action profilemanager]
Name=Open the Profile Manager
Exec=zen --ProfileManager %u
Name=Open Profile Manager
Name[ach]=Open Profile Manager
Name[af]=Open Profile Manager
Name[an]=Open Profile Manager
Name[ar]=افتح مدير الملف الشخصي
Name[ast]=Open Profile Manager
Name[az]=Open Profile Manager
Name[be]=Адкрыць менеджар профіляў
Name[bg]=Отваряне на мениджъра на профили
Name[bn]=Open Profile Manager
Name[bqi]=گۊشیڌن دؽوۉداری پوروفایل
Name[br]=Digeriñ an ardoer aeladoù
Name[brx]=Open Profile Manager
Name[bs]=Otvori Menadžera profila
Name[ca]=Obre el gestor de perfils
Name[ca_valencia]=Open Profile Manager
Name[cak]=Open Profile Manager
Name[ckb]=Open Profile Manager
Name[cs]=Otevřít Správce profilů
Name[cy]=Agorwch y Rheolwr Proffil
Name[da]=Åbn profilhåndtering
Name[de]=Profilverwaltung öffnen
Name[dsb]=Profilowy zastojnik wócyniś
Name[el]=Άνοιγμα Διαχείρισης προφίλ
Name[en_CA]=Open Profile Manager
Name[en_GB]=Open Profile Manager
Name[eo]=Malfermi administranton de profiloj
Name[es_AR]=Abrir administrador de perfiles
Name[es_CL]=Abrir administrador de perfiles
Name[es_ES]=Abrir administrador de perfiles
Name[es_MX]=Abrir administrador de perfiles
Name[et]=Open Profile Manager
Name[eu]=Ireki profilen kudeatzailea
Name[fa]=گشودن مدیر نمایه
Name[ff]=Open Profile Manager
Name[fi]=Avaa profiilien hallinta
Name[fr]=Ouvrir le gestionnaire de profils
Name[fur]=Vierç gjestôr profîi
Name[fy_NL]=Profylbehearder iepenje
Name[ga_IE]=Open Profile Manager
Name[gd]=Fosgail manaidsear nam pròifilean
Name[gl]=Abrir o xestor de perfís
Name[gn]=Embojuruja mbaete ñangarekoha
Name[gu_IN]=Open Profile Manager
Name[he]=פתיחת מנהל הפרופילים
Name[hi_IN]=Open Profile Manager
Name[hr]=Otvori upravljač profila
Name[hsb]=Zrjadowak profilow wočinić
Name[hu]=Profilkezelő megnyitása
Name[hy_AM]=Բացեք պրոֆիլի կառավարիչը
Name[hye]=Open Profile Manager
Name[ia]=Aperir le gestor de profilo
Name[id]=Buka Pengelola Profil
Name[is]=Opna umsýslu notandasniða
Name[it]=Apri gestore profili
Name[ja]=プロファイルマネージャーを開く
Name[ka]=პროფილის მმართველის გახსნა
Name[kab]=Ldi amsefrak n umaɣnu
Name[kk]=Профильдер бақарушысын ашу
Name[km]=Open Profile Manager
Name[kn]=Open Profile Manager
Name[ko]=프로필 관리자 열기
Name[lij]=Open Profile Manager
Name[lo]=ເປີດຕົວຈັດການໂປຣໄຟລ໌
Name[lt]=Open Profile Manager
Name[ltg]=Open Profile Manager
Name[lv]=Atvērt profilu pārvaldnieku
Name[meh]=Open Profile Manager
Name[mk]=Open Profile Manager
Name[ml]=രൂപരേഖ മാനേചർ
Name[mr]=Open Profile Manager
Name[ms]=Open Profile Manager
Name[my]=Open Profile Manager
Name[nb_NO]=Åpne profilbehandler
Name[ne_NP]=Open Profile Manager
Name[nl]=Profielbeheerder openen
Name[nn_NO]=Opne profilhandsaming
Name[oc]=Dobrir lo gestionari de perfils
Name[pa_IN]=ਪਰੋਫ਼ਾਈਲ ਮੈਨੇਜਰ ਖੋਲ੍ਹੋ
Name[pl]=Menedżer profili
Name[pt_BR]=Abrir gerenciador de perfis
Name[pt_PT]=Abrir o Gestor de Perfis
Name[rm]=Avrir l'administraziun da profils
Name[ro]=Deschide managerul de profiluri
Name[ru]=Открыть менеджер профилей
Name[sat]=ᱢᱮᱫᱦᱟᱸ ᱢᱮᱱᱮᱡᱚᱨ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ
Name[sc]=Aberi su gestore de profilos
Name[sco]=Open Profile Manager
Name[si]=පැතිකඩ කළමනාකරු අරින්න
Name[sk]=Otvoriť Správcu profilov
Name[skr]=پروفائل منیجر کھولو
Name[sl]=Odpri upravitelja profilov
Name[son]=Open Profile Manager
Name[sq]=Hapni Përgjegjës Profilesh
Name[sr]=Отворите управљач профила
Name[sv_SE]=Öppna Profilhanteraren
Name[szl]=Open Profile Manager
Name[ta]=Open Profile Manager
Name[te]=Open Profile Manager
Name[tg]=Кушодани мудири профилҳо
Name[th]=เปิดตัวจัดการโปรไฟล์
Name[tl]=Open Profile Manager
Name[tr]=Profil yöneticisini aç
Name[trs]=Sa nīkāj ñuūnj nej perfî huā nìnï̀nj ïn
Name[uk]=Відкрити менеджер профілів
Name[ur]=Open Profile Manager
Name[uz]=Open Profile Manager
Name[vi]=Mở trình quản lý hồ sơ
Name[wo]=Open Profile Manager
Name[xh]=Open Profile Manager
Name[zh_CN]=打开配置文件管理器
Name[zh_TW]=開啟設定檔管理員

View File

@@ -1 +1 @@
e097b3c5ca4139a5b88052379a776782e078356b
73901ca17f4a2159dd4488cea8684e9abbfdcc89

View File

@@ -321,6 +321,7 @@ zen-workspace-shortcut-switch-9 = Switch to Workspace 9
zen-workspace-shortcut-switch-10 = Switch to Workspace 10
zen-workspace-shortcut-forward = Forward Workspace
zen-workspace-shortcut-backward = Backward Workspace
zen-workspace-shortcut-create = Create New Workspace
zen-sidebar-shortcut-toggle = Toggle Sidebar's Width
zen-pinned-tab-shortcut-reset = Reset Pinned Tab to Pinned URL
zen-split-view-shortcut-grid = Toggle Split View Grid

View File

@@ -79,7 +79,6 @@ zen-icons-picker-svg =
.label = Icons
urlbar-search-mode-zen_actions = Actions
urlbar-search-mode-workspaces = { zen-panel-ui-workspaces-text }
zen-site-data-settings = Settings
zen-generic-manage = Manage
@@ -152,3 +151,5 @@ zen-window-sync-migration-dialog-accept = Got It
zen-appmenu-new-blank-window =
.label = New blank window
zen-spaces-search-placeholder =
.placeholder = Search your spaces...

8
package-lock.json generated
View File

@@ -1005,13 +1005,13 @@
"license": "MIT"
},
"node_modules/axios": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"proxy-from-env": "^2.1.0"
}

View File

@@ -27,7 +27,7 @@
"surfer": "surfer",
"test": "python3 scripts/run_tests.py",
"test:dbg": "python3 scripts/run_tests.py --jsdebugger --debug-on-failure",
"ffprefs": "cd tools/ffprefs && cargo run --bin ffprefs -- ../../",
"ffprefs": "${CARGO:-cargo} run --manifest-path tools/ffprefs/Cargo.toml --bin ffprefs -- prefs engine",
"lc": "surfer license-check",
"lc:fix": "surfer license-check --fix",
"use-moz-src": "cd engine && ./mach use-moz-src",

View File

@@ -7,3 +7,6 @@
- name: zen.keyboard.shortcuts.disable-mainkeyset-clear
value: false # for debugging
- name: zen.keyboard.shortcuts.global.enabled
value: true

View File

@@ -2,4 +2,5 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
content/browser/zen-components/ZenKeyboardShortcuts.mjs (../../zen/kbs/ZenKeyboardShortcuts.mjs)
- name: zen.live-folders.github.skip-new-pr-ui-check
value: false

View File

@@ -5,6 +5,9 @@
- name: zen.splitView.enable-tab-drop
value: true
- name: zen.splitView.enable-tab-click-split
value: true
- name: zen.splitView.min-resize-width
value: 7

View File

@@ -18,7 +18,7 @@
value: true
- name: zen.theme.acrylic-elements
value: false
value: "@IS_TWILIGHT@"
- name: zen.theme.disable-lightweight
value: true

View File

@@ -61,5 +61,8 @@
- name: zen.view.overflow-webext-toolbar
value: "@IS_TWILIGHT@"
- name: zen.view.overflow-webext-toolbar-threshold
value: 60
- name: zen.view.enable-loading-indicator
value: true

View File

@@ -17,6 +17,12 @@
- name: zen.workspaces.swipe-actions
value: true
- name: zen.workspaces.swipe-actions.delta-multiplier
value: 90
- name: zen.workspaces.switch-animation-duration
value: 200
- name: zen.workspaces.wrap-around-navigation
value: true

View File

@@ -60,4 +60,7 @@ def main():
if __name__ == "__main__":
import sys
if len(sys.argv) == 3:
_, DUMPS_FOLDER, ENGINE_DUMPS_FOLDER = sys.argv
main()

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index edeb473e46b3aa4b12eb4b59ce62e5ae48edd2a1..d2c6c8c150e732b77d65420520ca4905a9d3ea4d 100644
index edeb473e46b3aa4b12eb4b59ce62e5ae48edd2a1..99210f8bb5633d50d2cba24f1e13ca866c5b6959 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -2,7 +2,7 @@
@@ -11,7 +11,7 @@ index edeb473e46b3aa4b12eb4b59ce62e5ae48edd2a1..d2c6c8c150e732b77d65420520ca4905
<script src="chrome://browser/content/navigator-toolbox.js" />
<!-- Menu -->
@@ -18,9 +18,12 @@
@@ -18,9 +18,13 @@
#include browser-menubar.inc
</toolbaritem>
<spacer flex="1" skipintoolbarset="true" style="order: 1000;"/>
@@ -22,14 +22,14 @@ index edeb473e46b3aa4b12eb4b59ce62e5ae48edd2a1..d2c6c8c150e732b77d65420520ca4905
+ <html:div id="zen-toolbar-background" class="zen-toolbar-background zen-browser-generic-background">
+ <html:div class="zen-browser-grain" />
+ </html:div>
+ <box id="zen-overflow-extensions-list" skipintoolbarset="true" contextmenu="toolbar-context-menu" />
<toolbar id="TabsToolbar"
class="browser-toolbar browser-titlebar"
fullscreentoolbar="true"
@@ -62,6 +65,9 @@
@@ -62,6 +66,8 @@
<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"/>

View File

@@ -28,6 +28,8 @@
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-welcome.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-media-controls.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-download-box-animation.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-little-window.css" />
</linkset>
# Startup "preloaded" scripts that requre globals such as gBrowser and gURLBar

View File

@@ -9,7 +9,6 @@
#include ../../../zen/mods/jar.inc.mn
#include ../../../zen/spaces/jar.inc.mn
#include ../../../zen/tabs/jar.inc.mn
#include ../../../zen/kbs/jar.inc.mn
#include ../../../zen/glance/jar.inc.mn
#include ../../../zen/folders/jar.inc.mn
#include ../../../zen/welcome/jar.inc.mn
@@ -20,3 +19,4 @@
#include ../../../zen/fonts/jar.inc.mn
#include ../../../zen/boosts/jar.inc.mn
#include ../../../zen/live-folders/jar.inc.mn
#include ../../../zen/little-window/jar.inc.mn

View File

@@ -68,4 +68,6 @@
<command id="cmd_zenNewLiveFolder" />
<command id="cmd_zenDuplicateTab" />
<command id="cmd_zenNewLittleWindow" />
</commandset>

View File

@@ -0,0 +1,23 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
<panel id="zen-spaces-popup"
nonnativepopover="true"
type="arrow"
orient="vertical"
side="bottom"
hidden="true"
consumeoutsideclicks="never">
<hbox class="zen-spaces-list-header" flex="1">
<image class="zen-spaces-list-search-icon" src="chrome://global/skin/icons/search-glass.svg"/>
<html:input id="zen-spaces-list-search"
data-l10n-id="zen-spaces-search-placeholder"
type="search" />
</hbox>
<scrollbox class="zen-spaces-list-scrollbox" flex="1">
<vbox id="zen-spaces-list"></vbox>
<hbox id="zen-spaces-search-no-results" hidden="true" flex="1"
data-l10n-id="zen-spaces-search-no-results" />
</scrollbox>
</panel>

View File

@@ -5,6 +5,7 @@
#include zen-panels/theme-picker.inc
#include zen-panels/emojis-picker.inc
#include zen-panels/folders-search.inc
#include zen-panels/spaces-search.inc
#include zen-panels/site-data.inc
#include zen-panels/popups.inc

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/customizableui/CustomizableUI.sys.mjs b/browser/components/customizableui/CustomizableUI.sys.mjs
index db617c65b89643015d91b0f6a20eab7d7a1b598f..9acef640800bdc75f477a8e14e73f08f535e9d9e 100644
index db617c65b89643015d91b0f6a20eab7d7a1b598f..53598363bfbc2c5de4c6b4de712d22d89ee6ce1d 100644
--- a/browser/components/customizableui/CustomizableUI.sys.mjs
+++ b/browser/components/customizableui/CustomizableUI.sys.mjs
@@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
@@ -186,11 +186,34 @@ index db617c65b89643015d91b0f6a20eab7d7a1b598f..9acef640800bdc75f477a8e14e73f08f
});
lazy.log.debug(
@@ -7933,7 +7934,14 @@ class OverflowableToolbar {
@@ -7933,7 +7934,37 @@ class OverflowableToolbar {
Math.max(targetWidth, targetChildrenWidth)
);
totalAvailWidth = Math.ceil(totalAvailWidth);
- let isOverflowing = targetContentWidth > totalAvailWidth;
+ if (this.#target.id == 'nav-bar-customization-target' &&
+ win.gZenVerticalTabsManager._hasSetSingleToolbar &&
+ Services.prefs.getBoolPref("zen.view.overflow-webext-toolbar", true) &&
+ !win.gURLBar.hasAttribute("breakout-extend")) {
+ const availSelectors = ":is(#page-action-buttons, #zen-copy-url-button, .unified-extensions-item)";
+ const width = [
+ ...win.gURLBar._inputContainer.querySelectorAll(availSelectors),
+ ...win.document.getElementById("zen-overflow-extensions-list").querySelectorAll(availSelectors)
+ ].length * 26;
+ const urlbarWidth = win.document.getElementById("urlbar-container").getBoundingClientRect().width;
+ let overflowing = width > urlbarWidth * (Services.prefs.getIntPref("zen.view.overflow-webext-toolbar-threshold", 60) / 100);
+ let wasOverflowing = win.gURLBar._isOverflowingItems;
+ win.gURLBar._isOverflowingItems = overflowing;
+ if (wasOverflowing !== overflowing) {
+ const items = gPlacements.get("nav-bar");
+ for (let item of items) {
+ let [, node] = CustomizableUIInternal.getWidgetNode(item, win);
+ if (node?.hasAttribute("data-extensionid")) {
+ win.gZenVerticalTabsManager.appendCustomizableItem(win.document.getElementById("zen-sidebar-top-buttons-customization-target"), node);
+ }
+ }
+ }
+ }
+ if (win.gZenVerticalTabsManager._hasSetSingleToolbar && this.#toolbar.id == 'nav-bar') return { isOverflowing: false, targetContentWidth, totalAvailWidth };
+ let isOverflowing = targetContentWidth + (win.gZenVerticalTabsManager._hasSetSingleToolbar ? 0.1 : 0) > totalAvailWidth;
+ if (win.gZenVerticalTabsManager._hasSetSingleToolbar &&
@@ -202,7 +225,7 @@ index db617c65b89643015d91b0f6a20eab7d7a1b598f..9acef640800bdc75f477a8e14e73f08f
return { isOverflowing, targetContentWidth, totalAvailWidth };
}
@@ -7994,7 +8002,11 @@ class OverflowableToolbar {
@@ -7994,7 +8025,11 @@ class OverflowableToolbar {
return;
}
}
@@ -215,7 +238,7 @@ index db617c65b89643015d91b0f6a20eab7d7a1b598f..9acef640800bdc75f477a8e14e73f08f
lazy.log.debug(
`Need ${minSize} but width is ${totalAvailWidth} so bailing`
);
@@ -8027,7 +8039,7 @@ class OverflowableToolbar {
@@ -8027,7 +8062,7 @@ class OverflowableToolbar {
}
}
if (!inserted) {
@@ -224,7 +247,7 @@ index db617c65b89643015d91b0f6a20eab7d7a1b598f..9acef640800bdc75f477a8e14e73f08f
}
child.removeAttribute("cui-anchorid");
child.removeAttribute("overflowedItem");
@@ -8153,6 +8165,9 @@ class OverflowableToolbar {
@@ -8153,6 +8188,9 @@ class OverflowableToolbar {
* if no such list exists.
*/
get #webExtList() {
@@ -234,7 +257,7 @@ index db617c65b89643015d91b0f6a20eab7d7a1b598f..9acef640800bdc75f477a8e14e73f08f
if (!this.#webExtListRef) {
let targetID = this.#toolbar.getAttribute("addon-webext-overflowtarget");
if (!targetID) {
@@ -8164,6 +8179,9 @@ class OverflowableToolbar {
@@ -8164,6 +8202,9 @@ class OverflowableToolbar {
let win = this.#toolbar.ownerGlobal;
let { panel } = win.gUnifiedExtensions;
this.#webExtListRef = panel.querySelector(`#${targetID}`);
@@ -244,7 +267,7 @@ index db617c65b89643015d91b0f6a20eab7d7a1b598f..9acef640800bdc75f477a8e14e73f08f
}
return this.#webExtListRef;
}
@@ -8372,7 +8390,7 @@ class OverflowableToolbar {
@@ -8372,7 +8413,7 @@ class OverflowableToolbar {
break;
}
case "mousedown": {

View File

@@ -8,13 +8,16 @@ const { nsZenMultiWindowFeature } = ChromeUtils.importESModule(
{ global: "current" }
);
const { nsKeyShortcutModifiers } = ChromeUtils.importESModule(
"chrome://browser/content/zen-components/ZenKeyboardShortcuts.mjs",
{
global: "current",
}
const {
nsKeyShortcutModifiers,
ZenKeyboardShortcuts,
VALID_SHORTCUT_GROUPS,
} = ChromeUtils.importESModule(
"resource:///modules/zen/ZenKeyboardShortcuts.sys.mjs"
);
const gZenKeyboardShortcutsManager = ZenKeyboardShortcuts.manager;
var gZenMarketplaceManager = {
async init() {
const checkForUpdates = document.getElementById("zenThemeMarketplaceCheckForUpdates");

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js
index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..139fa9be7919928e5a57fda6c7fabe4bc5acf982 100644
index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..fb9ec4deb5871bc0ba57c323a413f07440e9aa42 100644
--- a/browser/components/tabbrowser/content/tab.js
+++ b/browser/components/tabbrowser/content/tab.js
@@ -21,6 +21,7 @@
@@ -151,14 +151,15 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..139fa9be7919928e5a57fda6c7fabe4b
on_click(event) {
if (event.button != 0) {
return;
@@ -617,14 +656,30 @@
@@ -617,14 +656,31 @@
trigger: "alt_click",
});
}
+ if (
+ !event.target.classList.contains("tab-close-button") &&
+ !event.target.classList.contains("tab-icon-overlay") &&
+ !event.target.classList.contains("tab-audio-button")
+ !event.target.classList.contains("tab-audio-button") &&
+ Services.prefs.getBoolPref("zen.splitView.enable-tab-click-split", false)
+ ) {
+ if (!this.splitView) {
+ gZenViewSplitter.contextSplitTabs(this);
@@ -183,7 +184,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..139fa9be7919928e5a57fda6c7fabe4b
gBrowser.multiSelectedTabsCount > 0 &&
!event.target.classList.contains("tab-close-button") &&
!event.target.classList.contains("tab-icon-overlay") &&
@@ -636,8 +691,9 @@
@@ -636,8 +692,9 @@
}
if (
@@ -195,7 +196,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..139fa9be7919928e5a57fda6c7fabe4b
) {
if (this.activeMediaBlocked) {
if (this.multiselected) {
@@ -655,7 +711,7 @@
@@ -655,7 +712,7 @@
return;
}
@@ -204,7 +205,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..139fa9be7919928e5a57fda6c7fabe4b
if (this.multiselected) {
gBrowser.removeMultiSelectedTabs(
lazy.TabMetrics.userTriggeredContext(
@@ -675,6 +731,14 @@
@@ -675,6 +732,14 @@
// (see tabbrowser-tabs 'click' handler).
gBrowser.tabContainer._blockDblClick = true;
}
@@ -219,7 +220,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..139fa9be7919928e5a57fda6c7fabe4b
}
on_dblclick(event) {
@@ -698,6 +762,8 @@
@@ -698,6 +763,8 @@
animate: true,
triggeringEvent: event,
});

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs
index 64afd613f454edd7786fcc1e2f307a582e4d5f51..50e2dd129d4f2e8f0b07e29639d660cd08ee7318 100644
index 64afd613f454edd7786fcc1e2f307a582e4d5f51..92f91379f43785cf5417c96b178884c860db6bd3 100644
--- a/browser/components/urlbar/UrlbarUtils.sys.mjs
+++ b/browser/components/urlbar/UrlbarUtils.sys.mjs
@@ -85,6 +85,8 @@ export var UrlbarUtils = {
@@ -30,7 +30,7 @@ index 64afd613f454edd7786fcc1e2f307a582e4d5f51..50e2dd129d4f2e8f0b07e29639d660cd
+ icon: "chrome://browser/skin/zen-icons/selectable/layers.svg",
+ pref: "shortcuts.workspaces",
+ telemetryLabel: "workspaces",
+ uiLabel: "urlbar-searchmode-workspaces",
+ uiLabel: "urlbar-search-mode-workspaces",
+ },
]);
},

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs
index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f5c811a82 100644
index b23244f9d3278918b016bb3fcab19687bc2e292a..e5de81b3060a1ee76b1a6aff2e4ae1ca50f2caa9 100644
--- a/browser/components/urlbar/content/UrlbarInput.mjs
+++ b/browser/components/urlbar/content/UrlbarInput.mjs
@@ -90,6 +90,13 @@ const lazy = XPCOMUtils.declareLazy({
@@ -75,7 +75,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
}
if (isCanonized) {
@@ -2696,6 +2728,42 @@ export class UrlbarInput extends HTMLElement {
@@ -2696,6 +2728,45 @@ export class UrlbarInput extends HTMLElement {
await this.#updateLayoutBreakoutDimensions();
}
@@ -84,7 +84,10 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
+ }
+
+ get zenUrlbarBehavior() {
+ if (this.document.documentElement.hasAttribute("inDOMFullscreen")) {
+ if (
+ this.document.documentElement.hasAttribute("inDOMFullscreen") ||
+ this.document.documentElement.hasAttribute("zen-little-window")
+ ) {
+ return "float";
+ }
+ return lazy.ZEN_URLBAR_BEHAVIOR;
@@ -118,7 +121,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
startLayoutExtend() {
if (!this.#allowBreakout || this.hasAttribute("breakout-extend")) {
// Do not expand if the Urlbar does not support being expanded or it is
@@ -2710,6 +2778,13 @@ export class UrlbarInput extends HTMLElement {
@@ -2710,6 +2781,13 @@ export class UrlbarInput extends HTMLElement {
this.setAttribute("breakout-extend", "true");
@@ -132,7 +135,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
// Enable the animation only after the first extend call to ensure it
// doesn't run when opening a new window.
if (!this.hasAttribute("breakout-extend-animate")) {
@@ -2729,6 +2804,27 @@ export class UrlbarInput extends HTMLElement {
@@ -2729,6 +2807,27 @@ export class UrlbarInput extends HTMLElement {
return;
}
@@ -160,7 +163,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
this.removeAttribute("breakout-extend");
this.#updateTextboxPosition();
}
@@ -2759,7 +2855,7 @@ export class UrlbarInput extends HTMLElement {
@@ -2759,7 +2858,7 @@ export class UrlbarInput extends HTMLElement {
forceUnifiedSearchButtonAvailable = false
) {
let prevState = this.getAttribute("pageproxystate");
@@ -169,7 +172,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
this.setAttribute("pageproxystate", state);
this._inputContainer.setAttribute("pageproxystate", state);
this._identityBox?.setAttribute("pageproxystate", state);
@@ -3031,10 +3127,12 @@ export class UrlbarInput extends HTMLElement {
@@ -3031,10 +3130,12 @@ export class UrlbarInput extends HTMLElement {
return;
}
this.style.top = px(
@@ -182,7 +185,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
);
}
@@ -3093,9 +3191,10 @@ export class UrlbarInput extends HTMLElement {
@@ -3093,9 +3194,10 @@ export class UrlbarInput extends HTMLElement {
return;
}
@@ -194,7 +197,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
);
this.style.setProperty(
"--urlbar-height",
@@ -3597,6 +3696,7 @@ export class UrlbarInput extends HTMLElement {
@@ -3597,6 +3699,7 @@ export class UrlbarInput extends HTMLElement {
}
_toggleActionOverride(event) {
@@ -202,7 +205,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
if (
event.keyCode == KeyEvent.DOM_VK_SHIFT ||
event.keyCode == KeyEvent.DOM_VK_ALT ||
@@ -3709,8 +3809,8 @@ export class UrlbarInput extends HTMLElement {
@@ -3709,8 +3812,8 @@ export class UrlbarInput extends HTMLElement {
if (!this.#isAddressbar) {
return val;
}
@@ -213,7 +216,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
: val;
// Only trim value if the directionality doesn't change to RTL and we're not
// showing a strikeout https protocol.
@@ -4006,6 +4106,7 @@ export class UrlbarInput extends HTMLElement {
@@ -4006,6 +4109,7 @@ export class UrlbarInput extends HTMLElement {
resultDetails = null,
browser = this.window.gBrowser.selectedBrowser
) {
@@ -221,7 +224,19 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
if (this.#isAddressbar) {
this.#prepareAddressbarLoad(
url,
@@ -4117,6 +4218,10 @@ export class UrlbarInput extends HTMLElement {
@@ -4088,6 +4192,11 @@ export class UrlbarInput extends HTMLElement {
* @returns {"current" | "tabshifted" | "tab" | "save" | "window"}
*/
_whereToOpen(event) {
+ if (this.document.documentElement.hasAttribute("zen-little-window")) {
+ // Little windows are single-tab popups -- never spawn extra tabs
+ // or new windows from the urlbar.
+ return "current";
+ }
let isKeyboardEvent = KeyboardEvent.isInstance(event);
let reuseEmpty = isKeyboardEvent;
let where = undefined;
@@ -4117,6 +4226,10 @@ export class UrlbarInput extends HTMLElement {
}
reuseEmpty = true;
}
@@ -232,7 +247,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
if (
where == "tab" &&
reuseEmpty &&
@@ -4124,6 +4229,9 @@ export class UrlbarInput extends HTMLElement {
@@ -4124,6 +4237,9 @@ export class UrlbarInput extends HTMLElement {
) {
where = "current";
}
@@ -242,7 +257,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
return where;
}
@@ -4378,6 +4486,7 @@ export class UrlbarInput extends HTMLElement {
@@ -4378,6 +4494,7 @@ export class UrlbarInput extends HTMLElement {
this.setResultForCurrentValue(null);
this.handleCommand();
this.controller.clearLastQueryContextCache();
@@ -250,7 +265,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
this._suppressStartQuery = false;
});
@@ -4385,7 +4494,6 @@ export class UrlbarInput extends HTMLElement {
@@ -4385,7 +4502,6 @@ export class UrlbarInput extends HTMLElement {
contextMenu.addEventListener("popupshowing", () => {
// Close the results pane when the input field contextual menu is open,
// because paste and go doesn't want a result selection.
@@ -258,7 +273,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
let controller =
this.document.commandDispatcher.getControllerForCommand("cmd_paste");
@@ -4541,7 +4649,11 @@ export class UrlbarInput extends HTMLElement {
@@ -4541,7 +4657,11 @@ export class UrlbarInput extends HTMLElement {
if (!engineName && !source && !this.hasAttribute("searchmode")) {
return;
}
@@ -271,7 +286,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
if (this._searchModeIndicatorTitle) {
this._searchModeIndicatorTitle.textContent = "";
this._searchModeIndicatorTitle.removeAttribute("data-l10n-id");
@@ -4851,6 +4963,7 @@ export class UrlbarInput extends HTMLElement {
@@ -4851,6 +4971,7 @@ export class UrlbarInput extends HTMLElement {
this.document.l10n.setAttributes(
this.inputField,
@@ -279,7 +294,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
l10nId,
l10nId == "urlbar-placeholder-with-name"
? { name: engineName }
@@ -4964,6 +5077,11 @@ export class UrlbarInput extends HTMLElement {
@@ -4964,6 +5085,11 @@ export class UrlbarInput extends HTMLElement {
}
_on_click(event) {
@@ -291,7 +306,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
switch (event.target) {
case this.inputField:
case this._inputContainer:
@@ -5042,7 +5160,7 @@ export class UrlbarInput extends HTMLElement {
@@ -5042,7 +5168,7 @@ export class UrlbarInput extends HTMLElement {
}
}
@@ -300,7 +315,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
this.view.autoOpen({ event });
} else {
if (this._untrimOnFocusAfterKeydown) {
@@ -5082,9 +5200,16 @@ export class UrlbarInput extends HTMLElement {
@@ -5082,9 +5208,16 @@ export class UrlbarInput extends HTMLElement {
}
_on_mousedown(event) {
@@ -318,7 +333,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
if (
event.composedTarget != this.inputField &&
event.composedTarget != this._inputContainer
@@ -5094,6 +5219,10 @@ export class UrlbarInput extends HTMLElement {
@@ -5094,6 +5227,10 @@ export class UrlbarInput extends HTMLElement {
this.focusedViaMousedown = !this.focused;
this._preventClickSelectsAll = this.focused;
@@ -329,7 +344,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
// Keep the focus status, since the attribute may be changed
// upon calling this.focus().
@@ -5129,7 +5258,7 @@ export class UrlbarInput extends HTMLElement {
@@ -5129,7 +5266,7 @@ export class UrlbarInput extends HTMLElement {
}
// Don't close the view when clicking on a tab; we may want to keep the
// view open on tab switch, and the TabSelect event arrived earlier.
@@ -338,7 +353,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
break;
}
@@ -5411,7 +5540,7 @@ export class UrlbarInput extends HTMLElement {
@@ -5411,7 +5548,7 @@ export class UrlbarInput extends HTMLElement {
// When we are in actions search mode we can show more results so
// increase the limit.
let maxResults =

View File

@@ -0,0 +1,14 @@
diff --git a/browser/components/urlbar/content/enUS-searchFeatures.ftl b/browser/components/urlbar/content/enUS-searchFeatures.ftl
index a1fb86058d1f6f015160163f75999b4a429bf1fd..83098291e21a8239513a4105a4604394a1dfabc0 100644
--- a/browser/components/urlbar/content/enUS-searchFeatures.ftl
+++ b/browser/components/urlbar/content/enUS-searchFeatures.ftl
@@ -2,6 +2,9 @@
# 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/.
+urlbar-search-mode-workspaces = Spaces
+urlbar-search-mode-workspaces-en = Spaces
+
### These strings are related to the Firefox Suggest feature. Firefox Suggest
### shows recommended and sponsored third-party results in the address bar
### panel. It also shows headings/labels above different groups of results. For

View File

@@ -1,22 +1,37 @@
diff --git a/browser/modules/BrowserWindowTracker.sys.mjs b/browser/modules/BrowserWindowTracker.sys.mjs
index 9aecab66d8f23fac9f16cea2120a5fe903ae1122..692f2bfe3899a58925789503a6bb2a547cdbf7f3 100644
index 9aecab66d8f23fac9f16cea2120a5fe903ae1122..e023c27bcb027d29ba9b3469eca5957d42040c46 100644
--- a/browser/modules/BrowserWindowTracker.sys.mjs
+++ b/browser/modules/BrowserWindowTracker.sys.mjs
@@ -330,6 +330,7 @@ export const BrowserWindowTracker = {
@@ -210,7 +210,8 @@ export const BrowserWindowTracker = {
!win.closed &&
(options.allowPopups || win.toolbar.visible) &&
(options.allowTaskbarTabs ||
- !win.document.documentElement.hasAttribute("taskbartab")) &&
+ (!win.document.documentElement.hasAttribute("taskbartab") &&
+ !win.document.documentElement.hasAttribute("zen-little-window"))) &&
(!("private" in options) ||
lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
lazy.PrivateBrowsingUtils.isWindowPrivate(win) == options.private)
@@ -330,6 +331,8 @@ export const BrowserWindowTracker = {
args = null,
remote = undefined,
fission = undefined,
+ zenSyncedWindow = true,
+ zenLittleWindow = false,
} = options;
args = lazy.AIWindow.handleAIWindowOptions(options);
@@ -386,6 +387,12 @@ export const BrowserWindowTracker = {
@@ -386,6 +389,16 @@ export const BrowserWindowTracker = {
windowFeatures,
args
);
+ win._zenStartupSyncFlag = Services.prefs.getBoolPref("zen.window-sync.prefer-unsynced-windows")
+ ? (zenSyncedWindow ? 'unsynced' : 'synced')
+ : (zenSyncedWindow ? 'synced' : 'unsynced');
+ if (zenLittleWindow) {
+ win._zenStartupLittleWindow = true;
+ win._zenStartupSyncFlag = 'unsynced';
+ }
+ if (win._zenStartupSyncFlag === 'unsynced' && openerWindow) {
+ win._zenStartupUnsyncedUserContextId = openerWindow.gZenWorkspaces.getCurrentSpaceContainerId();
+ }

View File

@@ -1,5 +1,5 @@
diff --git a/browser/modules/URILoadingHelper.sys.mjs b/browser/modules/URILoadingHelper.sys.mjs
index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee91598080730a 100644
index a005dbdf84609622ef8054f73f78c0c290e76125..2d347ac12d53ae97b61750d421a489ce10af3376 100644
--- a/browser/modules/URILoadingHelper.sys.mjs
+++ b/browser/modules/URILoadingHelper.sys.mjs
@@ -224,6 +224,7 @@ function openInWindow(url, params, sourceWindow) {
@@ -19,7 +19,17 @@ index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee9159
where = "tab";
targetBrowser = null;
} else if (
@@ -974,7 +975,7 @@ export const URILoadingHelper = {
@@ -724,7 +725,8 @@ export const URILoadingHelper = {
"navigator:browser" &&
(!skipPopups || top.toolbar.visible) &&
(!skipTaskbarTabs ||
- !top.document.documentElement.hasAttribute("taskbartab")) &&
+ (!top.document.documentElement.hasAttribute("taskbartab") &&
+ !top.document.documentElement.hasAttribute("zen-little-window"))) &&
(!forceNonPrivate || !PrivateBrowsingUtils.isWindowPrivate(top))
) {
return top;
@@ -974,7 +976,7 @@ export const URILoadingHelper = {
ignoreQueryString || replaceQueryString,
ignoreFragmentWhenComparing
);
@@ -28,7 +38,7 @@ index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee9159
for (let i = 0; i < browsers.length; i++) {
let browser = browsers[i];
let browserCompare = cleanURL(
@@ -1030,7 +1031,7 @@ export const URILoadingHelper = {
@@ -1030,7 +1032,7 @@ export const URILoadingHelper = {
);
aSplitView.ownerGlobal.focus();
} else {

View File

@@ -0,0 +1,172 @@
diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -298,11 +298,13 @@
panic_on_gl_error, picTileWidth, picTileHeight,
gfx::gfxVars::WebRenderRequiresHardwareDriver(),
StaticPrefs::gfx_webrender_low_quality_pinch_zoom_AtStartup(),
StaticPrefs::gfx_webrender_max_shared_surface_size_AtStartup(),
StaticPrefs::gfx_webrender_enable_subpixel_aa_AtStartup(),
- compositor->ShouldUseLayerCompositor())) {
+ compositor->ShouldUseLayerCompositor(),
+ StaticPrefs::
+ gfx_webrender_opaque_backdrop_fallback_AtStartup())) {
// wr_window_new puts a message into gfxCriticalNote if it returns
// false
MOZ_ASSERT(errorMessage);
error.AssignASCII(errorMessage);
wr_api_free_error_msg(errorMessage);
diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1998,10 +1998,11 @@
reject_software_rasterizer: bool,
low_quality_pinch_zoom: bool,
max_shared_surface_size: i32,
enable_subpixel_aa: bool,
use_layer_compositor: bool,
+ opaque_backdrop_fallback: bool,
) -> bool {
assert!(unsafe { is_in_render_thread() });
// Ensure the WR profiler callbacks are hooked up to the Gecko profiler.
set_profiler_hooks(Some(&PROFILER_HOOKS));
@@ -2164,10 +2165,11 @@
texture_cache_config,
reject_software_rasterizer,
low_quality_pinch_zoom,
max_shared_surface_size,
enable_dithering,
+ opaque_backdrop_fallback,
precise_linear_gradients,
..Default::default()
};
let window_size = DeviceIntSize::new(window_width, window_height);
diff --git a/gfx/wr/webrender/src/device/gl.rs b/gfx/wr/webrender/src/device/gl.rs
--- a/gfx/wr/webrender/src/device/gl.rs
+++ b/gfx/wr/webrender/src/device/gl.rs
@@ -3982,10 +3982,14 @@
pub fn disable_color_write(&self) {
self.gl.color_mask(false, false, false, false);
}
+ pub fn set_color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
+ self.gl.color_mask(r, g, b, a);
+ }
+
pub fn set_blend(&mut self, enable: bool) {
if enable {
self.gl.enable(gl::BLEND);
} else {
self.gl.disable(gl::BLEND);
diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/renderer/init.rs
--- a/gfx/wr/webrender/src/renderer/init.rs
+++ b/gfx/wr/webrender/src/renderer/init.rs
@@ -204,10 +204,12 @@
pub low_quality_pinch_zoom: bool,
pub max_shared_surface_size: i32,
/// If true, open a debug socket to listen for remote debugger.
/// Relies on `debugger` cargo feature being enabled.
pub enable_debugger: bool,
+ /// See explanation of `gfx.webrender.opaque-backdrop-fallback`.
+ pub opaque_backdrop_fallback: bool,
/// Use a more precise method for sampling gradients.
pub precise_linear_gradients: bool,
}
@@ -277,10 +279,11 @@
enable_instancing: true,
reject_software_rasterizer: false,
low_quality_pinch_zoom: false,
max_shared_surface_size: 2048,
enable_debugger: true,
+ opaque_backdrop_fallback: false,
precise_linear_gradients: false,
}
}
}
@@ -802,10 +805,11 @@
allocated_native_surfaces: FastHashSet::default(),
debug_overlay_state: DebugOverlayState::new(),
buffer_damage_tracker: BufferDamageTracker::default(),
max_primitive_instance_count,
enable_instancing: options.enable_instancing,
+ opaque_backdrop_fallback: options.opaque_backdrop_fallback,
consecutive_oom_frames: 0,
target_frame_publish_id: None,
pending_result_msg: None,
layer_compositor_frame_state_in_prev_frame: None,
external_composite_debug_items: Vec::new(),
diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
--- a/gfx/wr/webrender/src/renderer/mod.rs
+++ b/gfx/wr/webrender/src/renderer/mod.rs
@@ -867,10 +867,12 @@
buffer_damage_tracker: BufferDamageTracker,
max_primitive_instance_count: usize,
enable_instancing: bool,
+ opaque_backdrop_fallback: bool,
+
/// Count consecutive oom frames to detectif we are stuck unable to render
/// in a loop.
consecutive_oom_frames: u32,
/// update() defers processing of ResultMsg, if frame_publish_id of
@@ -2787,18 +2789,29 @@
let read_target = ReadTarget::from_texture(cache_texture);
// Should always be drawing to picture cache tiles or off-screen surface!
debug_assert!(!draw_target.is_default());
let device_to_framebuffer = Scale::new(1i32);
+ let dest_fb_rect = dest * device_to_framebuffer;
self.device.blit_render_target(
read_target,
src * device_to_framebuffer,
draw_target,
- dest * device_to_framebuffer,
+ dest_fb_rect,
TextureFilter::Linear,
);
+
+ if self.opaque_backdrop_fallback {
+ self.device.set_color_mask(false, false, false, true);
+ self.device.clear_target(
+ Some([0.0, 0.0, 0.0, 1.0]),
+ None,
+ Some(dest_fb_rect),
+ );
+ self.device.set_color_mask(true, true, true, true);
+ }
}
}
}
fn draw_picture_cache_target(
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -8439,10 +8439,17 @@
#else
value: false
#endif
mirror: once
+# Make backdrop-filter treat its captured backdrop as if it had been
+# composited over an opaque-black background. (See bug 2036640)
+- name: gfx.webrender.opaque-backdrop-fallback
+ type: bool
+ value: true
+ mirror: once
+
# Disable wait of GPU execution completion
- name: gfx.webrender.wait-gpu-finished.disabled
type: bool
value: false
mirror: once

View File

@@ -1,7 +1,7 @@
diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml
--- a/browser/base/content/main-popupset.inc.xhtml
+++ b/browser/base/content/main-popupset.inc.xhtml
@@ -196,10 +196,11 @@
@@ -192,10 +192,11 @@
<!-- Starting point for selection actions -->
<panel class="panel-no-padding"
id="selection-shortcut-action-panel"
@@ -13,7 +13,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content
<html:moz-button id="ai-action-button"/>
</hbox>
</panel>
@@ -207,10 +208,11 @@
@@ -203,10 +204,11 @@
<!-- Shortcut options for Gen AI action -->
<panel class="panel-no-padding"
@@ -25,7 +25,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content
</panel>
<html:template id="screenshotsPagePanelTemplate">
@@ -560,10 +562,11 @@
@@ -610,10 +612,11 @@
type="arrow"
orient="vertical"
noautofocus="true"
@@ -40,7 +40,7 @@ diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content
diff --git a/browser/components/asrouter/modules/FeatureCallout.sys.mjs b/browser/components/asrouter/modules/FeatureCallout.sys.mjs
--- a/browser/components/asrouter/modules/FeatureCallout.sys.mjs
+++ b/browser/components/asrouter/modules/FeatureCallout.sys.mjs
@@ -1046,10 +1046,11 @@
@@ -1054,10 +1054,11 @@
noautofocus="true"
flip="slide"
type="arrow"
@@ -70,22 +70,22 @@ diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/brows
diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
--- a/dom/xul/XULPopupElement.cpp
+++ b/dom/xul/XULPopupElement.cpp
@@ -82,10 +82,14 @@
@@ -80,10 +80,14 @@
}
void XULPopupElement::OpenPopupAtScreen(int32_t aXPos, int32_t aYPos,
bool aIsContextMenu,
Event* aTriggerEvent) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ // TODO(cheff): At nsCocoaWindow::Show but we check for ShouldShowAsNSPopover
+ // to determine whether to use a native popover or not. This should sort of
+ // "replicate" that logic here, but it's a bit of a hacky way.
+ SetAttr(kNameSpaceID_None, nsGkAtoms::nonnativepopover, u"true"_ns, true);
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
pm->ShowPopupAtScreen(this, aXPos, aYPos, aIsContextMenu, aTriggerEvent);
}
}
@@ -94,10 +98,14 @@
@@ -93,10 +97,14 @@
int32_t aWidth, int32_t aHeight,
bool aIsContextMenu,
bool aAttributesOverride,
@@ -103,7 +103,7 @@ diff --git a/dom/xul/XULPopupElement.cpp b/dom/xul/XULPopupElement.cpp
diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -530,18 +530,10 @@
@@ -528,18 +528,10 @@
// Move the popup to the position specified in its |left| and |top|
// attributes.
@@ -122,7 +122,7 @@ diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
public:
/**
* Return whether the popup direction should be RTL.
@@ -550,10 +542,18 @@
@@ -548,10 +540,18 @@
*
* Return whether the popup direction should be RTL.
*/
@@ -144,7 +144,7 @@ diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -19477,10 +19477,19 @@
@@ -19672,10 +19672,19 @@
value: true
mirror: always
@@ -167,7 +167,7 @@ diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/Sta
diff --git a/toolkit/themes/shared/global-shared.css b/toolkit/themes/shared/global-shared.css
--- a/toolkit/themes/shared/global-shared.css
+++ b/toolkit/themes/shared/global-shared.css
@@ -85,10 +85,22 @@
@@ -72,10 +72,22 @@
--menuitem-border-radius: var(--arrowpanel-menuitem-border-radius);
--menuitem-padding: var(--arrowpanel-menuitem-padding);
--menuitem-margin: var(--arrowpanel-menuitem-margin);
@@ -251,7 +251,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -3,10 +3,13 @@
@@ -4,10 +4,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCocoaWindow.h"
@@ -265,7 +265,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
#include "nsIDOMWindowUtils.h"
#include "nsILocalFileMac.h"
#include "CocoaCompositorWidget.h"
@@ -4973,10 +4976,15 @@
@@ -5031,10 +5034,15 @@
if (mWindowType == WindowType::Popup) {
SetPopupWindowLevel();
mWindow.backgroundColor = NSColor.clearColor;
@@ -281,7 +281,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// the active space. Does not work with multiple displays. See
// NeedsRecreateToReshow() for multi-display with multi-space workaround.
mWindow.collectionBehavior = mWindow.collectionBehavior |
@@ -5178,10 +5186,57 @@
@@ -5236,10 +5244,57 @@
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
@@ -339,54 +339,58 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
if (!mWindow) {
@@ -5242,10 +5297,54 @@
@@ -5300,10 +5355,58 @@
mWindow.contentView.needsDisplay = YES;
if (!nativeParentWindow || mPopupLevel != PopupLevel::Parent) {
[mWindow orderFront:nil];
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
+ if (ShouldShowAsNSPopover()) {
+ if (ShouldShowAsNSPopover() && nativeParentWindow) {
+ nsMenuPopupFrame* popupFrame = GetPopupFrame();
+ if (nativeParentWindow) {
+ NSRectEdge preferredEdge =
+ AlignmentPositionToNSRectEdge(popupFrame->GetAlignmentPosition());
+ nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
+ nsPresContext* pc = popupFrame->PresContext();
+ int32_t appUnitsPerDevPixel = pc->AppUnitsPerDevPixel();
+ mozilla::DesktopToLayoutDeviceScale desktopToLayoutScale =
+ pc->DeviceContext()->GetDesktopToDeviceScale();
+ mozilla::DesktopIntRect popupAnchorRectScaled =
+ mozilla::DesktopIntRect::RoundOut(
+ mozilla::LayoutDeviceRect::FromAppUnits(anchorRectAppUnits,
+ appUnitsPerDevPixel) /
+ desktopToLayoutScale);
+ // Taking the now correctly scaled anchor rect and turning it into a
+ // gecko rect this accounts for the y-axis inversion that cocoa needs,
+ // as the origin is in the bottom left. This rect is in screen space
+ NSRect cocoaScreenRect =
+ nsCocoaUtils::GeckoRectToCocoaRect(popupAnchorRectScaled);
+ // We take the screen space rect and convert it to window space
+ // coordinates, as NSPopover requires the coordinates to be in view
+ // space and inside the view. If the coordinates are outside our view,
+ // the popover will fail silently
+ NSRect windowRect =
+ [nativeParentWindow convertRectFromScreen:cocoaScreenRect];
+ NSView* parentView = [nativeParentWindow contentView];
+ // We take the window space rect and convert it to view space for the
+ // specific parent view
+ NSRect positioningRect = [parentView convertRect:windowRect
+ fromView:nil];
+ BOOL shouldHideAnchor = NO;
+ auto& element = popupFrame->PopupElement();
+ if (element.GetBoolAttr(nsGkAtoms::hidepopovertail)) {
+ shouldHideAnchor = YES;
+ }
+ [(PopupWindow*)mWindow showPopoverRelativeToRect:positioningRect
+ ofView:parentView
+ preferredEdge:preferredEdge
+ hiddenAnchor:shouldHideAnchor];
+ SyncPopoverBounds([(PopupWindow*)mWindow popover], popupFrame);
+ NSRectEdge preferredEdge =
+ AlignmentPositionToNSRectEdge(popupFrame->GetAlignmentPosition());
+ nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
+ nsPresContext* pc = popupFrame->PresContext();
+ int32_t appUnitsPerDevPixel = pc->AppUnitsPerDevPixel();
+ mozilla::DesktopToLayoutDeviceScale desktopToLayoutScale =
+ pc->DeviceContext()->GetDesktopToDeviceScale();
+ mozilla::DesktopIntRect popupAnchorRectScaled =
+ mozilla::DesktopIntRect::RoundOut(
+ mozilla::LayoutDeviceRect::FromAppUnits(anchorRectAppUnits,
+ appUnitsPerDevPixel) /
+ desktopToLayoutScale);
+ // Taking the now correctly scaled anchor rect and turning it into a
+ // gecko rect this accounts for the y-axis inversion that cocoa needs,
+ // as the origin is in the bottom left. This rect is in screen space
+ NSRect cocoaScreenRect =
+ nsCocoaUtils::GeckoRectToCocoaRect(popupAnchorRectScaled);
+ // We take the screen space rect and convert it to window space
+ // coordinates, as NSPopover requires the coordinates to be in view
+ // space and inside the view. If the coordinates are outside our view,
+ // the popover will fail silently
+ NSRect windowRect =
+ [nativeParentWindow convertRectFromScreen:cocoaScreenRect];
+ NSView* parentView = [nativeParentWindow contentView];
+ // We take the window space rect and convert it to view space for the
+ // specific parent view
+ NSRect positioningRect = [parentView convertRect:windowRect
+ fromView:nil];
+ BOOL shouldHideAnchor = NO;
+ auto& element = popupFrame->PopupElement();
+ if (element.GetBoolAttr(nsGkAtoms::hidepopovertail)) {
+ shouldHideAnchor = YES;
+ }
+ [(PopupWindow*)mWindow showPopoverRelativeToRect:positioningRect
+ ofView:parentView
+ preferredEdge:preferredEdge
+ hiddenAnchor:shouldHideAnchor];
+ SyncPopoverBounds([(PopupWindow*)mWindow popover], popupFrame);
+ if (mPopupLevel == PopupLevel::Parent) {
+ [nativeParentWindow addChildWindow:mWindow ordered:NSWindowAbove];
+ }
+
+ // Exit early here since the popover is now shown.
+ mWindow.isBeingShown = NO;
+ return;
+ }
// If our popup window is a non-native context menu, tell the OS (and
@@ -394,12 +398,13 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// close other programs' context menus when ours open.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*)mWindow isContextMenu]) {
@@ -5316,10 +5415,15 @@
@@ -5373,11 +5476,15 @@
// unhook it here before ordering it out. When you order out the child
// of a window it hides the parent window.
if (mWindowType == WindowType::Popup && nativeParentWindow) {
[nativeParentWindow removeChildWindow:mWindow];
}
-
+ // Handle NSPopover hiding or traditional window hiding
+ if ([mWindow isKindOfClass:[PopupWindow class]] &&
+ [(PopupWindow*)mWindow usePopover]) {
@@ -410,7 +415,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// other programs) that a menu has closed.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*)mWindow isContextMenu]) {
@@ -5366,10 +5470,28 @@
@@ -5424,10 +5531,28 @@
return false;
}
return nsIWidget::ShouldUseOffMainThreadCompositing();
@@ -439,12 +444,12 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
return mWindow.isOpaque ? TransparencyMode::Opaque
: TransparencyMode::Transparent;
@@ -6328,10 +6450,20 @@
@@ -6378,10 +6503,19 @@
// We ignore aRepaint -- we have to call display:YES, otherwise the
// title bar doesn't immediately get repainted and is displayed in
// the wrong place, leading to a visual jump.
[mWindow setFrame:newFrame display:YES];
+ if (ShouldUseNSPopover() && [(PopupWindow*)mWindow usePopover]) {
+ [(PopupWindow*)mWindow updatePopoverContent];
+ // A popover won't resize by setting the frame
@@ -454,18 +459,18 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
+ [[(PopupWindow*)mWindow popover] setContentSize:contentSize];
+ SyncPopoverBounds([(PopupWindow*)mWindow popover], GetPopupFrame());
+ }
+
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
void nsCocoaWindow::Resize(const DesktopRect& aRect, bool aRepaint) {
DoResize(aRect.x, aRect.y, aRect.width, aRect.height, aRepaint, false);
@@ -8314,17 +8446,26 @@
@@ -8393,18 +8527,31 @@
backing:bufferingType
defer:deferCreation];
if (!self) {
return nil;
}
-
+ mPopover = nil;
+ mPopoverViewController = nil;
+ mUsePopover = NO;
@@ -477,6 +482,11 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
}
+- (void)dealloc {
+ if (mPopover) {
+ ChildViewMouseTracker::OnDestroyWindow(
+ mPopover.contentViewController.view.window);
+ }
+
+ [mPopover release];
+ [mPopoverViewController release];
+ [super dealloc];
@@ -487,7 +497,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
// Return 0 in order to match what the system does for sheet windows and
// _NSPopoverWindows.
- (CGFloat)_backdropBleedAmount {
@@ -8378,10 +8519,122 @@
@@ -8460,10 +8607,122 @@
- (void)setIsContextMenu:(BOOL)flag {
mIsContextMenu = flag;
@@ -613,7 +623,7 @@ diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -843,10 +843,15 @@
@@ -829,10 +829,15 @@
virtual void SuppressAnimation(bool aSuppress) {}
/** Sets windows-specific mica backdrop on this widget. */
@@ -644,7 +654,7 @@ diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
Atom("highest", "highest"),
Atom("horizontal", "horizontal"),
Atom("hover", "hover"),
@@ -757,10 +758,11 @@
@@ -759,10 +760,11 @@
Atom("nohref", "nohref"),
Atom("noinitialselection", "noinitialselection"),
Atom("nomodule", "nomodule"),

View File

@@ -1,251 +0,0 @@
diff --git a/dom/chrome-webidl/IOUtils.webidl b/dom/chrome-webidl/IOUtils.webidl
--- a/dom/chrome-webidl/IOUtils.webidl
+++ b/dom/chrome-webidl/IOUtils.webidl
@@ -94,23 +94,23 @@
* otherwise rejects with a DOMException.
*/
[NewObject]
Promise<unsigned long long> writeUTF8(DOMString path, UTF8String string, optional WriteOptions options = {});
/**
- * Attempts to serialize |value| into a JSON string and encode it as into a
- * UTF-8 string, then safely write the result to a file at |path|. Works
- * exactly like |write|.
+ * Attempts to serialize |value| into a JSON string and encode it as a UTF-8
+ * string, then safely write the result to a file at |path|. Works exactly
+ * like |write|.
*
* @param path An absolute file path
* @param value The value to be serialized.
* @param options Options for writing the file. The "append" mode is not supported.
*
* @return Resolves with the number of bytes successfully written to the file,
* otherwise rejects with a DOMException.
*/
[NewObject]
- Promise<unsigned long long> writeJSON(DOMString path, any value, optional WriteOptions options = {});
+ Promise<WriteJSONResult> writeJSON(DOMString path, any value, optional WriteJSONOptions options = {});
/**
* Moves the file from |sourcePath| to |destPath|, creating necessary parents.
* If |destPath| is a directory, then the source file will be moved into the
* destination directory.
*
@@ -567,10 +567,39 @@
* If true, compress the data with LZ4-encoding before writing to the file.
*/
boolean compress = false;
};
+/**
+ * Options to be passed to the |IOUtils.writeJSON| method.
+ */
+dictionary WriteJSONOptions: WriteOptions {
+ /**
+ * An optional length hint that will be used to pre-allocate the buffer that
+ * will hold the stringified JSON.
+ *
+ * This is the *length* and not the size (i.e., it is the number of UTF-16
+ * codepoints and not the number of bytes).
+ */
+ unsigned long long lengthHint = 0;
+};
+
+/**
+ * Information about a WriteJSON operation.
+ */
+dictionary WriteJSONResult {
+ /**
+ * The number of bytes written.
+ */
+ required unsigned long long size;
+
+ /**
+ * The length of the stringified JSON (in UTF-16 codepoints).
+ */
+ required unsigned long long jsonLength;
+};
+
/**
* Options to be passed to the |IOUtils.move| method.
*/
dictionary MoveOptions {
/**
diff --git a/xpcom/ioutils/IOUtils.h b/xpcom/ioutils/IOUtils.h
--- a/xpcom/ioutils/IOUtils.h
+++ b/xpcom/ioutils/IOUtils.h
@@ -94,11 +94,11 @@
const nsACString& aString, const dom::WriteOptions& aOptions,
ErrorResult& aError);
static already_AddRefed<dom::Promise> WriteJSON(
dom::GlobalObject& aGlobal, const nsAString& aPath,
- JS::Handle<JS::Value> aValue, const dom::WriteOptions& aOptions,
+ JS::Handle<JS::Value> aValue, const dom::WriteJSONOptions& aOptions,
ErrorResult& aError);
static already_AddRefed<dom::Promise> Move(dom::GlobalObject& aGlobal,
const nsAString& aSourcePath,
const nsAString& aDestPath,
@@ -736,13 +736,16 @@
RefPtr<nsIFile> mBackupFile;
RefPtr<nsIFile> mTmpFile;
dom::WriteMode mMode;
bool mFlush = false;
bool mCompress = false;
+ size_t mLengthHint = 0;
static Result<InternalWriteOpts, IOUtils::IOError> FromBinding(
const dom::WriteOptions& aOptions);
+ static Result<InternalWriteOpts, IOUtils::IOError> FromBinding(
+ const dom::WriteJSONOptions& aOptions);
};
/**
* Re-implements the file compression and decompression utilities found
* in toolkit/components/lz4/lz4.js
diff --git a/xpcom/ioutils/IOUtils.cpp b/xpcom/ioutils/IOUtils.cpp
--- a/xpcom/ioutils/IOUtils.cpp
+++ b/xpcom/ioutils/IOUtils.cpp
@@ -589,15 +589,21 @@
return WriteSync(file, AsBytes(Span(str)), opts);
});
});
}
+static bool AppendJSON(const char16_t* aBuf, uint32_t aLen, void* aStr) {
+ nsAString* str = static_cast<nsAString*>(aStr);
+
+ return str->Append(aBuf, aLen, fallible);
+}
+
/* static */
already_AddRefed<Promise> IOUtils::WriteJSON(GlobalObject& aGlobal,
const nsAString& aPath,
JS::Handle<JS::Value> aValue,
- const WriteOptions& aOptions,
+ const WriteJSONOptions& aOptions,
ErrorResult& aError) {
return WithPromiseAndState(
aGlobal, aError, [&](Promise* promise, auto& state) {
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aPath, promise,
@@ -623,14 +629,15 @@
file->HumanReadablePath().get()));
return;
}
JSContext* cx = aGlobal.Context();
- JS::Rooted<JS::Value> rootedValue(cx, aValue);
+ JS::Rooted<JS::Value> value(cx, aValue);
nsString string;
- if (!nsContentUtils::StringifyJSON(cx, aValue, string,
- UndefinedIsNullStringLiteral)) {
+ if (!JS_StringifyWithLengthHint(cx, &value, nullptr,
+ JS::NullHandleValue, AppendJSON,
+ &string, opts.mLengthHint)) {
JS::Rooted<JS::Value> exn(cx, JS::UndefinedValue());
if (JS_GetPendingException(cx, &exn)) {
JS_ClearPendingException(cx);
promise->MaybeReject(exn);
} else {
@@ -639,22 +646,29 @@
"Could not serialize object to JSON"_ns));
}
return;
}
- DispatchAndResolve<uint32_t>(
+ DispatchAndResolve<dom::WriteJSONResult>(
state->mEventQueue, promise,
[file = std::move(file), string = std::move(string),
- opts = std::move(opts)]() -> Result<uint32_t, IOError> {
+ opts = std::move(opts)]() -> Result<WriteJSONResult, IOError> {
nsAutoCString utf8Str;
if (!CopyUTF16toUTF8(string, utf8Str, fallible)) {
return Err(IOError(
NS_ERROR_OUT_OF_MEMORY,
"Failed to write to `%s': could not allocate buffer",
file->HumanReadablePath().get()));
}
- return WriteSync(file, AsBytes(Span(utf8Str)), opts);
+
+ uint32_t size =
+ MOZ_TRY(WriteSync(file, AsBytes(Span(utf8Str)), opts));
+
+ dom::WriteJSONResult result;
+ result.mSize = size;
+ result.mJsonLength = static_cast<uint32_t>(string.Length());
+ return result;
});
});
}
/* static */
@@ -2840,10 +2854,20 @@
opts.mCompress = aOptions.mCompress;
return opts;
}
+Result<IOUtils::InternalWriteOpts, IOUtils::IOError>
+IOUtils::InternalWriteOpts::FromBinding(const WriteJSONOptions& aOptions) {
+ InternalWriteOpts opts =
+ MOZ_TRY(FromBinding(static_cast<const WriteOptions&>(aOptions)));
+
+ opts.mLengthHint = aOptions.mLengthHint;
+
+ return opts;
+}
+
/* static */
Result<IOUtils::JsBuffer, IOUtils::IOError> IOUtils::JsBuffer::Create(
IOUtils::BufferKind aBufferKind, size_t aCapacity) {
JsBuffer buffer(aBufferKind, aCapacity);
if (aCapacity != 0 && !buffer.mBuffer) {
diff --git a/xpcom/ioutils/tests/test_ioutils_read_write_json.html b/xpcom/ioutils/tests/test_ioutils_read_write_json.html
--- a/xpcom/ioutils/tests/test_ioutils_read_write_json.html
+++ b/xpcom/ioutils/tests/test_ioutils_read_write_json.html
@@ -140,10 +140,43 @@
);
await cleanup(filename);
});
+ add_task(async function test_writeJSON_return() {
+ const filename = PathUtils.join(PathUtils.tempDir, "test_ioutils_writeJSON_return.tmp");
+
+ const obj = { emoji: "☕️ ⚧️ 😀 🖖🏿 🤠 🏳️‍🌈 🥠 🏴‍☠️ 🪐" };
+
+ const expectedJson = JSON.stringify(obj);
+ const size = new TextEncoder().encode(expectedJson).byteLength;
+
+ {
+ const result = await IOUtils.writeJSON(filename, obj, { lengthHint: 0 });
+
+ is(await IOUtils.readUTF8(filename), expectedJson, "should have written expected JSON");
+
+ is(typeof result, "object", "writeJSON returns an object");
+ ok(result !== null, "writeJSON returns non-null");
+
+ ok(Object.hasOwn(result, "size"), "result has size property");
+ ok(Object.hasOwn(result, "jsonLength"), "result has jsonLength property");
+
+ is(result.size, size, "Should have written the expected number of bytes");
+ is(result.jsonLength, expectedJson.length, "Should have written the expected number of UTF-16 codepoints");
+ }
+
+ {
+ const result = await IOUtils.writeJSON(filename, obj, { lengthHint: expectedJson.length, compress: true });
+
+ isnot(result.size, size, "Should have written a different number of bytes due to compression");
+ is(result.jsonLength, expectedJson.length, "Should have written the same number of UTF-16 codepoints");
+ }
+
+ await cleanup(filename);
+ });
+
add_task(async function test_append_json() {
const filename = PathUtils.join(PathUtils.tempDir, "test_ioutils_append_json.tmp");
await IOUtils.writeJSON(filename, OBJECT);

View File

@@ -1,279 +0,0 @@
diff --git a/browser/components/sessionstore/SessionFile.sys.mjs b/browser/components/sessionstore/SessionFile.sys.mjs
--- a/browser/components/sessionstore/SessionFile.sys.mjs
+++ b/browser/components/sessionstore/SessionFile.sys.mjs
@@ -503,10 +503,12 @@
if (isFinalWrite) {
Services.obs.notifyObservers(
null,
"sessionstore-final-state-write-complete"
);
+
+ lazy.SessionWriter.deinit();
}
});
},
async wipe() {
diff --git a/browser/components/sessionstore/SessionWriter.sys.mjs b/browser/components/sessionstore/SessionWriter.sys.mjs
--- a/browser/components/sessionstore/SessionWriter.sys.mjs
+++ b/browser/components/sessionstore/SessionWriter.sys.mjs
@@ -6,10 +6,12 @@
ChromeUtils.defineESModuleGetters(lazy, {
sessionStoreLogger: "resource:///modules/sessionstore/SessionLogger.sys.mjs",
});
+const BROWSER_PURGE_SESSION_HISTORY = "browser:purge-session-history";
+
/**
* We just started (we haven't written anything to disk yet) from
* `Paths.clean`. The backup directory may not exist.
*/
const STATE_CLEAN = "clean";
@@ -58,10 +60,14 @@
export const SessionWriter = {
init(origin, useOldExtension, paths, prefs = {}) {
return SessionWriterInternal.init(origin, useOldExtension, paths, prefs);
},
+ deinit() {
+ return SessionWriterInternal.deinit();
+ },
+
/**
* Write the contents of the session file.
*
* @param state - May get changed on shutdown.
*/
@@ -80,10 +86,17 @@
return await SessionWriterInternal.wipe();
} finally {
unlock();
}
},
+
+ /**
+ * *Test Only* Return the SessionWriter's length hint for writing JSON.
+ */
+ get _jsonLengthHint() {
+ return SessionWriterInternal.jsonLengthHint;
+ },
};
const SessionWriterInternal = {
// Path to the files used by the SessionWriter
Paths: null,
@@ -104,10 +117,19 @@
/**
* Number of old upgrade backups that are being kept
*/
maxUpgradeBackups: null,
+ /**
+ * The size of the last write with IOUtils.writeJSON.
+ *
+ * Because SessionWriter writes such a large object graph we will otherwise
+ * spend a large portion of `write()` doing memory allocations and memcpy
+ * when serializing the session file to disk.
+ */
+ jsonLengthHint: 0,
+
/**
* Initialize (or reinitialize) the writer.
*
* @param {string} origin Which of sessionstore.js or its backups
* was used. One of the `STATE_*` constants defined above.
@@ -136,13 +158,20 @@
this.Paths = paths;
this.maxUpgradeBackups = prefs.maxUpgradeBackups;
this.maxSerializeBack = prefs.maxSerializeBack;
this.maxSerializeForward = prefs.maxSerializeForward;
this.upgradeBackupNeeded = paths.nextUpgradeBackup != paths.upgradeBackup;
+
+ Services.obs.addObserver(this, BROWSER_PURGE_SESSION_HISTORY);
+
return { result: true };
},
+ deinit() {
+ Services.obs.removeObserver(this, BROWSER_PURGE_SESSION_HISTORY);
+ },
+
/**
* Write the session to disk.
* Write the session to disk, performing any necessary backup
* along the way.
*
@@ -208,36 +237,42 @@
// We are shutting down. At this stage, we know that
// $Paths.clean is either absent or corrupted. If it was
// originally present and valid, it has been moved to
// $Paths.cleanBackup a long time ago. We can therefore write
// with the guarantees that we erase no important data.
- await IOUtils.writeJSON(this.Paths.clean, state, {
+ const result = await IOUtils.writeJSON(this.Paths.clean, state, {
tmpPath: this.Paths.clean + ".tmp",
compress: true,
+ lengthHint: this.jsonLengthHint,
});
+ this.jsonLengthHint = result.jsonLength;
fileStat = await IOUtils.stat(this.Paths.clean);
} else if (this.state == STATE_RECOVERY) {
// At this stage, either $Paths.recovery was written >= 15
// seconds ago during this session or we have just started
// from $Paths.recovery left from the previous session. Either
// way, $Paths.recovery is good. We can move $Path.backup to
// $Path.recoveryBackup without erasing a good file with a bad
// file.
- await IOUtils.writeJSON(this.Paths.recovery, state, {
+ const result = await IOUtils.writeJSON(this.Paths.recovery, state, {
tmpPath: this.Paths.recovery + ".tmp",
backupFile: this.Paths.recoveryBackup,
compress: true,
+ lengthHint: this.jsonLengthHint,
});
+ this.jsonLengthHint = result.jsonLength;
fileStat = await IOUtils.stat(this.Paths.recovery);
} else {
// In other cases, either $Path.recovery is not necessary, or
// it doesn't exist or it has been corrupted. Regardless,
// don't backup $Path.recovery.
- await IOUtils.writeJSON(this.Paths.recovery, state, {
+ const result = await IOUtils.writeJSON(this.Paths.recovery, state, {
tmpPath: this.Paths.recovery + ".tmp",
compress: true,
+ lengthHint: this.jsonLengthHint,
});
+ this.jsonLengthHint = result.jsonLength;
fileStat = await IOUtils.stat(this.Paths.recovery);
}
telemetry.writeFileMs = Date.now() - startWriteMs;
telemetry.fileSizeBytes = fileStat.size;
@@ -420,6 +455,18 @@
if (exn) {
throw exn;
}
},
+
+ observe(_subject, topic, _data) {
+ switch (topic) {
+ case BROWSER_PURGE_SESSION_HISTORY:
+ this._onPurgeSessionHistory();
+ break;
+ }
+ },
+
+ _onPurgeSessionHistory() {
+ this.jsonLengthHint = 0;
+ },
};
diff --git a/browser/components/sessionstore/test/unit/test_write_json_length_hint.js b/browser/components/sessionstore/test/unit/test_write_json_length_hint.js
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_write_json_length_hint.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+
+const profile = do_get_profile();
+
+updateAppInfo({
+ name: "SessionRestoreTest",
+ ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+ version: "1",
+ platformVersion: "",
+});
+
+const { SessionFile } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/SessionFile.sys.mjs"
+);
+const { SessionWriter } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/SessionWriter.sys.mjs"
+);
+
+add_setup(async function setup() {
+ const source = do_get_file("data/sessionstore_valid.js");
+ source.copyTo(profile, "sessionstore.js");
+
+ await writeCompressedFile(
+ SessionFile.Paths.clean.replace("jsonlz4", "js"),
+ SessionFile.Paths.clean
+ );
+
+ await SessionFile.read();
+});
+
+add_task(async function test_json_length_hint() {
+ await IOUtils.writeJSON(PathUtils.join(PathUtils.profileDir, "dingus"), {
+ gunk: true,
+ });
+
+ Assert.equal(
+ SessionWriter._jsonLengthHint,
+ 0,
+ "SessionWriter length hint starts at 0"
+ );
+
+ await SessionFile.write({});
+
+ const lengthHint = SessionWriter._jsonLengthHint;
+
+ Assert.equal(
+ SessionWriter._jsonLengthHint,
+ JSON.stringify({}).length,
+ "SessionWriter should cache length hint"
+ );
+
+ const contents = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "data", "sessionstore_complete.json")
+ );
+ await SessionFile.write(contents);
+
+ Assert.notEqual(
+ SessionWriter._jsonLengthHint,
+ lengthHint,
+ "SessionWriter length hint updated"
+ );
+
+ Assert.greater(
+ SessionWriter._jsonLengthHint,
+ lengthHint,
+ "SessionWriteLength hint is now larger"
+ );
+
+ Services.obs.notifyObservers(null, "browser:purge-session-history");
+
+ Assert.equal(
+ SessionWriter._jsonLengthHint,
+ 0,
+ "browser:purge-session-history notification cleans length hint"
+ );
+
+ await SessionFile.write(contents);
+
+ Assert.notEqual(
+ SessionWriter._jsonLengthHint,
+ lengthHint,
+ "SessionWriter length hint updated"
+ );
+});
diff --git a/browser/components/sessionstore/test/unit/xpcshell.toml b/browser/components/sessionstore/test/unit/xpcshell.toml
--- a/browser/components/sessionstore/test/unit/xpcshell.toml
+++ b/browser/components/sessionstore/test/unit/xpcshell.toml
@@ -39,5 +39,7 @@
skip-if = [
"condprof", # Bug 1769154
]
["test_startup_session_async.js"]
+
+["test_write_json_length_hint.js"]

View File

@@ -0,0 +1,280 @@
diff --git a/browser/components/sessionstore/SessionWriter.sys.mjs b/browser/components/sessionstore/SessionWriter.sys.mjs
--- a/browser/components/sessionstore/SessionWriter.sys.mjs
+++ b/browser/components/sessionstore/SessionWriter.sys.mjs
@@ -80,10 +80,14 @@
return await SessionWriterInternal.wipe();
} finally {
unlock();
}
},
+
+ get _jsonLengthHint() {
+ return SessionWriterInternal._lastJsonLength;
+ },
};
const SessionWriterInternal = {
// Path to the files used by the SessionWriter
Paths: null,
@@ -104,10 +108,14 @@
/**
* Number of old upgrade backups that are being kept
*/
maxUpgradeBackups: null,
+ // Estimated JSON string length from the previous write, used to pre-size
+ // the serialization buffer and avoid incremental reallocations.
+ _lastJsonLength: 0,
+
/**
* Initialize (or reinitialize) the writer.
*
* @param {string} origin Which of sessionstore.js or its backups
* was used. One of the `STATE_*` constants defined above.
@@ -201,48 +209,60 @@
}
}
let startWriteMs = Date.now();
let fileStat;
+ // Add 5% headroom to the hint so small growth between saves doesn't
+ // cause reallocs. The compressed-size-based estimate already has
+ // sufficient margin from the 4x multiplier.
+ let jsonLengthHint = Math.ceil(this._lastJsonLength * 1.05);
+
+ let uncompressedBytes;
if (options.isFinalWrite) {
// We are shutting down. At this stage, we know that
// $Paths.clean is either absent or corrupted. If it was
// originally present and valid, it has been moved to
// $Paths.cleanBackup a long time ago. We can therefore write
// with the guarantees that we erase no important data.
- await IOUtils.writeJSON(this.Paths.clean, state, {
+ uncompressedBytes = await IOUtils.writeJSON(this.Paths.clean, state, {
tmpPath: this.Paths.clean + ".tmp",
compress: true,
+ jsonLengthHint,
});
fileStat = await IOUtils.stat(this.Paths.clean);
} else if (this.state == STATE_RECOVERY) {
// At this stage, either $Paths.recovery was written >= 15
// seconds ago during this session or we have just started
// from $Paths.recovery left from the previous session. Either
// way, $Paths.recovery is good. We can move $Path.backup to
// $Path.recoveryBackup without erasing a good file with a bad
// file.
- await IOUtils.writeJSON(this.Paths.recovery, state, {
+ uncompressedBytes = await IOUtils.writeJSON(this.Paths.recovery, state, {
tmpPath: this.Paths.recovery + ".tmp",
backupFile: this.Paths.recoveryBackup,
compress: true,
+ jsonLengthHint,
});
fileStat = await IOUtils.stat(this.Paths.recovery);
} else {
// In other cases, either $Path.recovery is not necessary, or
// it doesn't exist or it has been corrupted. Regardless,
// don't backup $Path.recovery.
- await IOUtils.writeJSON(this.Paths.recovery, state, {
+ uncompressedBytes = await IOUtils.writeJSON(this.Paths.recovery, state, {
tmpPath: this.Paths.recovery + ".tmp",
compress: true,
+ jsonLengthHint,
});
fileStat = await IOUtils.stat(this.Paths.recovery);
}
telemetry.writeFileMs = Date.now() - startWriteMs;
telemetry.fileSizeBytes = fileStat.size;
+ // Use the actual pre-compression size from this write as the hint
+ // for the next write's buffer allocation.
+ this._lastJsonLength = uncompressedBytes;
lazy.sessionStoreLogger.debug(
`SessionWriter.write wrote ${telemetry.fileSizeBytes} bytes in ${telemetry.writeFileMs}ms`
);
} catch (ex) {
// Don't throw immediately
@@ -375,10 +395,11 @@
} catch (ex) {
exn = exn || ex;
}
this.state = STATE_EMPTY;
+ this._lastJsonLength = 0;
if (exn) {
throw exn;
}
return { result: true };
diff --git a/browser/components/sessionstore/test/unit/test_write_json_length_hint.js b/browser/components/sessionstore/test/unit/test_write_json_length_hint.js
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_write_json_length_hint.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { SessionWriter } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/SessionWriter.sys.mjs"
+);
+
+const profd = do_get_profile();
+const { SessionFile } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/SessionFile.sys.mjs"
+);
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo({
+ name: "SessionRestoreTest",
+ ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+ version: "1",
+ platformVersion: "",
+});
+
+add_setup(async function () {
+ let source = do_get_file("data/sessionstore_valid.js");
+ source.copyTo(profd, "sessionstore.js");
+ await writeCompressedFile(
+ SessionFile.Paths.clean.replace("jsonlz4", "js"),
+ SessionFile.Paths.clean
+ );
+ await SessionFile.read();
+});
+
+add_task(async function test_length_hint_updates_after_write() {
+ Assert.equal(
+ SessionWriter._jsonLengthHint,
+ 0,
+ "Length hint starts at 0"
+ );
+
+ await SessionFile.write({});
+
+ let hintAfterSmall = SessionWriter._jsonLengthHint;
+ Assert.equal(
+ hintAfterSmall,
+ JSON.stringify({}).length,
+ "Hint matches the uncompressed JSON byte length"
+ );
+
+ let largerState = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, "data", "sessionstore_complete.json")
+ );
+ await SessionFile.write(largerState);
+
+ Assert.greater(
+ SessionWriter._jsonLengthHint,
+ hintAfterSmall,
+ "Hint grows after writing a larger state"
+ );
+});
+
+add_task(async function test_length_hint_resets_on_wipe() {
+ await SessionFile.write({ windows: [{ tabs: [{ entries: [] }] }] });
+ Assert.greater(SessionWriter._jsonLengthHint, 0, "Hint is nonzero");
+
+ await SessionFile.wipe();
+ Assert.equal(
+ SessionWriter._jsonLengthHint,
+ 0,
+ "Hint resets to 0 after wipe"
+ );
+});
diff --git a/browser/components/sessionstore/test/unit/xpcshell.toml b/browser/components/sessionstore/test/unit/xpcshell.toml
--- a/browser/components/sessionstore/test/unit/xpcshell.toml
+++ b/browser/components/sessionstore/test/unit/xpcshell.toml
@@ -39,5 +39,10 @@
skip-if = [
"condprof", # Bug 1769154
]
["test_startup_session_async.js"]
+
+["test_write_json_length_hint.js"]
+support-files = [
+ "data/sessionstore_complete.json",
+]
diff --git a/dom/chrome-webidl/IOUtils.webidl b/dom/chrome-webidl/IOUtils.webidl
--- a/dom/chrome-webidl/IOUtils.webidl
+++ b/dom/chrome-webidl/IOUtils.webidl
@@ -101,12 +101,12 @@
*
* @param path An absolute file path
* @param value The value to be serialized.
* @param options Options for writing the file. The "append" mode is not supported.
*
- * @return Resolves with the number of bytes successfully written to the file,
- * otherwise rejects with a DOMException.
+ * @return Resolves with the pre-compression size of the serialized JSON in
+ * bytes (UTF-8), otherwise rejects with a DOMException.
*/
[NewObject]
Promise<unsigned long long> writeJSON(DOMString path, any value, optional WriteOptions options = {});
/**
* Moves the file from |sourcePath| to |destPath|, creating necessary parents.
@@ -564,10 +564,16 @@
boolean flush = false;
/**
* If true, compress the data with LZ4-encoding before writing to the file.
*/
boolean compress = false;
+ /**
+ * For |writeJSON|, a hint for the expected JSON string length in UTF-16 code
+ * units. When provided, the JSON serializer pre-allocates a buffer of this
+ * size to avoid incremental reallocations.
+ */
+ unsigned long long jsonLengthHint = 0;
};
/**
* Options to be passed to the |IOUtils.move| method.
*/
diff --git a/xpcom/ioutils/IOUtils.cpp b/xpcom/ioutils/IOUtils.cpp
--- a/xpcom/ioutils/IOUtils.cpp
+++ b/xpcom/ioutils/IOUtils.cpp
@@ -622,13 +622,22 @@
return;
}
JSContext* cx = aGlobal.Context();
JS::Rooted<JS::Value> rootedValue(cx, aValue);
+ size_t lengthHint = aOptions.mJsonLengthHint;
nsString string;
- if (!nsContentUtils::StringifyJSON(cx, aValue, string,
- UndefinedIsNullStringLiteral)) {
+ if (lengthHint) {
+ string.SetCapacity(lengthHint);
+ }
+ if (!JS_StringifyWithLengthHint(
+ cx, &rootedValue, nullptr, JS::NullHandleValue,
+ [](const char16_t* aBuf, uint32_t aLen, void* aData) -> bool {
+ return static_cast<nsAString*>(aData)->Append(aBuf, aLen,
+ fallible);
+ },
+ &string, lengthHint)) {
JS::Rooted<JS::Value> exn(cx, JS::UndefinedValue());
if (JS_GetPendingException(cx, &exn)) {
JS_ClearPendingException(cx);
promise->MaybeReject(exn);
} else {
@@ -648,11 +657,13 @@
return Err(IOError(
NS_ERROR_OUT_OF_MEMORY,
"Failed to write to `%s': could not allocate buffer",
file->HumanReadablePath().get()));
}
- return WriteSync(file, AsBytes(Span(utf8Str)), opts);
+ uint32_t uncompressedSize = utf8Str.Length();
+ MOZ_TRY(WriteSync(file, AsBytes(Span(utf8Str)), opts));
+ return uncompressedSize;
});
});
}
/* static */

View File

@@ -10,7 +10,19 @@
// Specifically trying to target FeatureCallout.sys.mjs's change.
// IMPORTANT: Make sure Feature callouts STILL use native popopvers when
// syncing from upstream, as this is a critical part of the patch.
"+ nonnativepopover=\"true\"": "+ "
"+ nonnativepopover=\"true\"": "+ ",
// Fix conflicts with upstream changes.
"--menuitem-border-radius: var(--panel-menuitem-border-radius)": "--menuitem-border-radius: var(--arrowpanel-menuitem-border-radius)",
"--menuitem-padding: var(--panel-menuitem-padding)": "--menuitem-padding: var(--arrowpanel-menuitem-padding)",
"--menuitem-margin: var(--panel-menuitem-margin)": "--menuitem-margin: var(--arrowpanel-menuitem-margin)",
" \n #include \"nsCocoaWindow.h\"\n \n #include \"nsISupportsPrimitives.h\"\n #include \"nsArrayUtils.h\"":
" * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n \n #include \"nsCocoaWindow.h\"\n \n #include \"nsISupportsPrimitives.h\"\n #include \"nsArrayUtils.h\"",
" #include \"nsISupportsPrimitives.h\"\n": "",
" Atom(\"nonnative\", \"nonnative\"),\n": "",
"Atom(\"noscript\", \"noscript\"),": "Atom(\"noscript\", \"noscript\"),\n Atom(\"noshade\", \"noshade\"),"
}
},
{
@@ -21,6 +33,12 @@
"\n\n": "\n // may want to figure out a more robust way to detect abandonment."
}
},
{
"type": "local",
// TODO: Convert into https://phabricator.services.mozilla.com/D298079
// once it gets accepted.
"path": "firefox/allow_backdrop_to_work_on_transparency.patch"
},
{
"type": "local",
"path": "firefox/no_liquid_glass_icon.patch"
@@ -44,8 +62,7 @@
"type": "phabricator",
"ids": [
"D247145",
"D247215",
"D247217"
"D298708"
],
"name": "Session store use size hint"
},

View File

@@ -1,5 +1,5 @@
diff --git a/toolkit/modules/JSONFile.sys.mjs b/toolkit/modules/JSONFile.sys.mjs
index 397991e4af8f49b6365d729fc11267b5c1113400..1955b7ff1d428e891f5ef066e7a4ac25aa5ec9b4 100644
index 397991e4af8f49b6365d729fc11267b5c1113400..9b1d6fd3850b239000a3c4d2a2d5799a0989f4e3 100644
--- a/toolkit/modules/JSONFile.sys.mjs
+++ b/toolkit/modules/JSONFile.sys.mjs
@@ -132,6 +132,7 @@ export function JSONFile(config) {
@@ -16,14 +16,14 @@ index 397991e4af8f49b6365d729fc11267b5c1113400..1955b7ff1d428e891f5ef066e7a4ac25
try {
- await IOUtils.writeJSON(
+ if (this._useSizeHints && this._lastSavedSize) {
+ this._options.lengthHint = this._lastSavedSize;
+ this._options.jsonLengthHint = Math.ceil(this._lastSavedSize * 1.05);
+ }
+ const result = await IOUtils.writeJSON(
this.path,
this._data,
Object.assign({ tmpPath: this.path + ".tmp" }, this._options)
);
+ this._lastSavedSize = this._useSizeHints ? result.jsonLength : null;
+ this._lastSavedSize = this._useSizeHints ? result : null;
} catch (ex) {
if (typeof this._data.toJSONSafe == "function") {
// If serialization fails, try fallback safe JSON converter.

View File

@@ -1,5 +1,5 @@
diff --git a/widget/SwipeTracker.cpp b/widget/SwipeTracker.cpp
index 887d06d3bd9cdaa934880e0ae7a11ec8b737fb61..e2bf27c0130701f1d50990b60a5ef76e93c5a6bf 100644
index 887d06d3bd9cdaa934880e0ae7a11ec8b737fb61..7225d4264c9ec71ef1e5e717a1a62c4ef0aff0b7 100644
--- a/widget/SwipeTracker.cpp
+++ b/widget/SwipeTracker.cpp
@@ -3,6 +3,7 @@
@@ -10,7 +10,17 @@ index 887d06d3bd9cdaa934880e0ae7a11ec8b737fb61..e2bf27c0130701f1d50990b60a5ef76e
#include "InputData.h"
#include "mozilla/FlushType.h"
@@ -90,7 +91,7 @@ bool SwipeTracker::ComputeSwipeSuccess() const {
@@ -67,6 +68,9 @@ double SwipeTracker::SwipeSuccessTargetValue() const {
}
double SwipeTracker::ClampToAllowedRange(double aGestureAmount) const {
+ if (StaticPrefs::zen_swipe_is_fast_swipe()) {
+ return aGestureAmount;
+ }
// gestureAmount needs to stay between -1 and 0 when swiping right and
// between 0 and 1 when swiping left.
double min =
@@ -90,7 +94,7 @@ bool SwipeTracker::ComputeSwipeSuccess() const {
return (mGestureAmount * targetValue +
mCurrentVelocity * targetValue *
@@ -19,3 +29,13 @@ index 887d06d3bd9cdaa934880e0ae7a11ec8b737fb61..e2bf27c0130701f1d50990b60a5ef76e
kSwipeSuccessThreshold;
}
@@ -141,7 +145,8 @@ nsEventStatus SwipeTracker::ProcessEvent(
// display the UI as if we were at the success threshold as that would
// give a false indication that navigation would happen.
if (!computedSwipeSuccess && (eventAmount >= kSwipeSuccessThreshold ||
- eventAmount <= -kSwipeSuccessThreshold)) {
+ eventAmount <= -kSwipeSuccessThreshold)
+ && !StaticPrefs::zen_swipe_is_fast_swipe()) {
eventAmount = 0.999 * kSwipeSuccessThreshold;
if (mGestureAmount < 0.f) {
eventAmount = -eventAmount;

View File

@@ -0,0 +1,27 @@
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
index 515177a3a97094142593a98fb1b3023acf1ccb87..fb6f932547f9523a240f95915b7440dcdfa16975 100644
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -5240,7 +5240,7 @@ static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
// calls to ...orderFront: in TRY blocks. See bmo bug 470864.
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
mWindow.contentView.needsDisplay = YES;
- if (!nativeParentWindow || mPopupLevel != PopupLevel::Parent) {
+ if (!mZenShowLocked && (!nativeParentWindow || mPopupLevel != PopupLevel::Parent)) {
[mWindow orderFront:nil];
}
NS_OBJC_END_TRY_IGNORE_BLOCK;
@@ -5287,7 +5287,12 @@ static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
// We don't want most alwaysontop / alert windows to pull focus when
// they're opened, as these tend to be for peripheral indicators and
// displays.
- if ((mAlwaysOnTop && mPiPType != PiPType::DocumentPiP) || mIsAlert) {
+ if (mZenShowLocked) {
+ // Zen: window-control service has this widget locked-hidden;
+ // skip the native order-front but let the rest of Show()'s
+ // bookkeeping run normally.
+ } else if ((mAlwaysOnTop && mPiPType != PiPType::DocumentPiP) ||
+ mIsAlert) {
[mWindow orderFront:nil];
} else {
[mWindow makeKeyAndOrderFront:nil];

View File

@@ -0,0 +1,18 @@
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
index 89950bd72c77ed961e59faa2fadb8f21a78fd23a..8c74bfa08af3c16cc05e11ea34902cbcdde3c64a 100644
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -1076,7 +1076,12 @@ void nsWindow::Show(bool aState) {
}
#endif
- NativeShow(aState);
+ // Zen: skip the actual GTK show when window-control has the widget
+ // locked-hidden; mIsShown is already set above so Mozilla's
+ // bookkeeping treats the widget as shown.
+ if (!(aState && mZenShowLocked)) {
+ NativeShow(aState);
+ }
RefreshWindowClass();
}

View File

@@ -0,0 +1,28 @@
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
index c22e055b9254ed1c8943c232a8339c574563dffc..0a0cb7cad4e878f93c03078564dac051dde1d014 100644
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -698,6 +698,14 @@ class nsIWidget : public nsSupportsWeakReference {
*/
virtual void Show(bool aState) = 0;
+ /**
+ * Zen: when set to true, Show(true) calls on this widget become
+ * no-ops until SetZenShowLocked(false) is called. Used to keep a
+ * window invisible while it's being set up.
+ */
+ void SetZenShowLocked(bool aLocked) { mZenShowLocked = aLocked; }
+ bool IsZenShowLocked() const { return mZenShowLocked; }
+
/**
* Whether or not a widget must be recreated after being hidden to show
* again properly.
@@ -2419,6 +2427,8 @@ class nsIWidget : public nsSupportsWeakReference {
mozilla::Maybe<FullscreenSavedState> mSavedBounds;
bool mUpdateCursor;
+ // Zen: when true, Show(true) is a no-op. See SetZenShowLocked().
+ bool mZenShowLocked = false;
bool mIMEHasFocus;
bool mIMEHasQuit;
// if the window is fully occluded (rendering may be paused in response)

View File

@@ -0,0 +1,59 @@
diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
index baed7d09e31291b26a1d0a1ddfd63b57f0ce67d0..9daa1140e9538427d002c6f8ff99cd68265249cc 100644
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -1761,28 +1761,33 @@ void nsWindow::Show(bool aState) {
// cursor.
SetCursor(Cursor{eCursor_standard});
- switch (mFrameState->GetSizeMode()) {
- case nsSizeMode_Fullscreen:
- ::ShowWindow(mWnd, SW_SHOW);
- break;
- case nsSizeMode_Maximized:
- ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
- break;
- case nsSizeMode_Minimized:
- ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
- break;
- default:
- if (CanTakeFocus() &&
- (!mAlwaysOnTop || mPiPType == PiPType::DocumentPiP)) {
- ::ShowWindow(mWnd, SW_SHOWNORMAL);
- } else {
- ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
- // Don't flicker the window if we're restoring session
- if (!sIsRestoringSession) {
- (void)GetAttention(2);
+ // Zen: skip the actual ShowWindow() call when window-control
+ // has the widget locked-hidden; mIsVisible is already set above
+ // so Mozilla's bookkeeping treats the widget as shown.
+ if (!mZenShowLocked) {
+ switch (mFrameState->GetSizeMode()) {
+ case nsSizeMode_Fullscreen:
+ ::ShowWindow(mWnd, SW_SHOW);
+ break;
+ case nsSizeMode_Maximized:
+ ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
+ break;
+ case nsSizeMode_Minimized:
+ ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
+ break;
+ default:
+ if (CanTakeFocus() &&
+ (!mAlwaysOnTop || mPiPType == PiPType::DocumentPiP)) {
+ ::ShowWindow(mWnd, SW_SHOWNORMAL);
+ } else {
+ ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
+ // Don't flicker the window if we're restoring session
+ if (!sIsRestoringSession) {
+ (void)GetAttention(2);
+ }
}
- }
- break;
+ break;
+ }
}
if (!mHasBeenShown) {

View File

@@ -16,3 +16,5 @@ category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 applicatio
#include common/Components.manifest
#include sessionstore/SessionComponents.manifest
#include live-folders/LiveFoldersComponents.manifest
#include kbs/KbsComponents.manifest
#include little-window/LittleWindowComponents.manifest

View File

@@ -76,7 +76,7 @@ export class ZenBoostsChild extends JSWindowActorChild {
* > #define NS_RGBA(_r, _g, _b, _a) \
* > ((nscolor)(((_a) << 24) | ((_b) << 16) | ((_g) << 8) | (_r)))
*
* Converts [r, g, b] array to NSColor
* Converts [r, g, b] array to (uint32_t) NSColor
* Make a color out of r,g,b,a values. This assumes that the r,g,b,a
* values are properly constrained to 0-255.
*
@@ -92,7 +92,8 @@ export class ZenBoostsChild extends JSWindowActorChild {
// be fully opaque and we need an extra byte to store the contrast value. This allows
// us to still use an nscolor as parameter instead of having to deal with WebIDL structs
// shenanigans.
return (contrast << 24) | (b << 16) | (g << 8) | r;
// Take into account that its an unsigned int
return ((contrast << 24) | (b << 16) | (g << 8) | r) >>> 0;
}
/**
@@ -173,7 +174,7 @@ export class ZenBoostsChild extends JSWindowActorChild {
}
this.#removeEventListeners();
break;
case "DOMDocElementInserted":
case "DOMWindowCreated":
this.#applyBoostForPageIfAvailable();
break;
default:

View File

@@ -38,6 +38,18 @@ void BrowsingContext::WalkPresContexts(Callback&& aCallback) {
});
}
static void RefreshBoostCacheIfMatchesCurrent(BrowsingContext* aChanged) {
auto* backend = zen::nsZenBoostsBackend::GetInstance();
if (!backend) {
return;
}
auto current = backend->GetCurrentBrowsingContext();
if (!current || current->Top() != aChanged) {
return;
}
backend->RefreshCachedBoostState();
}
/**
* @brief Called when the ZenBoostsData field is set on a browsing context.
* Triggers a restyle if the boost data has changed.
@@ -49,6 +61,7 @@ void BrowsingContext::DidSet(FieldIndex<IDX_ZenBoostsData>,
if (ZenBoostsData() == aOldValue) {
return;
}
RefreshBoostCacheIfMatchesCurrent(this);
PresContextAffectingFieldChanged();
TRIGGER_PRES_CONTEXT_RESTYLE();
}
@@ -64,6 +77,7 @@ void BrowsingContext::DidSet(FieldIndex<IDX_IsZenBoostsInverted>,
if (IsZenBoostsInverted() == aOldValue) {
return;
}
RefreshBoostCacheIfMatchesCurrent(this);
PresContextAffectingFieldChanged();
TRIGGER_PRES_CONTEXT_RESTYLE();
}

View File

@@ -145,7 +145,7 @@ inline static auto zenPrecomputeAccent(nscolor aAccentColor) {
return aOriginalColor;
}
const float inv255 = 1.0f / 255.0f;
constexpr float inv255 = 1.0f / 255.0f;
const float blendFactor = contrast * inv255;
// sRGB -> linear
@@ -261,7 +261,9 @@ inline static nscolor zenInvertColorChannel(nscolor aColor) {
}
/**
* @brief Retrieves the current boost data from the browsing context.
* @brief Retrieves the current boost data from the browsing context. When
* called without aPresContext, reads the precomputed cache populated on
* presshell entry; otherwise resolves from the supplied PresContext.
*/
ZEN_HOT_FUNCTION
inline static void GetZenBoostsDataFromBrowsingContext(
@@ -272,11 +274,14 @@ inline static void GetZenBoostsDataFromBrowsingContext(
!SHOULD_APPLY_BOOSTS_TO_ANONYMOUS_CONTENT())) {
return;
}
auto browsingContext = zenBoosts->GetCurrentBrowsingContext();
if (aPresContext) {
if (auto document = aPresContext->Document()) {
browsingContext = document->GetBrowsingContext();
}
if (!aPresContext) {
*aData = zenBoosts->mCachedCurrentAccent;
*aIsInverted = zenBoosts->mCachedCurrentInverted;
return;
}
mozilla::dom::BrowsingContext* browsingContext = nullptr;
if (auto document = aPresContext->Document()) {
browsingContext = document->GetBrowsingContext();
}
if (!browsingContext) {
return;
@@ -318,6 +323,18 @@ auto nsZenBoostsBackend::onPresShellEntered(mozilla::dom::Document* aDocument)
return;
}
mCurrentBrowsingContext = browsingContext;
RefreshCachedBoostState();
}
auto nsZenBoostsBackend::RefreshCachedBoostState() -> void {
if (!mCurrentBrowsingContext) {
mCachedCurrentAccent = 0;
mCachedCurrentInverted = false;
return;
}
auto top = mCurrentBrowsingContext->Top();
mCachedCurrentAccent = top->ZenBoostsData();
mCachedCurrentInverted = top->IsZenBoostsInverted();
}
[[nodiscard]] ZEN_HOT_FUNCTION auto

View File

@@ -64,11 +64,27 @@ class nsZenBoostsBackend final {
*/
auto onPresShellEntered(mozilla::dom::Document* aDocument) -> void;
/**
* @brief Refresh the cached boost state from the current top BrowsingContext.
* Called from onPresShellEntered and from BrowsingContext::DidSet hooks when
* the underlying boost fields change.
*/
auto RefreshCachedBoostState() -> void;
[[nodiscard]]
inline auto GetCurrentBrowsingContext() const {
return mCurrentBrowsingContext;
}
/**
* Cached boost data for the current top BrowsingContext, refreshed on
* presshell entry and on DidSet hooks. Read by the per-color hot path so
* that boost-off pages don't pay for a BrowsingContext walk on every color
* resolve.
*/
ZenBoostData mCachedCurrentAccent = 0;
bool mCachedCurrentInverted = false;
private:
/**
* The presshell of the current document being rendered.

View File

@@ -13,7 +13,6 @@
"chrome://browser/content/zen-components/ZenCompactMode.mjs",
"chrome://browser/content/ZenUIManager.mjs",
"chrome://browser/content/zen-components/ZenMods.mjs",
"chrome://browser/content/zen-components/ZenKeyboardShortcuts.mjs",
"chrome://browser/content/zen-components/ZenSessionStore.mjs",
"chrome://browser/content/zen-components/ZenMediaController.mjs",
"chrome://browser/content/zen-components/ZenGlanceManager.mjs",

View File

@@ -92,7 +92,7 @@ export class nsZenMenuBar {
key="zen-workspace-forward"/>
<menuitem
data-l10n-id="zen-panel-ui-workspaces-change-back"
command="cmd_zenWorkspaceBack"
command="cmd_zenWorkspaceBackward"
key="zen-workspace-backward"/>
</menupopup>
</menu>`);

View File

@@ -32,7 +32,6 @@ class ZenStartup {
return;
}
this.#hasInitializedLayout = true;
gZenKeyboardShortcutsManager.beforeInit();
try {
const kNavbarItems = ["nav-bar", "PersonalToolbar"];
const kNewContainerId = "zen-appcontent-navbar-container";

View File

@@ -295,6 +295,7 @@ window.gZenUIManager = {
onFloatingURLBarOpen() {
requestAnimationFrame(() => {
this.updateTabsToolbar();
window.dispatchEvent(new CustomEvent("ZenFloatingURLBarOpened"));
});
},
@@ -607,6 +608,12 @@ window.gZenUIManager = {
gURLBar._zenHandleUrlbarClose = null;
}
window.dispatchEvent(
new CustomEvent("ZenURLBarClosedEarly", {
detail: { onSwitch, onElementPicked },
})
);
const isFocusedBefore = gURLBar.focused;
setTimeout(() => {
// We use this attribute on Tabbrowser::addTab
@@ -698,7 +705,16 @@ window.gZenUIManager = {
this.urlbarShowDomainOnly
) {
let url = BrowserUIUtils.removeSingleTrailingSlashFromURL(aURL);
return url.startsWith("https://") ? url.split("/")[2] : url;
requestIdleCallback(() => {
// Scroll the urlbar all the way to the right so that
// the domain is always visible when the url is too long.
gURLBar.inputField.scrollLeft = gURLBar.inputField.scrollWidth;
});
let stripped = url.startsWith("https://") ? url.split("/")[2] : url;
if (stripped.startsWith("www.")) {
stripped = stripped.substring(4);
}
return stripped;
}
return BrowserUIUtils.trimURL(aURL);
},
@@ -942,7 +958,9 @@ window.gZenVerticalTabsManager = {
?.includes("toolbar") ||
document.documentElement
.getAttribute("chromehidden")
?.includes("menubar")
?.includes("menubar") ||
document.documentElement.hasAttribute("zen-little-window") ||
window._zenStartupLittleWindow
);
});
@@ -950,7 +968,18 @@ window.gZenVerticalTabsManager = {
this,
"_canReplaceNewTab",
"zen.urlbar.replace-newtab",
true
true,
null,
val => {
// On little windows, we always want to replace new tabs
if (
window._zenStartupLittleWindow ||
document.documentElement.hasAttribute("zen-little-window")
) {
return true;
}
return val;
}
);
var updateEvent = this._updateEvent.bind(this);
var onPrefChange = this._onPrefChange.bind(this);
@@ -1261,7 +1290,8 @@ window.gZenVerticalTabsManager = {
const topButtons = document.getElementById("zen-sidebar-top-buttons");
const isCompactMode =
gZenCompactModeManager.preference && !forCustomizableMode;
(gZenCompactModeManager.preference && !forCustomizableMode) ||
this.hidesTabsToolbar;
const isVerticalTabs = this._prefsVerticalTabs || forCustomizableMode;
const isSidebarExpanded = this._prefsSidebarExpanded || !isVerticalTabs;
const isRightSide = this._prefsRightSide && isVerticalTabs;
@@ -1355,7 +1385,7 @@ window.gZenVerticalTabsManager = {
buttonsTarget.append(this._topButtonsSeparatorElement);
}
for (const button of elements) {
this._topButtonsSeparatorElement.after(button);
this.appendCustomizableItem(this._topButtonsSeparatorElement, button);
}
buttonsTarget.prepend(
document.getElementById("unified-extensions-button")
@@ -1558,16 +1588,36 @@ window.gZenVerticalTabsManager = {
Services.prefs.setBoolPref("zen.tabs.vertical.right-side", newVal);
},
appendCustomizableItem(target, child, placements) {
appendCustomizableItem(target, child, placements = []) {
if (
target.id === "zen-sidebar-top-buttons-customization-target" &&
this._hasSetSingleToolbar &&
placements.includes(child.id)
(target.id === "zen-sidebar-top-buttons-customization-target" ||
target === this._topButtonsSeparatorElement)
) {
this._topButtonsSeparatorElement.before(child);
return;
if (placements.includes(child.id)) {
this._topButtonsSeparatorElement.before(child);
return;
} else if (
child.hasAttribute("data-extensionid") &&
Services.prefs.getBoolPref("zen.view.overflow-webext-toolbar", true)
) {
if (gURLBar._isOverflowingItems) {
const overflowElements = document.getElementById(
"zen-overflow-extensions-list"
);
overflowElements.appendChild(child);
} else {
const element = document.getElementById("page-action-buttons");
element.before(child);
}
return;
}
}
if (target === this._topButtonsSeparatorElement) {
this._topButtonsSeparatorElement.after(child);
} else {
target.appendChild(child);
}
target.appendChild(child);
},
async renameTabKeydown(event) {

View File

@@ -5,6 +5,7 @@
EXTRA_JS_MODULES += [
"sys/ZenActorsManager.sys.mjs",
"sys/ZenCustomizableUI.sys.mjs",
"sys/ZenSearchPopup.sys.mjs",
"sys/ZenUIMigration.sys.mjs",
]

View File

@@ -4,3 +4,4 @@
color-scheme: dark;
--zen-urlbar-outline-offset: -2px;
--zen-urlbar-filter: blur(40px) saturate(110%) brightness(25%) contrast(100%);

View File

@@ -4,3 +4,4 @@
color-scheme: light;
--zen-urlbar-outline-offset: 0px;
--zen-urlbar-filter: blur(40px) saturate(110%) brightness(75%) contrast(100%);

View File

@@ -37,6 +37,18 @@
}
@keyframes zen-dialog-fade-in {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes zen-dialog-fade-in-shifted {
from {
opacity: 0;
transform: translateY(calc(-10% - 10px));

View File

@@ -43,7 +43,7 @@
}
/* stylelint-disable-next-line media-query-no-invalid */
@media (not -moz-pref("zen.view.shift-down-site-on-hover")) {
@media (not -moz-pref("zen.view.shift-down-site-on-hover")) and -moz-pref("zen.view.hide-window-controls") {
.browserSidebarContainer:is(.deck-selected, [zen-split="true"]) .browserContainer {
transition: margin var(--zen-hidden-toolbar-transition);
@@ -59,7 +59,8 @@
--margin-top-fix: calc(-1 * var(--zen-toolbar-height-with-bookmarks) + var(--zen-element-separation));
}
@media -moz-pref("zen.view.experimental-no-window-controls") or (not -moz-pref("zen.view.hide-window-controls")) or (not -moz-pref("browser.tabs.inTitlebar")) {
/* stylelint-disable-next-line media-query-no-invalid */
@media -moz-pref("zen.view.experimental-no-window-controls") or (not -moz-pref("browser.tabs.inTitlebar")) {
:root:not([zen-has-bookmarks="true"])[zen-single-toolbar="true"] & {
--margin-top-fix: 0px;
}

View File

@@ -20,9 +20,13 @@ body,
position: inherit;
}
:root:is([inDOMFullscreen="true"], [chromehidden~="location"], [chromehidden~="toolbar"]) {
:root:is(
[inDOMFullscreen="true"], [chromehidden~="location"],
[chromehidden~="toolbar"], [zen-little-window="true"]
) {
#navigator-toolbox,
#zen-sidebar-splitter {
#zen-sidebar-splitter,
#zen-sidebar-top-buttons {
visibility: collapse;
}
}

View File

@@ -139,10 +139,12 @@
}
.identity-box-button,
.urlbar-page-action {
.urlbar-page-action,
.unified-extensions-item {
opacity: 0;
height: 100%; /* To still be able to open popups */
visibility: collapse;
margin: 0 !important;
:root:not([supress-primary-adjustment="true"]) & {
transition:
@@ -189,6 +191,10 @@
}
}
:root[zen-single-toolbar="true"] #urlbar[breakout-extend="true"] .unified-extensions-item {
display: none !important;
}
/* stylelint-disable-next-line media-query-no-invalid */
@media -moz-pref("zen.urlbar.single-toolbar-show-copy-url", false) {
:root[zen-single-toolbar="true"] #zen-copy-url-button {
@@ -262,9 +268,9 @@
& .urlbar-background {
--zen-urlbar-background-base: light-dark(#fbfbfb, color-mix(in srgb, hsl(0, 0%, 6.7%), var(--zen-colors-primary) 30%));
/* stylelint-disable-next-line media-query-no-invalid */
@media -moz-pref("zen.theme.acrylic-elements") {
@media -moz-pref("zen.theme.acrylic-elements") and (not (prefers-reduced-transparency: reduce)) {
--zen-urlbar-background-transparent: light-dark(
color-mix(in srgb, var(--zen-urlbar-background-base) 90%, transparent),
color-mix(in srgb, white 80%, transparent),
color-mix(in srgb, var(--zen-urlbar-background-base) 65%, transparent)
);
}
@@ -276,7 +282,7 @@
/* stylelint-disable-next-line media-query-no-invalid */
@media -moz-pref("zen.theme.acrylic-elements") {
backdrop-filter: blur(42px) saturate(110%) brightness(0.25) contrast(100%) !important;
backdrop-filter: var(--zen-urlbar-filter) !important;
}
}

View File

@@ -6,10 +6,12 @@
#zen-overflow-extensions-list:not(:empty) {
--uei-icon-size: 14px;
display: flex;
display: grid;
gap: 8px;
padding: 8px 2px;
padding-bottom: 0;
border-radius: calc(var(--border-radius-medium) - 4px);
grid-template-columns: repeat(auto-fit, minmax(32px, 1fr));
& .unified-extensions-item {
flex: 1;
@@ -26,6 +28,7 @@
}
& .unified-extensions-item-action-button {
appearance: none;
background-color: var(--zen-toolbar-element-bg);
height: 30px;
margin: 0;

View File

@@ -10,8 +10,14 @@
box-shadow: 0 10px 8px rgba(0, 0 , 0, 0.15) !important;
outline-offset: -1.5px;
&:not([sizeto="available"]) {
@media not (prefers-reduced-motion: reduce) {
animation: zen-dialog-fade-in 0.3s ease-out;
}
.content-prompt-dialog & {
@media not (prefers-reduced-motion: reduce) {
animation: zen-dialog-fade-in-shifted 0.3s ease-out;
}
transform: translateY(-10%);
}
}

View File

@@ -739,17 +739,13 @@
--zen-loading-progress-bar-color: color-mix(in srgb, var(--zen-primary-color), light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.5)) 70%);
position: fixed;
top: calc(-2px - env(hairline));
top: calc(var(--zen-element-separation) / -2);
/* Minimum -2px, but if its larger, elemenet separation / -2 will be used as top offset, to avoid overlapping with the notification stack */
:root:is([zen-no-padding="true"], [inDOMFullscreen="true"]) & {
:root:is([zen-no-padding="true"], [inDOMFullscreen="true"], :not([zen-single-toolbar="true"])) & {
top: 4px;
}
:root[zen-single-toolbar="true"] & {
top: calc(var(--zen-element-separation) / -2);
}
left: 50%;
transform: translate(-50%, -50%) scale(0);
background: var(--zen-loading-progress-bar-color);

View File

@@ -293,7 +293,7 @@
}
#main-window[windowtype="navigator:browser"]:not([chromehidden~='toolbar']) {
min-height: 495px !important;
min-height: var(--zen-minimum-window-height, 495px) !important;
@media (-moz-windows-mica) or (-moz-platform: macos) or ((-moz-platform: linux) and
-moz-pref('zen.widget.linux.transparency')) {

View File

@@ -67,7 +67,7 @@ if (!Services.appinfo.inSafeMode) {
child: {
esModuleURI: "resource:///actors/ZenBoostsChild.sys.mjs",
events: {
DOMDocElementInserted: { capture: true },
DOMWindowCreated: {},
unload: {},
},
},

View File

@@ -3,6 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { ZenLittleWindow } from "resource:///modules/zen/ZenLittleWindow.sys.mjs";
export const ZenCustomizableUI = new (class {
constructor() {}
@@ -38,10 +39,15 @@ export const ZenCustomizableUI = new (class {
// We do not have access to the window object here
init(window) {
this.#initLittleWindow(window);
this.#addSidebarButtons(window);
this.#modifyToolbarButtons(window);
}
#initLittleWindow(window) {
ZenLittleWindow.onLittleWindow(window);
}
#addSidebarButtons(window) {
const kDefaultSidebarWidth =
AppConstants.platform === "macosx" ? "230px" : "186px";

View File

@@ -0,0 +1,152 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Generic searchable XUL <panel> driver. One instance owns one panel +
* its search input + list + (optional) no-results element, and exposes
* `populate(items)` and `open(anchor, options)`.
*
* Each item passed to `populate` is `{ label, render?, onPick }`:
* - label: string used for the data-label search filter.
* - render: optional () => Element factory. If omitted a bare hbox
* with a <label> is created.
* - onPick: callback invoked when the item is clicked or activated
* via Enter on the keyboard.
*
* The driver handles:
* - filtering by lowercased substring match against data-label;
* - arrow-key / Tab navigation with [selected="true"] highlight;
* - Enter to activate the highlighted item;
* - autofocus of the search input on popupshown;
* - cleanup of all listeners on popuphidden.
*/
export class ZenSearchPopup {
#panel = null;
#searchInput = null;
#list = null;
#noResults = null;
#itemSelector = ".zen-search-popup-item";
#items = [];
/**
* @param {object} aOptions
* @param {Element} aOptions.panel The <panel> XUL element.
* @param {Element} aOptions.searchInput The search <html:input>.
* @param {Element} aOptions.list The container holding items.
* @param {Element} [aOptions.noResults] Optional "no results" element.
* @param {string} [aOptions.itemSelector] Per-item selector. Default
* is `.zen-search-popup-item`; custom items must carry that class
* or override this option.
*/
constructor({ panel, searchInput, list, noResults, itemSelector }) {
this.#panel = panel;
this.#searchInput = searchInput;
this.#list = list;
this.#noResults = noResults;
if (itemSelector) this.#itemSelector = itemSelector;
}
populate(items) {
this.#items = items;
this.#list.innerHTML = "";
const doc = this.#panel.ownerDocument;
for (const item of items) {
let node;
if (typeof item.render === "function") {
node = item.render();
} else {
node = doc.createXULElement("hbox");
const label = doc.createXULElement("label");
label.setAttribute("value", item.label);
node.appendChild(label);
}
node.classList.add(this.#itemSelector.replace(/^\./, ""));
node.setAttribute("data-label", item.label);
node.addEventListener("click", () => {
this.#panel.hidePopup();
item.onPick?.(item);
});
this.#list.appendChild(node);
}
}
open(anchor, { position = "after_end", onShown, onHidden } = {}) {
if (!this.#panel || !this.#list) return;
this.#panel.hidden = false;
if (this.#searchInput) this.#searchInput.value = "";
if (this.#noResults) this.#noResults.hidden = true;
const doc = this.#panel.ownerDocument;
const sel = this.#itemSelector;
const onSearch = () => {
const query = (this.#searchInput?.value || "").toLowerCase();
let visible = 0;
for (const item of this.#list.querySelectorAll(sel)) {
const label = item.getAttribute("data-label")?.toLowerCase() || "";
const found = label.includes(query);
item.hidden = !found;
if (found) visible++;
}
if (this.#noResults) this.#noResults.hidden = visible > 0;
};
if (this.#searchInput) {
this.#searchInput.addEventListener("input", onSearch);
}
const onKeyDown = event => {
if (
event.key === "ArrowDown" ||
event.key === "ArrowUp" ||
event.key === "Tab"
) {
event.preventDefault();
const isUp =
event.key === "ArrowUp" || (event.key === "Tab" && event.shiftKey);
const items = Array.from(this.#list.querySelectorAll(sel)).filter(
it => !it.hidden
);
if (!items.length) return;
let index = items.indexOf(
this.#list.querySelector(`${sel}[selected="true"]`)
);
index = isUp
? (index - 1 + items.length) % items.length
: (index + 1) % items.length;
items.forEach(it => it.removeAttribute("selected"));
const target = items[index];
target.setAttribute("selected", "true");
target.scrollIntoView({ block: "nearest", behavior: "smooth" });
} else if (event.key === "Enter") {
const sel2 = this.#list.querySelector(`${sel}[selected="true"]`);
if (sel2) sel2.click();
}
};
doc.addEventListener("keydown", onKeyDown);
const onPanelShown = event => {
if (event.target !== this.#panel) return;
this.#searchInput?.focus();
this.#searchInput?.select?.();
onShown?.();
};
this.#panel.addEventListener("popupshown", onPanelShown);
const onPanelHidden = event => {
if (event.target !== this.#panel) return;
if (this.#searchInput) {
this.#searchInput.removeEventListener("input", onSearch);
}
doc.removeEventListener("keydown", onKeyDown);
this.#panel.removeEventListener("popupshown", onPanelShown);
this.#panel.removeEventListener("popuphidden", onPanelHidden);
onHidden?.();
};
this.#panel.addEventListener("popuphidden", onPanelHidden);
this.#panel.openPopup(anchor, position);
}
}

View File

@@ -136,6 +136,13 @@ document.addEventListener(
case "cmd_zenNewNavigatorUnsynced":
OpenBrowserWindow({ zenSyncedWindow: false });
break;
case "cmd_zenNewLittleWindow": {
const { ZenLittleWindow } = ChromeUtils.importESModule(
"resource:///modules/zen/ZenLittleWindow.sys.mjs"
);
ZenLittleWindow.openLittleWindow(window);
break;
}
case "cmd_zenNewLiveFolder": {
const { ZenLiveFoldersManager } = ChromeUtils.importESModule(
"resource:///modules/zen/ZenLiveFoldersManager.sys.mjs"

View File

@@ -158,6 +158,14 @@
) {
separation = 0;
}
// Little windows are visually a single floating bar; we never want
// chrome padding around them.
if (
document.documentElement.hasAttribute("zen-little-window") ||
window._zenStartupLittleWindow
) {
separation = 0;
}
// In order to still use it on fullscreen, even if it's 0px, add .1px (almost invisible)
separation = Math.max(kMinElementSeparation, separation);
document.documentElement.style.setProperty(

View File

@@ -126,6 +126,9 @@ window.gZenCompactModeManager = {
},
get shouldBeCompact() {
if (document.documentElement.hasAttribute("zen-little-window")) {
return false;
}
return !document.documentElement
.getAttribute("chromehidden")
?.includes("toolbar");

View File

@@ -3,6 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import { nsZenDOMOperatedFeature } from "chrome://browser/content/zen-components/ZenCommonUtils.mjs";
import { ZenSearchPopup } from "resource:///modules/ZenSearchPopup.sys.mjs";
function formatRelativeTime(timestamp) {
const now = Date.now();
@@ -42,6 +43,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
);
#popup = null;
#searchPopup = null;
#popupTimer = null;
#mouseTimer = null;
#lastHighlightedGroup = null;
@@ -188,15 +190,12 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
#initTabsPopup() {
this.#popup = document.getElementById("zen-folder-tabs-popup");
const search = this.#popup.querySelector("#zen-folder-tabs-list-search");
const tabsList = this.#popup.querySelector("#zen-folder-tabs-list");
search.addEventListener("input", () => {
const query = search.value.toLowerCase();
for (const item of tabsList.children) {
item.hidden = !item.getAttribute("data-label").includes(query);
}
this.#searchPopup = new ZenSearchPopup({
panel: this.#popup,
searchInput: this.#popup.querySelector("#zen-folder-tabs-list-search"),
list: this.#popup.querySelector("#zen-folder-tabs-list"),
noResults: document.getElementById("zen-folder-tabs-search-no-results"),
itemSelector: ".folders-tabs-list-item",
});
this.#popup.addEventListener("mouseover", () => {
@@ -788,93 +787,18 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
document.getElementById("zen-folder-tabs-search-no-results").hidden = true;
this.#populateTabsList(activeGroup);
const search = this.#popup.querySelector("#zen-folder-tabs-list-search");
document.l10n.setArgs(search, {
"folder-name": activeGroup.name,
});
const tabsList = this.#popup.querySelector("#zen-folder-tabs-list");
const onSearchInput = () => {
const query = search.value.toLowerCase();
let foundTabs = 0;
for (const item of tabsList.children) {
const found = item.getAttribute("data-label").includes(query);
item.hidden = !found;
if (found) {
foundTabs++;
}
}
document.getElementById("zen-folder-tabs-search-no-results").hidden =
foundTabs > 0;
};
search.addEventListener("input", onSearchInput);
const onKeyDown = event => {
// Arrow down and up to navigate through the list
if (
event.key === "ArrowDown" ||
event.key === "ArrowUp" ||
event.key === "Tab"
) {
event.preventDefault();
let isUp =
event.key === "ArrowUp" || (event.key === "Tab" && event.shiftKey);
const items = Array.from(tabsList.children).filter(
item => !item.hidden
);
if (items.length === 0) {
return;
}
let index = items.indexOf(
tabsList.querySelector(".folders-tabs-list-item[selected]")
);
if (!isUp) {
index = (index + 1) % items.length;
} else {
index = (index - 1 + items.length) % items.length;
}
items.forEach(item => item.removeAttribute("selected"));
const targetItem = items[index];
targetItem.setAttribute("selected", "true");
targetItem.scrollIntoView({ block: "start", behavior: "smooth" });
} else if (event.key === "Enter") {
// Enter to select the currently highlighted item
const highlightedItem = tabsList.querySelector(
".folders-tabs-list-item[selected]"
);
if (highlightedItem) {
highlightedItem.click();
}
}
};
document.addEventListener("keydown", onKeyDown);
document.l10n.setArgs(
this.#popup.querySelector("#zen-folder-tabs-list-search"),
{ "folder-name": activeGroup.name }
);
const target = event.target;
target.setAttribute("open", true);
const handlePopupHidden = event => {
if (event.target !== this.#popup) {
return;
}
search.value = "";
target.removeAttribute("open");
search.removeEventListener("input", onSearchInput);
document.removeEventListener("keydown", onKeyDown);
};
this.#popup.addEventListener(
"popupshown",
() => {
search.focus();
search.select();
},
{ once: true }
);
this.#popup.addEventListener("popuphidden", handlePopupHidden, {
once: true,
this.#searchPopup.open(target, {
position: this.#searchPopupOptions,
onHidden: () => target.removeAttribute("open"),
});
this.#popup.openPopup(target, this.#searchPopupOptions);
}
get #searchPopupOptions() {

View File

@@ -313,16 +313,18 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
// content process since it does not take into account scroll. This way, we can
// be sure that the coordinates are correct.
const tabPanelsRect = gBrowser.tabpanels.getBoundingClientRect();
const zoomLevel =
this.#currentParentTab?.linkedBrowser.browsingContext.fullZoom || 1;
const rect = new DOMRect(
data.clientX + tabPanelsRect.left,
data.clientY + tabPanelsRect.top,
data.width,
data.height
data.clientX / zoomLevel + tabPanelsRect.left,
data.clientY / zoomLevel + tabPanelsRect.top,
data.width / zoomLevel,
data.height / zoomLevel
);
return await this.#imageBitmapToObjectURL(
await window.browsingContext.currentWindowGlobal.drawSnapshot(
rect,
1,
zoomLevel,
"transparent",
undefined
)

View File

@@ -66,11 +66,13 @@ export class ZenGlanceChild extends JSWindowActorChild {
) {
rect = originalTargetRect;
}
// Change the rect to make sure we take into account zoom.
const zoom = this.browsingContext.fullZoom;
this.sendAsyncMessage("ZenGlance:RecordLinkClickData", {
clientX: rect.left,
clientY: rect.top,
width: rect.width,
height: rect.height,
clientX: rect.left * zoom,
clientY: rect.top * zoom,
width: rect.width * zoom,
height: rect.height * zoom,
});
}

View File

@@ -83,6 +83,7 @@
& label {
max-width: 4rem;
margin-left: 8px;
color: white;
}
& image {

View File

@@ -0,0 +1,6 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
category browser-before-ui-startup resource:///modules/zen/ZenKeyboardShortcuts.sys.mjs ZenKeyboardShortcuts.init
category browser-quit-application-granted resource:///modules/zen/ZenKeyboardShortcuts.sys.mjs ZenKeyboardShortcuts.uninit

View File

@@ -0,0 +1,134 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenGlobalShortcuts.h"
#include "mozilla/dom/Document.h"
#include "nsContentUtils.h"
#include "nsGlobalWindowOuter.h"
#include "nsIWindowMediator.h"
#include "nsPIDOMWindow.h"
#include "nsReadableUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
namespace zen {
ZenGlobalShortcuts* ZenGlobalShortcuts::sInstance = nullptr;
NS_IMPL_ISUPPORTS(ZenGlobalShortcuts, nsIZenGlobalShortcuts)
ZenGlobalShortcuts::ZenGlobalShortcuts() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!sInstance);
sInstance = this;
}
ZenGlobalShortcuts::~ZenGlobalShortcuts() {
MOZ_ASSERT(NS_IsMainThread());
for (auto& reg : mRegistrations) {
NativeUnregister(reg);
}
mRegistrations.Clear();
NativeShutdown();
sInstance = nullptr;
}
NS_IMETHODIMP
ZenGlobalShortcuts::RegisterShortcut(const nsACString& aId,
const nsACString& aKey,
uint32_t aModifiers, bool* aRetVal) {
MOZ_ASSERT(NS_IsMainThread());
*aRetVal = false;
for (const auto& reg : mRegistrations) {
if (reg.id.Equals(aId)) return NS_ERROR_ALREADY_INITIALIZED;
}
Registration reg;
reg.id = aId;
reg.internalId = mNextInternalId++;
if (NS_FAILED(NativeRegister(reg, aKey, aModifiers))) {
return NS_OK;
}
mRegistrations.AppendElement(std::move(reg));
*aRetVal = true;
return NS_OK;
}
NS_IMETHODIMP
ZenGlobalShortcuts::UnregisterShortcut(const nsACString& aId) {
MOZ_ASSERT(NS_IsMainThread());
for (size_t i = 0; i < mRegistrations.Length(); ++i) {
if (mRegistrations[i].id.Equals(aId)) {
NativeUnregister(mRegistrations[i]);
mRegistrations.RemoveElementAt(i);
return NS_OK;
}
}
return NS_OK;
}
NS_IMETHODIMP
ZenGlobalShortcuts::UnregisterAll() {
MOZ_ASSERT(NS_IsMainThread());
for (auto& reg : mRegistrations) {
NativeUnregister(reg);
}
mRegistrations.Clear();
return NS_OK;
}
const ZenGlobalShortcuts::Registration* ZenGlobalShortcuts::FindByInternalId(
uint32_t aInternalId) const {
for (const auto& reg : mRegistrations) {
if (reg.internalId == aInternalId) return &reg;
}
return nullptr;
}
// static
void ZenGlobalShortcuts::OnNativeShortcut(uint32_t aInternalId) {
if (!NS_IsMainThread()) {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"ZenGlobalShortcuts::OnNativeShortcut",
[aInternalId]() { OnNativeShortcut(aInternalId); }));
return;
}
if (!sInstance) return;
const Registration* reg = sInstance->FindByInternalId(aInternalId);
if (!reg) return;
DispatchEventForId(reg->id);
}
// static
void ZenGlobalShortcuts::DispatchEventForId(const nsACString& aId) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIWindowMediator> med = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
if (!med) return;
nsCOMPtr<mozIDOMWindowProxy> mostRecent;
med->GetMostRecentBrowserWindow(getter_AddRefs(mostRecent));
if (!mostRecent) return;
nsCOMPtr<nsPIDOMWindowOuter> outer = nsPIDOMWindowOuter::From(mostRecent);
if (!outer) return;
RefPtr<mozilla::dom::Document> doc = outer->GetExtantDoc();
if (!doc) return;
nsAutoString eventName;
eventName.AssignLiteral(u"zen-global-shortcut-");
AppendUTF8toUTF16(aId, eventName);
nsContentUtils::DispatchTrustedEvent(doc, nsGlobalWindowOuter::Cast(outer),
eventName, mozilla::CanBubble::eYes,
mozilla::Cancelable::eNo);
}
} // namespace zen

View File

@@ -0,0 +1,61 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ZenGlobalShortcuts_h_
#define mozilla_ZenGlobalShortcuts_h_
#include "nsIZenGlobalShortcuts.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsTArray.h"
namespace zen {
/**
* @brief Singleton XPCOM service that registers OS-level global hotkeys
* and dispatches a trusted DOM event on the most recently focused
* browser window when one fires.
*/
class ZenGlobalShortcuts final : public nsIZenGlobalShortcuts {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIZENGLOBALSHORTCUTS
ZenGlobalShortcuts();
// Per-shortcut record. Public so the per-OS backend can read/write its
// fields directly without going through accessors.
struct Registration {
nsCString id;
uint32_t internalId = 0;
void* nativeHandle = nullptr;
};
// Called by the per-OS layer when a registered shortcut is triggered
// by the system. Safe to call from any thread; bounces to the main
// thread before touching DOM state.
static void OnNativeShortcut(uint32_t aInternalId);
private:
~ZenGlobalShortcuts();
static ZenGlobalShortcuts* sInstance;
const Registration* FindByInternalId(uint32_t aInternalId) const;
static void DispatchEventForId(const nsACString& aId);
// Per-OS implementations live in cocoa/, windows/, or the stub.
static nsresult NativeRegister(Registration& aReg, const nsACString& aKey,
uint32_t aModifiers);
static void NativeUnregister(Registration& aReg);
static void NativeShutdown();
nsTArray<Registration> mRegistrations;
uint32_t mNextInternalId = 1;
};
} // namespace zen
#endif

View File

@@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenGlobalShortcuts.h"
// Linux/other-toolkit fallback. A real implementation needs X11
// XGrabKey on the root window or, on Wayland, the
// org.freedesktop.portal.GlobalShortcuts portal over D-Bus. Until one
// is added, registrations always fail and JS-side code can fall back
// to in-window shortcuts.
namespace zen {
// static
nsresult ZenGlobalShortcuts::NativeRegister(Registration&, const nsACString&,
uint32_t) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// static
void ZenGlobalShortcuts::NativeUnregister(Registration&) {}
// static
void ZenGlobalShortcuts::NativeShutdown() {}
} // namespace zen

View File

@@ -0,0 +1,210 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenGlobalShortcuts.h"
#include "mozilla/TextEvents.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#import <Carbon/Carbon.h>
namespace zen {
namespace {
using mozilla::CodeNameIndex;
using mozilla::WidgetKeyboardEvent;
constexpr FourCharCode kZenHotKeySignature = 'zen ';
// Mozilla-internal aliases referenced by NativeKeyToDOMCodeName.inc but
// not part of Carbon. Mirrors widget/cocoa/TextInputHandler.h so we
// don't need to drag the whole header in.
enum {
kVK_PC_ContextMenu = 0x6E,
kVK_Powerbook_KeypadEnter = 0x34,
};
class MacGlobalShortcuts final {
public:
MacGlobalShortcuts() = delete;
static nsresult Register(ZenGlobalShortcuts::Registration& aReg,
const nsACString& aKey, uint32_t aModifiers);
static void Unregister(ZenGlobalShortcuts::Registration& aReg);
static void Shutdown();
private:
static bool EnsureHandler();
static OSStatus HandleHotKey(EventHandlerCallRef, EventRef, void*);
static bool ResolveKey(const nsACString& aKey, UInt32& aOut);
static UInt32 ToCarbonModifiers(uint32_t aMods);
static EventHandlerUPP sUPP;
static EventHandlerRef sHandler;
};
EventHandlerUPP MacGlobalShortcuts::sUPP = nullptr;
EventHandlerRef MacGlobalShortcuts::sHandler = nullptr;
// static
OSStatus MacGlobalShortcuts::HandleHotKey(EventHandlerCallRef, EventRef inEvent,
void*) {
EventHotKeyID hkID;
if (GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID,
nullptr, sizeof(hkID), nullptr, &hkID) == noErr) {
ZenGlobalShortcuts::OnNativeShortcut(hkID.id);
}
return noErr;
}
// static
bool MacGlobalShortcuts::EnsureHandler() {
if (sHandler) return true;
sUPP = NewEventHandlerUPP(HandleHotKey);
if (!sUPP) return false;
EventTypeSpec spec = {kEventClassKeyboard, kEventHotKeyPressed};
OSStatus status =
InstallApplicationEventHandler(sUPP, 1, &spec, nullptr, &sHandler);
if (status != noErr) {
DisposeEventHandlerUPP(sUPP);
sUPP = nullptr;
sHandler = nullptr;
return false;
}
return true;
}
// Convert the JS-friendly key string into a DOM code-name (e.g. "A" ->
// "KeyA", "5" -> "Digit5", "F1"/"f1" -> "F1", "Space"/"space" -> "Space").
// Returns false for inputs we don't accept.
static bool ToDOMCodeName(const nsACString& aKey, nsAString& aOut) {
aOut.Truncate();
if (aKey.Length() == 1) {
char c = aKey[0];
if (c >= 'a' && c <= 'z') c = char(c - 32);
if (c >= 'A' && c <= 'Z') {
aOut.AssignLiteral(u"Key");
} else if (c >= '0' && c <= '9') {
aOut.AssignLiteral(u"Digit");
} else {
return false;
}
aOut.Append(char16_t(c));
return true;
}
// Multi-character: assume it's a DOM code name, normalized to leading
// upper-case ("space" -> "Space", "f1" -> "F1").
AppendUTF8toUTF16(aKey, aOut);
if (!aOut.IsEmpty() && aOut[0] >= 'a' && aOut[0] <= 'z') {
aOut.BeginWriting()[0] = char16_t(aOut[0] - 32);
}
return true;
}
struct CodeIndexToMacKey {
CodeNameIndex idx;
UInt32 keyCode;
};
// Generated from widget's mapping table. Order matches the .inc, so when
// multiple native keys map to the same DOM code (e.g. NumpadEnter ->
// kVK_ANSI_KeypadEnter and kVK_Powerbook_KeypadEnter), the first entry
// wins -- which is the one we'd want to pass to RegisterEventHotKey.
static constexpr CodeIndexToMacKey kCodeIndexToMacKeyTable[] = {
#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
{mozilla::aCodeNameIndex, static_cast<UInt32>(aNativeKey)},
#include "NativeKeyToDOMCodeName.inc"
#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
};
// static
bool MacGlobalShortcuts::ResolveKey(const nsACString& aKey, UInt32& aOut) {
nsAutoString domCode;
if (!ToDOMCodeName(aKey, domCode)) return false;
CodeNameIndex idx = WidgetKeyboardEvent::GetCodeNameIndex(domCode);
if (idx == mozilla::CODE_NAME_INDEX_USE_STRING) return false;
for (const auto& entry : kCodeIndexToMacKeyTable) {
if (entry.idx == idx) {
aOut = entry.keyCode;
return true;
}
}
return false;
}
// static
UInt32 MacGlobalShortcuts::ToCarbonModifiers(uint32_t aMods) {
UInt32 m = 0;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_SHIFT) m |= shiftKey;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_CTRL) m |= controlKey;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_ALT) m |= optionKey;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_META) m |= cmdKey;
return m;
}
// static
nsresult MacGlobalShortcuts::Register(ZenGlobalShortcuts::Registration& aReg,
const nsACString& aKey,
uint32_t aModifiers) {
if (!EnsureHandler()) return NS_ERROR_FAILURE;
UInt32 keyCode;
if (!ResolveKey(aKey, keyCode)) return NS_ERROR_INVALID_ARG;
EventHotKeyID hkID;
hkID.signature = kZenHotKeySignature;
hkID.id = aReg.internalId;
EventHotKeyRef ref = nullptr;
OSStatus status =
RegisterEventHotKey(keyCode, ToCarbonModifiers(aModifiers), hkID,
GetApplicationEventTarget(), 0, &ref);
if (status != noErr || !ref) return NS_ERROR_FAILURE;
aReg.nativeHandle = static_cast<void*>(ref);
return NS_OK;
}
// static
void MacGlobalShortcuts::Unregister(ZenGlobalShortcuts::Registration& aReg) {
if (!aReg.nativeHandle) return;
UnregisterEventHotKey(static_cast<EventHotKeyRef>(aReg.nativeHandle));
aReg.nativeHandle = nullptr;
}
// static
void MacGlobalShortcuts::Shutdown() {
if (sHandler) {
RemoveEventHandler(sHandler);
sHandler = nullptr;
}
if (sUPP) {
DisposeEventHandlerUPP(sUPP);
sUPP = nullptr;
}
}
} // namespace
// static
nsresult ZenGlobalShortcuts::NativeRegister(Registration& aReg,
const nsACString& aKey,
uint32_t aModifiers) {
return MacGlobalShortcuts::Register(aReg, aKey, aModifiers);
}
// static
void ZenGlobalShortcuts::NativeUnregister(Registration& aReg) {
MacGlobalShortcuts::Unregister(aReg);
}
// static
void ZenGlobalShortcuts::NativeShutdown() { MacGlobalShortcuts::Shutdown(); }
} // namespace zen

View File

@@ -0,0 +1,18 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
FINAL_LIBRARY = "xul"
SOURCES += [
"ZenGlobalShortcutsCocoa.mm",
]
LOCAL_INCLUDES += [
"../",
"/widget",
]
OS_LIBS += [
"-framework Carbon",
]

View File

@@ -0,0 +1,14 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Classes = [
{
'cid': '{b8e9f3a2-7c1d-4a5b-9e6f-3d8c2a1b5e74}',
'interfaces': ['nsIZenGlobalShortcuts'],
'contract_ids': ['@mozilla.org/zen/global-shortcuts;1'],
'type': 'zen::ZenGlobalShortcuts',
'headers': ['mozilla/ZenGlobalShortcuts.h'],
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
},
]

View File

@@ -0,0 +1,29 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
"nsIZenGlobalShortcuts.idl",
]
EXPORTS.mozilla += [
"ZenGlobalShortcuts.h",
]
SOURCES += [
"ZenGlobalShortcuts.cpp",
]
XPCOM_MANIFESTS += [
"components.conf",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
DIRS += ["cocoa"]
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
DIRS += ["windows"]
else:
SOURCES += ["ZenGlobalShortcutsStub.cpp"]
FINAL_LIBRARY = "xul"
XPIDL_MODULE = "zen_global_shortcuts"

View File

@@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
/**
* @brief OS-level global keyboard shortcut registration for Zen.
*
* Registers shortcuts with the operating system so that pressing the
* key combination triggers a callback even when no Zen window is
* focused (or the application is in the background). When a shortcut
* fires, a trusted DOM event is dispatched on the most recently
* focused browser window. The event type is
* "zen-global-shortcut-<id>", where <id> is the identifier passed at
* registration time.
*/
[scriptable, uuid(b8e9f3a2-7c1d-4a5b-9e6f-3d8c2a1b5e74)]
interface nsIZenGlobalShortcuts : nsISupports {
const unsigned long MODIFIER_NONE = 0;
const unsigned long MODIFIER_SHIFT = 1;
const unsigned long MODIFIER_CTRL = 2;
const unsigned long MODIFIER_ALT = 4;
const unsigned long MODIFIER_META = 8;
/**
* @brief Register a global keyboard shortcut.
* @param aId Caller-chosen identifier; the dispatched event name will be
* "zen-global-shortcut-" + aId. Must be unique across active
* registrations.
* @param aKey Key name. Supported: "A".."Z", "0".."9", "F1".."F12",
* "Space".
* @param aModifiers Bitmask of MODIFIER_* constants. On macOS, META
* is Command; on Windows, META is the Windows key.
* @return true if the OS accepted the registration.
*/
boolean registerShortcut(in ACString aId, in ACString aKey,
in unsigned long aModifiers);
/**
* @brief Unregister a previously registered shortcut by id.
*/
void unregisterShortcut(in ACString aId);
/**
* @brief Unregister all shortcuts registered through this service.
*/
void unregisterAll();
};

View File

@@ -0,0 +1,166 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ZenGlobalShortcuts.h"
#include "nsString.h"
#include <windows.h>
namespace zen {
namespace {
constexpr wchar_t kWindowClassName[] = L"ZenGlobalShortcutsWindow";
class WinGlobalShortcuts final {
public:
WinGlobalShortcuts() = delete;
static nsresult Register(ZenGlobalShortcuts::Registration& aReg,
const nsACString& aKey, uint32_t aModifiers);
static void Unregister(ZenGlobalShortcuts::Registration& aReg);
static void Shutdown();
private:
static bool EnsureWindow();
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static bool ResolveKey(const nsACString& aKey, UINT& aOut);
static UINT ToWinModifiers(uint32_t aMods);
static HWND sWindow;
static ATOM sClass;
};
HWND WinGlobalShortcuts::sWindow = nullptr;
ATOM WinGlobalShortcuts::sClass = 0;
// static
LRESULT CALLBACK WinGlobalShortcuts::WndProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
if (msg == WM_HOTKEY) {
ZenGlobalShortcuts::OnNativeShortcut(static_cast<uint32_t>(wParam));
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
// static
bool WinGlobalShortcuts::EnsureWindow() {
if (sWindow) return true;
HINSTANCE module = GetModuleHandleW(nullptr);
if (!sClass) {
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = WndProc;
wc.hInstance = module;
wc.lpszClassName = kWindowClassName;
sClass = RegisterClassExW(&wc);
if (!sClass) return false;
}
sWindow = CreateWindowExW(0, kWindowClassName, L"", 0, 0, 0, 0, 0,
HWND_MESSAGE, nullptr, module, nullptr);
return sWindow != nullptr;
}
// static
bool WinGlobalShortcuts::ResolveKey(const nsACString& aKey, UINT& aOut) {
if (aKey.Length() == 1) {
char c = aKey[0];
if (c >= 'a' && c <= 'z') c = char(c - 32);
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
aOut = static_cast<UINT>(c);
return true;
}
return false;
}
if (aKey.LowerCaseEqualsLiteral("space")) {
aOut = VK_SPACE;
return true;
}
if ((aKey.Length() == 2 || aKey.Length() == 3) &&
(aKey[0] == 'F' || aKey[0] == 'f')) {
int n = aKey[1] - '0';
if (n < 0 || n > 9) return false;
if (aKey.Length() == 3) {
int d = aKey[2] - '0';
if (d < 0 || d > 9) return false;
n = n * 10 + d;
}
if (n >= 1 && n <= 12) {
aOut = VK_F1 + (n - 1);
return true;
}
}
return false;
}
// static
UINT WinGlobalShortcuts::ToWinModifiers(uint32_t aMods) {
UINT m = MOD_NOREPEAT;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_SHIFT) m |= MOD_SHIFT;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_CTRL) m |= MOD_CONTROL;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_ALT) m |= MOD_ALT;
if (aMods & nsIZenGlobalShortcuts::MODIFIER_META) m |= MOD_WIN;
return m;
}
// static
nsresult WinGlobalShortcuts::Register(ZenGlobalShortcuts::Registration& aReg,
const nsACString& aKey,
uint32_t aModifiers) {
if (!EnsureWindow()) return NS_ERROR_FAILURE;
UINT vk;
if (!ResolveKey(aKey, vk)) return NS_ERROR_INVALID_ARG;
if (!RegisterHotKey(sWindow, static_cast<int>(aReg.internalId),
ToWinModifiers(aModifiers), vk)) {
return NS_ERROR_FAILURE;
}
aReg.nativeHandle =
reinterpret_cast<void*>(static_cast<uintptr_t>(aReg.internalId));
return NS_OK;
}
// static
void WinGlobalShortcuts::Unregister(ZenGlobalShortcuts::Registration& aReg) {
if (!sWindow || !aReg.nativeHandle) return;
UnregisterHotKey(
sWindow,
static_cast<int>(reinterpret_cast<uintptr_t>(aReg.nativeHandle)));
aReg.nativeHandle = nullptr;
}
// static
void WinGlobalShortcuts::Shutdown() {
if (sWindow) {
DestroyWindow(sWindow);
sWindow = nullptr;
}
if (sClass) {
UnregisterClassW(kWindowClassName, GetModuleHandleW(nullptr));
sClass = 0;
}
}
} // namespace
// static
nsresult ZenGlobalShortcuts::NativeRegister(Registration& aReg,
const nsACString& aKey,
uint32_t aModifiers) {
return WinGlobalShortcuts::Register(aReg, aKey, aModifiers);
}
// static
void ZenGlobalShortcuts::NativeUnregister(Registration& aReg) {
WinGlobalShortcuts::Unregister(aReg);
}
// static
void ZenGlobalShortcuts::NativeShutdown() { WinGlobalShortcuts::Shutdown(); }
} // namespace zen

View File

@@ -0,0 +1,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
FINAL_LIBRARY = "xul"
SOURCES += [
"ZenGlobalShortcutsWindows.cpp",
]
LOCAL_INCLUDES += [
"../",
]

11
src/zen/kbs/moz.build Normal file
View File

@@ -0,0 +1,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES.zen += [
"ZenKeyboardShortcuts.sys.mjs",
]
DIRS += [
"global-shortcuts",
]

View File

@@ -0,0 +1,6 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
category browser-before-ui-startup resource:///modules/zen/ZenLittleWindow.sys.mjs ZenLittleWindow.init
category browser-quit-application-granted resource:///modules/zen/ZenLittleWindow.sys.mjs ZenLittleWindow.uninit

View File

@@ -0,0 +1,141 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { ZenSpacesSearch } from "resource:///modules/zen/ZenSpacesSearch.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
lazy,
"ZenWindowControl",
"@mozilla.org/zen/window-control;1",
Ci.nsIZenWindowControl
);
const URLBAR_HEIGHT = 340;
const URLBAR_WIDTH = 640;
const FEATURES =
"titlebar,close,toolbar,location,personalbar=no,status,menubar=no," +
`resizable,minimizable,scrollbars,width=${URLBAR_WIDTH},height=${URLBAR_HEIGHT},centerscreen`;
class nsZenLittleWindow {
init() {}
uninit() {}
/**
* Open a fresh little window, or focus an existing empty one if there
* already is a little window sitting on its empty tab.
*
* @param {Window} opener The browser window asking for the little window.
* @returns {Window|null} The window that received focus.
*/
openLittleWindow(opener) {
for (const win of this.#iterLittleWindows()) {
if (this.#isOnEmptyTab(win)) {
win.focus();
return win;
}
}
let win = opener.OpenBrowserWindow({
zenLittleWindow: true,
all: false,
features: FEATURES,
});
win.windowUtils.suppressAnimation(true);
// Hide the OS-level window until the floating urlbar is ready, so the
// user never sees a half-laid-out chrome flash on top.
lazy.ZenWindowControl.hide(win);
return win;
}
#isLittleWindow(win) {
return (
!!win._zenStartupLittleWindow ||
win.document?.documentElement?.hasAttribute("zen-little-window")
);
}
#isOnEmptyTab(win) {
const tab = win.gBrowser?.selectedTab;
return !!tab?.hasAttribute("zen-empty-tab");
}
*#iterLittleWindows() {
const en = Services.wm.getEnumerator("navigator:browser");
while (en.hasMoreElements()) {
const win = en.getNext();
if (!win.closed && this.#isLittleWindow(win)) {
yield win;
}
}
}
onLittleWindow(win) {
if (!this.#isLittleWindow(win)) {
return;
}
ZenSpacesSearch.init(win);
const observer = new win.ResizeObserver(entries => {
if (win.closed) {
return;
}
for (const entry of entries) {
if (entry.target.id === "urlbar") {
const { width, height } = entry.target.getBoundingClientRect();
win.resizeTo(width, height);
}
}
});
const onClosed = event => {
observer.disconnect();
if (!win.closed && !event.detail?.onElementPicked) {
lazy.ZenWindowControl.hide(win);
win.close();
} else {
const [width, height] = [1000, 600];
win.setResizable(true);
win.resizeTo(1000, 600);
win.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow)
.center(null, true, true)
}
};
const urlbar = win.gURLBar;
observer.observe(urlbar);
// TODO: Handle window blur event
win.setResizable(false);
win.addEventListener(
"ZenFloatingURLBarOpened",
() => {
win.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow)
.center(null, true, true)
if (AppConstants.platform == "macosx" && !Services.focus.activeWindow) {
Cc["@mozilla.org/widget/macdocksupport;1"]
.getService(Ci.nsIMacDockSupport)
.activateApplication(true);
}
win.focus();
urlbar.focus();
},
{ once: true }
);
win.addEventListener("ZenURLBarClosed", onClosed, { once: true });
win.addEventListener("unload", () => observer.disconnect(), { once: true });
// Hacky, but used to prevent flashing and still being able to render
lazy.ZenWindowControl.show(win);
lazy.ZenWindowControl.hide(win);
win.gZenWorkspaces.promiseInitialized.then(() => {
win.windowUtils.suppressAnimation(false);
lazy.ZenWindowControl.show(win);
});
}
}
export const ZenLittleWindow = new nsZenLittleWindow();

View File

@@ -0,0 +1,142 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { ZenSearchPopup } from "resource:///modules/ZenSearchPopup.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
});
/*
* Owns the "send to a synced window" split-button that lives in the
* nav-bar of a little window. Main click opens the urlbar's current
* value in a fresh synced browser window using the active workspace;
* the dropdown opens a ZenSearchPopup over #zen-spaces-popup so the
* user can pick a different workspace to land in.
*/
class ZenSpacesSearchService {
/**
* Per-window setup.
* @param {Window} aWindow A little window.
*/
init(aWindow) {
if (!aWindow || aWindow._zenSpacesSearchInited) return;
aWindow._zenSpacesSearchInited = true;
const doc = aWindow.document;
const panel = doc.getElementById("zen-spaces-popup");
if (!panel) return;
const popup = new ZenSearchPopup({
panel,
searchInput: doc.getElementById("zen-spaces-list-search"),
list: doc.getElementById("zen-spaces-list"),
noResults: doc.getElementById("zen-spaces-search-no-results"),
itemSelector: ".zen-spaces-list-item",
});
const parts = this.#injectButton(aWindow);
if (!parts) return;
const { button, main, dropmarker } = parts;
main.addEventListener("click", event => {
if (event.button !== 0) return;
this.#openInWorkspace(aWindow, null);
});
dropmarker.addEventListener("click", event => {
if (event.button !== 0) return;
event.stopPropagation();
this.#openSpacesPopup(aWindow, popup, button);
});
}
#injectButton(aWindow) {
const doc = aWindow.document;
const target = doc.getElementById("nav-bar-customization-target");
if (!target) return null;
const button = doc.createXULElement("hbox");
button.id = "zen-little-window-send-to-window";
button.setAttribute("removable", "false");
const main = doc.createXULElement("hbox");
main.classList.add("zen-stw-main");
const prefix = doc.createXULElement("label");
prefix.classList.add("zen-stw-prefix");
prefix.setAttribute(
"data-l10n-id",
"zen-little-window-send-to-window-prefix"
);
const spaceName = doc.createXULElement("label");
spaceName.classList.add("zen-stw-space-name");
spaceName.setAttribute(
"value",
aWindow.gZenWorkspaces?.getActiveWorkspaceFromCache?.()?.name || ""
);
main.appendChild(prefix);
main.appendChild(spaceName);
const separator = doc.createXULElement("hbox");
separator.classList.add("zen-stw-separator");
const dropmarker = doc.createXULElement("hbox");
dropmarker.classList.add("zen-stw-dropmarker");
const dropIcon = doc.createXULElement("image");
dropIcon.classList.add("zen-stw-dropmarker-icon");
dropmarker.appendChild(dropIcon);
button.appendChild(main);
button.appendChild(separator);
button.appendChild(dropmarker);
target.appendChild(button);
return { button, main, dropmarker, spaceName };
}
#openSpacesPopup(aWindow, popup, anchor) {
const workspaces = lazy.ZenSessionStore.getClonedSpaces();
popup.populate(
workspaces.map(space => ({
label: space.name || space.uuid,
render: () => {
const node = aWindow.document.createXULElement("hbox");
const label = aWindow.document.createXULElement("label");
label.setAttribute("value", space.name || space.uuid);
label.classList.add("zen-spaces-list-item-label");
node.appendChild(label);
return node;
},
onPick: () => this.#openInWorkspace(aWindow, space.uuid),
}))
);
popup.open(anchor);
}
#openInWorkspace(aWindow, workspaceUuid) {
const url = aWindow.gURLBar?.value?.trim();
if (!url) return;
const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
const urlString = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
urlString.data = url;
args.appendElement(urlString);
const opts = { args, zenSyncedWindow: true };
if (workspaceUuid) opts.zenInitialWorkspace = workspaceUuid;
const newWin = aWindow.OpenBrowserWindow(opts);
if (newWin) aWindow.close();
}
}
export const ZenSpacesSearch = new ZenSpacesSearchService();

View File

@@ -0,0 +1,5 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
content/browser/zen-styles/zen-little-window.css (../../zen/little-window/zen-little-window.css)

View File

@@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES.zen += [
"ZenLittleWindow.sys.mjs",
"ZenSpacesSearch.sys.mjs",
]

View File

@@ -0,0 +1,162 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Little-window chrome: the URL bar fills the entire window. The vertical
* tab strip, sidebar buttons, bookmarks toolbar, and any other chrome are
* suppressed so the window acts as a quick search/launch box.
*/
#zen-little-window-send-to-window {
align-items: stretch;
border-radius: var(--border-radius-small);
background: light-dark(white, rgba(0, 0, 0, 0.4));
height: 32px;
margin-inline: 4px;
overflow: hidden;
user-select: none;
align-self: center;
box-shadow: 0 0 1px 2px rgba(0, 0, 0, 0.01);
.zen-stw-main,
.zen-stw-dropmarker {
align-items: center;
padding-inline: 12px;
transition: background 80ms ease;
&:hover {
background: var(--toolbarbutton-active-background, color-mix(in srgb, currentColor 12%, transparent));
}
}
.zen-stw-prefix {
color: color-mix(in srgb, currentColor 60%, transparent);
margin-inline-end: 6px;
}
.zen-stw-space-name {
color: var(--zen-primary-color, currentColor);
font-weight: 600;
}
.zen-stw-separator {
width: 1px;
background: color-mix(in srgb, currentColor 18%, transparent);
margin-block: 6px;
}
.zen-stw-dropmarker {
padding-inline: 8px;
}
.zen-stw-dropmarker-icon {
width: 12px;
height: 12px;
-moz-context-properties: fill;
fill: color-mix(in srgb, currentColor 65%, transparent);
list-style-image: url("chrome://global/skin/icons/arrow-down.svg");
}
}
#zen-spaces-popup {
--arrowpanel-padding: 0;
--zen-spaces-list-padding: 6px;
padding: var(--zen-spaces-list-padding);
min-width: 250px;
.zen-spaces-list-header {
display: flex;
flex-direction: row;
padding: 6px;
border-bottom: 1px solid color-mix(in srgb, currentColor, transparent 90%);
}
.zen-spaces-list-search-icon {
width: 14px;
height: 14px;
margin-inline: 4px 6px;
-moz-context-properties: fill;
fill: currentColor;
opacity: 0.6;
}
#zen-spaces-list-search {
flex: 1;
background: transparent;
border: none;
outline: none;
color: inherit;
font: inherit;
}
.zen-spaces-list-item {
padding: 6px 10px;
border-radius: 6px;
cursor: pointer;
&:hover,
&[selected="true"] {
background: var(--toolbarbutton-hover-background);
}
&[active="true"]::after {
content: "•";
margin-inline-start: auto;
opacity: 0.7;
}
}
#zen-spaces-search-no-results {
padding: 12px;
opacity: 0.7;
justify-content: center;
}
}
:root[zen-little-window="true"] {
toolbarspring[cui-areatype="toolbar"],
#nav-bar-customization-target > .toolbarbutton-1[disabled="true"] {
display: none;
}
&[zen-has-empty-tab="true"] {
/* Keep in sync with URLBAR_HEIGHT in ZenLittleWindow.sys.mjs */
--zen-minimum-window-height: 40px;
min-width: unset !important;
#zen-appcontent-wrapper {
visibility: hidden;
}
#urlbar[breakout-extend] {
min-width: 600px !important;
max-width: 600px !important;
left: 50% !important;
top: 0 !important;
transform: translate(10px, 0) !important;
visibility: visible;
& .urlbar-background {
outline: none !important;
}
& .urlbar-input-container {
-moz-window-dragging: drag;
}
& .urlbar-input-box {
-moz-window-dragging: no-drag;
}
}
}
@media (-moz-platform: macos) {
#nav-bar {
padding-inline-start: 6px;
}
}
#urlbar-container {
--border-radius-medium: var(--border-radius-small);
}
}

View File

@@ -34,7 +34,11 @@ export class nsGithubLiveFolderProvider extends nsZenLiveFolderProvider {
if (
this.state.type === "pull-requests" &&
typeof this.state.isJsonApi !== "boolean"
typeof this.state.isJsonApi !== "boolean" &&
!Services.prefs.getBoolPref(
"zen.live-folders.github.skip-new-pr-ui-check",
false
)
) {
const { text, status } = await this.fetch(this.state.url, {
headers: {

View File

@@ -62,6 +62,14 @@ export class nsRssLiveFolderProvider extends nsZenLiveFolderProvider {
if (!item.url || !item.date) {
return false;
}
try {
const parsed = Services.io.newURI(item.url);
if (parsed.scheme !== "http" && parsed.scheme !== "https") {
return false;
}
} catch {
return false;
}
if (!this.state.timeRange) {
return true;
}
@@ -73,10 +81,9 @@ export class nsRssLiveFolderProvider extends nsZenLiveFolderProvider {
for (let item of items) {
if (item.url) {
try {
const url = new URL(item.url);
const favicon = await lazy.PlacesUtils.favicons.getFaviconForPage(
Services.io.newURI(url.href)
);
const url = Services.io.newURI(item.url);
const favicon =
await lazy.PlacesUtils.favicons.getFaviconForPage(url);
item.icon =
favicon?.dataURI.spec ||
this.manager.window.gZenEmojiPicker.getSVGURL("logo-rss.svg");

View File

@@ -11,6 +11,8 @@ DIRS += [
"common",
"drag-and-drop",
"glance",
"kbs",
"little-window",
"live-folders",
"mods",
"tests",

View File

@@ -97,11 +97,11 @@ class nsZenWindowSync {
};
/**
* Promise that resolves when the current docshell swap operation is finished.
* Promise|null 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();
#docShellSwitchPromise = null;
/**
* Map of sync handlers for different event types.
@@ -206,6 +206,13 @@ class nsZenWindowSync {
* @param {Window} aWindow - The browser window that is about to be shown.
*/
#onWindowBeforeShow(aWindow) {
if (aWindow._zenStartupLittleWindow) {
aWindow.document.documentElement.setAttribute(
"zen-little-window",
"true"
);
delete aWindow._zenStartupLittleWindow;
}
if (
aWindow.gZenWindowSync ||
aWindow.document.documentElement.hasAttribute("zen-unsynced-window")
@@ -1485,18 +1492,29 @@ class nsZenWindowSync {
) {
return;
}
let promise = this.#docShellSwitchPromise;
if (this.#docShellSwitchPromise) {
return;
}
const onTabSelect = event => {
if (event.detail?.previousTab === event.target) {
return;
}
this.#lastSelectedTab = null;
this.on_TabSelect(event, { ignorePromise: true });
};
this.#lastFocusedWindow = new WeakRef(window);
this.#lastSelectedTab = new WeakRef(window.gBrowser.selectedTab);
window.addEventListener("TabSelect", onTabSelect, { once: true });
// eslint-disable-next-line no-async-promise-executor
this.#docShellSwitchPromise = new Promise(async resolve => {
await promise;
await this.#onTabSwitchOrWindowFocus(window);
window.removeEventListener("TabSelect", onTabSelect);
resolve();
this.#docShellSwitchPromise = null;
});
}
on_TabSelect(aEvent) {
on_TabSelect(aEvent, { ignorePromise = false } = {}) {
const tab = aEvent.target;
if (this.#lastSelectedTab?.deref() === tab) {
return;
@@ -1504,11 +1522,15 @@ class nsZenWindowSync {
this.#lastSelectedTab = new WeakRef(tab);
const previousTab = aEvent.detail.previousTab;
let promise = this.#docShellSwitchPromise;
if (promise && !ignorePromise) {
return;
}
// eslint-disable-next-line no-async-promise-executor
this.#docShellSwitchPromise = new Promise(async resolve => {
await promise;
await this.#onTabSwitchOrWindowFocus(tab.ownerGlobal, previousTab);
resolve();
this.#docShellSwitchPromise = null;
});
}

View File

@@ -148,6 +148,28 @@ class nsZenWorkspaceCreation extends MozXULElement {
this.createButton.disabled = !this.inputName.value.trim();
});
this.inputName.addEventListener("keydown", event => {
if (event.key === "Enter") {
event.preventDefault();
event.stopPropagation();
if (!this.createButton.disabled) {
this.createButton.doCommand();
}
}
});
// Bound on the root so Esc works regardless of which child has focus
// (name input, icon picker trigger, profile button, primary button).
// Open popups consume Esc before it reaches us, so the emoji/profile
// pickers still close as expected.
this.addEventListener("keydown", event => {
if (event.key === "Escape") {
event.preventDefault();
event.stopPropagation();
this.cancelButton.doCommand();
}
});
this.inputIcon.addEventListener("command", this.onIconCommand.bind(this));
this.profilesPopup = this.querySelector(

View File

@@ -137,8 +137,6 @@ class nsZenWorkspaces {
document.documentElement.setAttribute("zen-private-window", "true");
}
this.popupOpenHandler = this._popupOpenHandler.bind(this);
window.addEventListener("resize", this.onWindowResize.bind(this));
this.addPopupListeners();
@@ -599,16 +597,6 @@ class nsZenWorkspaces {
);
}
_popupOpenHandler() {
// If a popup is opened, we should stop the swipe gesture
if (this._swipeManager?.isGestureActive) {
document.documentElement.removeAttribute("swipe-gesture");
gZenUIManager.tabsWrapper.style.removeProperty("scrollbar-width");
this.updateTabsContainers();
this._cancelSwipeAnimation();
}
}
get activeWorkspace() {
return this.#activeWorkspace;
}
@@ -1907,7 +1895,9 @@ class nsZenWorkspaces {
} = {}
) {
gZenUIManager.tabsWrapper.style.scrollbarWidth = "none";
const kGlobalAnimationDuration = 0.2;
const kGlobalAnimationDuration =
Services.prefs.getIntPref("zen.workspaces.switch-animation-duration") /
1000;
this._animatingChange = true;
const animations = [];
const workspaces = this.getWorkspaces();

View File

@@ -35,6 +35,19 @@ export class ZenSpacesSwipe {
}
this._attachWorkspaceSwipeGestures(element);
}
this._popupOpenHandler = this._popupOpenHandler.bind(this);
}
get #stripWidth() {
return (
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("navigator-toolbox")
).width +
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("zen-sidebar-splitter")
).width
);
}
_attachWorkspaceSwipeGestures(element) {
@@ -107,7 +120,7 @@ export class ZenSpacesSwipe {
gZenFolders.cancelPopupTimer();
document.documentElement.setAttribute("swipe-gesture", "true");
document.addEventListener("popupshown", ws.popupOpenHandler, {
document.addEventListener("popupshown", this._popupOpenHandler, {
once: true,
});
@@ -128,17 +141,16 @@ export class ZenSpacesSwipe {
return;
}
const stripWidth = this.#stripWidth;
event.preventDefault();
event.stopPropagation();
const stripWidth =
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("navigator-toolbox")
).width +
window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("zen-sidebar-splitter")
).width;
const delta = event.delta * stripWidth;
const delta =
event.delta *
Services.prefs.getIntPref(
"zen.workspaces.swipe-actions.delta-multiplier"
);
let translateX = this._swipeState.lastDelta + delta;
// Add a force multiplier as we are translating the strip depending on how close to the edge we are
let forceMultiplier = Math.min(
@@ -152,7 +164,8 @@ export class ZenSpacesSwipe {
translateX = this._swipeState.lastDelta;
}
if (Math.abs(delta) > 0.8) {
if (Math.abs(delta) > 0.9) {
delete ws._hasAnimatedBackgrounds;
this._swipeState.direction = delta > 0 ? "left" : "right";
}
@@ -176,6 +189,10 @@ export class ZenSpacesSwipe {
const rawDirection = moveForward ? 1 : -1;
const direction = ws.naturalScroll ? -1 : 1;
await ws.changeWorkspaceShortcut(rawDirection * direction, true);
}
onSwipeGestureAnimationEnd() {
const ws = gZenWorkspaces;
// Reset swipe state
this._swipeState = {
@@ -183,10 +200,6 @@ export class ZenSpacesSwipe {
lastDelta: 0,
direction: null,
};
}
onSwipeGestureAnimationEnd() {
const ws = gZenWorkspaces;
Services.prefs.setBoolPref("zen.swipe.is-fast-swipe", false);
document.documentElement.removeAttribute("swipe-gesture");
@@ -198,11 +211,15 @@ export class ZenSpacesSwipe {
);
delete ws._hasAnimatedBackgrounds;
ws.updateTabsContainers();
document.removeEventListener("popupshown", ws.popupOpenHandler, {
document.removeEventListener("popupshown", this._popupOpenHandler, {
once: true,
});
}
_popupOpenHandler() {
this.onSwipeGestureAnimationEnd();
}
get isGestureActive() {
return this._swipeState?.isGestureActive;
}

View File

@@ -138,6 +138,11 @@ zen-workspace-creation {
margin-left: auto;
min-width: unset !important;
border-radius: 6px;
&:focus-visible {
outline: 2px solid var(--zen-colors-border-contrast);
outline-offset: 2px;
}
}
}
@@ -153,6 +158,11 @@ zen-workspace-creation {
&:hover {
background-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
}
&:focus-visible {
outline: 2px solid var(--zen-colors-border-contrast);
outline-offset: 2px;
}
}
& .zen-workspace-creation-buttons {

View File

@@ -321,6 +321,7 @@ zen-workspace {
height: 100%;
overflow: hidden;
color: var(--toolbox-textcolor);
will-change: transform;
@media not (prefers-reduced-motion: reduce) {
transition: padding-top 0.1s;

View File

@@ -238,7 +238,7 @@
width: 200px;
height: 250px;
border-radius: 16px;
background: black;
background: light-dark(white, black);
justify-content: center;
align-items: center;
padding: 20px;
@@ -251,7 +251,7 @@
}
& label {
color: white;
color: light-dark(black, white);
font-size: 14px;
font-weight: bold;
text-align: center;

View File

@@ -318,7 +318,7 @@
&,
& .tab-content > image {
transition:
scale 0.2s ease,
scale 0.1s ease,
var(--zen-tabbox-element-indent-transition);
}
}
@@ -1077,7 +1077,7 @@
#tabs-newtab-button {
max-height: var(--tab-min-height);
display: flex !important;
transition: scale 0.2s ease;
transition: scale 0.1s ease;
#tabbrowser-tabs[movingtab] & {
transition: transform 0.1s ease;
}
@@ -1131,6 +1131,7 @@
transition:
max-height 0.3s ease-out,
grid-template-columns 0.3s ease-out;
will-change: transform;
opacity: 1;
--min-essentials-width-wrap: calc(var(--tab-min-height) + 4px);
grid-template-columns: repeat(auto-fit, minmax(max(23.7%, var(--min-essentials-width-wrap)), 1fr));

Some files were not shown because too many files have changed in this diff Show More