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>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-07 18:40:04 +00:00
committed by GitHub
74 changed files with 2227 additions and 790 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`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 150.0`!
- [`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 @@
fb55808f9cdd2172649e551705008af4f98038fe
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

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

@@ -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/.
- 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

@@ -39,3 +39,6 @@
- name: zen.urlbar.suggestions.quick-actions
value: true
- name: browser.urlbar.shortcuts.workspaces
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

@@ -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

@@ -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,12 +1,21 @@
diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs
index 2d21248256c6c2bfb8dac958133c10e3251ef564..6645211ef09518b41cb737e3186fbd0162ecf700 100644
index 2d21248256c6c2bfb8dac958133c10e3251ef564..f788bd10ec2c08e4b27b77cd3bb0489fb04e8b7a 100644
--- a/browser/components/urlbar/UrlbarPrefs.sys.mjs
+++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs
@@ -799,6 +799,7 @@ function makeDefaultResultGroups({ showSearchSuggestionsFirst }) {
@@ -462,6 +462,7 @@ const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([
["shortcuts.tabs", true],
["shortcuts.history", true],
["shortcuts.actions", true],
+ ["shortcuts.workspaces", true],
// Boolean to determine if the providers defined in `exposureResults`
// should be displayed in search results. This can be set by a
@@ -799,6 +800,8 @@ function makeDefaultResultGroups({ showSearchSuggestionsFirst }) {
*/
let rootGroup = {
children: [
+ { children: [{ group: lazy.UrlbarUtils.RESULT_GROUP.ZEN_ACTION }] },
+ { children: [{ group: lazy.UrlbarUtils.RESULT_GROUP.ZEN_WORKSPACE }] },
// heuristic
{
maxResultCount: 1,

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/UrlbarProvidersManager.sys.mjs b/browser/components/urlbar/UrlbarProvidersManager.sys.mjs
index 08455d8d5da233639ccebc0e77c0810fb4f674c3..78d0e875978b568b79646489c28b125a44ea79fa 100644
index 08455d8d5da233639ccebc0e77c0810fb4f674c3..da8092b561c3dd8864e57f5a52a1a643db29ace1 100644
--- a/browser/components/urlbar/UrlbarProvidersManager.sys.mjs
+++ b/browser/components/urlbar/UrlbarProvidersManager.sys.mjs
@@ -913,6 +913,7 @@ export class Query {
@@ -10,3 +10,26 @@ index 08455d8d5da233639ccebc0e77c0810fb4f674c3..78d0e875978b568b79646489c28b125a
(!this.context.trimmedSearchString ||
(!this.context.searchMode.engineName && !result.autofill))
) {
@@ -1043,6 +1044,7 @@ function updateSourcesIfEmpty(context) {
lazy.UrlbarTokenizer.TYPE.RESTRICT_TITLE,
lazy.UrlbarTokenizer.TYPE.RESTRICT_URL,
lazy.UrlbarTokenizer.TYPE.RESTRICT_ACTION,
+ lazy.UrlbarTokenizer.TYPE.RESTRICT_WORKSPACE,
].includes(t.type)
);
@@ -1100,6 +1102,14 @@ function updateSourcesIfEmpty(context) {
acceptedSources.push(source);
}
break;
+ case lazy.UrlbarUtils.RESULT_SOURCE.WORKSPACES:
+ if (
+ restrictTokenType === lazy.UrlbarTokenizer.TYPE.RESTRICT_WORKSPACE ||
+ !restrictTokenType
+ ) {
+ acceptedSources.push(source);
+ }
+ break;
case lazy.UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL:
case lazy.UrlbarUtils.RESULT_SOURCE.ADDON:
default:

View File

@@ -0,0 +1,28 @@
diff --git a/browser/components/urlbar/UrlbarTokenizer.sys.mjs b/browser/components/urlbar/UrlbarTokenizer.sys.mjs
index d4af0ee5138a69139b94d898fb07e2345172f025..f750aae3f9f0a849ca009784510575b2b7119e6d 100644
--- a/browser/components/urlbar/UrlbarTokenizer.sys.mjs
+++ b/browser/components/urlbar/UrlbarTokenizer.sys.mjs
@@ -66,6 +66,7 @@ export var UrlbarTokenizer = {
// `looksLikeOrigin()` returned `LOOKS_LIKE_ORIGIN.OTHER` for this token. It
// may or may not be an origin.
POSSIBLE_ORIGIN_BUT_SEARCH_ALLOWED: 12,
+ RESTRICT_WORKSPACE: 13,
}),
// The special characters below can be typed into the urlbar to restrict
@@ -83,6 +84,7 @@ export var UrlbarTokenizer = {
TITLE: "#",
URL: "$",
ACTION: ">",
+ WORKSPACE: "`",
}),
// The keys of characters in RESTRICT that will enter search mode.
@@ -97,6 +99,7 @@ export var UrlbarTokenizer = {
if (lazy.UrlbarPrefs.get("scotchBonnet.enableOverride")) {
keys.push(this.RESTRICT.ACTION);
}
+ keys.push(this.RESTRICT.WORKSPACE);
return new Set(keys);
},

View File

@@ -1,29 +1,50 @@
diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs
index 64afd613f454edd7786fcc1e2f307a582e4d5f51..b4af9cc2fbddba2c5229e8ffee7b9c0c06c3e1d0 100644
index 64afd613f454edd7786fcc1e2f307a582e4d5f51..92f91379f43785cf5417c96b178884c860db6bd3 100644
--- a/browser/components/urlbar/UrlbarUtils.sys.mjs
+++ b/browser/components/urlbar/UrlbarUtils.sys.mjs
@@ -85,6 +85,7 @@ export var UrlbarUtils = {
@@ -85,6 +85,8 @@ export var UrlbarUtils = {
RESTRICT_SEARCH_KEYWORD: "restrictSearchKeyword",
SUGGESTED_INDEX: "suggestedIndex",
TAIL_SUGGESTION: "tailSuggestion",
+ ZEN_ACTION: "zenAction",
+ ZEN_WORKSPACE: "zenWorkspace",
}),
// Defines provider types.
@@ -146,6 +147,7 @@ export var UrlbarUtils = {
@@ -146,6 +148,8 @@ export var UrlbarUtils = {
OTHER_NETWORK: 6,
ADDON: 7,
ACTIONS: 8,
+ ZEN_ACTIONS: 9,
+ WORKSPACES: 10,
}),
// Per-result exposure telemetry.
@@ -587,6 +589,8 @@ export var UrlbarUtils = {
@@ -295,6 +299,14 @@ export var UrlbarUtils = {
telemetryLabel: "actions",
uiLabel: "urlbar-searchmode-actions",
},
+ {
+ source: this.RESULT_SOURCE.WORKSPACES,
+ restrict: lazy.UrlbarTokenizer.RESTRICT.WORKSPACE,
+ icon: "chrome://browser/skin/zen-icons/selectable/layers.svg",
+ pref: "shortcuts.workspaces",
+ telemetryLabel: "workspaces",
+ uiLabel: "urlbar-search-mode-workspaces",
+ },
]);
},
@@ -587,6 +599,12 @@ export var UrlbarUtils = {
return this.RESULT_GROUP.HEURISTIC_FALLBACK;
case "UrlbarProviderHistoryUrlHeuristic":
return this.RESULT_GROUP.HEURISTIC_HISTORY_URL;
+ case "ZenUrlbarProviderGlobalActions":
+ return this.RESULT_GROUP.ZEN_ACTION;
+ if (result.source == this.RESULT_SOURCE.WORKSPACES) {
+ return this.RESULT_GROUP.ZEN_WORKSPACE;
+ } else {
+ return this.RESULT_GROUP.ZEN_ACTION;
+ }
case "UrlbarProviderOmnibox":
return this.RESULT_GROUP.HEURISTIC_OMNIBOX;
case "UrlbarProviderRestrictKeywordsAutofill":

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

@@ -796,7 +796,8 @@
--fp-enabled: 1;
}
#alltabs-button {
#alltabs-button,
#urlbar-engine-one-off-item-workspaces {
list-style-image: url("chrome://browser/skin/tabs.svg") !important;
}

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,10 +1,20 @@
diff --git a/toolkit/content/widgets/panel.js b/toolkit/content/widgets/panel.js
index dc3e34847f1b6dfd58f5e036fd7d714ef51c1380..8d8e370ca8549d8208669d4fb344fc8abb54fadd 100644
index dc3e34847f1b6dfd58f5e036fd7d714ef51c1380..e88ab308c5eba01ccf82a0d1b9477555fcb9a5de 100644
--- a/toolkit/content/widgets/panel.js
+++ b/toolkit/content/widgets/panel.js
@@ -137,6 +137,9 @@
@@ -136,7 +136,19 @@
let anchorRoot =
this.anchorNode.closest("toolbarbutton, .anchor-root") ||
this.anchorNode;
+ let toolbox = anchorRoot.closest("#navigator-toolbox");
+ if (toolbox) {
+ // Disable transitions for now, see gh-11667
+ toolbox.style.transition = "none";
+ toolbox.setAttribute("zen-compact-mode-active", "true");
+ anchorRoot.ownerGlobal.setTimeout(() => {
+ toolbox.style.transition = "";
+ }, 0);
+ }
anchorRoot.setAttribute("open", "true");
+ if (anchorRoot.closest("#urlbar") && window.gURLBar) {
+ gURLBar.setAttribute("has-popup-open", "true");
@@ -12,7 +22,7 @@ index dc3e34847f1b6dfd58f5e036fd7d714ef51c1380..8d8e370ca8549d8208669d4fb344fc8a
}
if (this.getAttribute("animate") != "false") {
@@ -183,6 +186,9 @@
@@ -183,6 +195,9 @@
this.anchorNode.closest("toolbarbutton, .anchor-root") ||
this.anchorNode;
anchorRoot.removeAttribute("open");

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

@@ -88,7 +88,7 @@ export class nsZenBoostStyles {
style += `}\n`;
}
if (boostData.customCSS.trim() != "") {
if ((boostData.customCSS || "").trim() != "") {
style += `/* USER CSS */\n`;
style += `${boostData.customCSS || ""}\n`;
}

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

@@ -115,7 +115,7 @@
<panel type="arrow" popupalign="topmiddle" id="zen-boost-advanced-color-options-panel">
<vbox>
<p data-l10n-id="zen-bootst-color-contrast"></p>
<html:input id="zen-boost-color-contrast" type="range" min="0.1" max="0.9" value="0.75" step="0.01"/>
<html:input id="zen-boost-color-contrast" type="range" min="0.05" max="0.9" value="0.75" step="0.01"/>
</vbox>
<vbox>
<p data-l10n-id="zen-bootst-color-brightness"></p>

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

@@ -705,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);
},
@@ -1376,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")
@@ -1579,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

@@ -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")) and (not ((-moz-pref("zen.view.experimental-no-window-controls") or (not -moz-pref("zen.view.hide-window-controls"))) and -moz-pref("zen.view.use-single-toolbar"))) {
@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,6 +59,13 @@
--margin-top-fix: calc(-1 * var(--zen-toolbar-height-with-bookmarks) + var(--zen-element-separation));
}
/* 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;
}
}
margin-top: var(--margin-top-fix);
}
}

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

@@ -250,7 +250,7 @@
--panel-separator-color: color-mix(in srgb, currentColor 15%, transparent) !important;
--zen-big-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
--zen-active-tab-scale: 0.99;
--zen-active-tab-scale: 0.985;
/* Define tab hover background color */
--tab-hover-background-color: var(--toolbarbutton-hover-background);

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

@@ -952,6 +952,7 @@
// Sometimes, dragend doesn't always get called when dragging
// to different windows, see gh-8643.
delete ownerGlobal.gZenCompactModeManager._isTabBeingDragged;
ownerGlobal.gZenCompactModeManager._clearAllHoverStates();
}
this.clearSpaceSwitchTimer();
gZenFolders.highlightGroupOnDragOver(null);
@@ -1107,7 +1108,10 @@
);
for (let i = startIndex; i <= endIndex; i++) {
let item = items[i];
if (!movingTabs.includes(item)) {
if (
!movingTabs.includes(item) &&
!(isTabGroupLabel(item) && i == startIndex)
) {
tabsInBetween.push(item);
}
}

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

@@ -820,7 +820,7 @@ class nsZenKeyboardShortcutsLoader {
}
class nsZenKeyboardShortcutsVersioner {
static LATEST_KBS_VERSION = 19;
static LATEST_KBS_VERSION = 20;
constructor() {}
@@ -1131,6 +1131,21 @@ class nsZenKeyboardShortcutsVersioner {
);
}
if (version < 18) {
// Migrate from version 17 to 18.
// Add shortcut to Create New Workspace (unbound by default)
data.push(
new KeyShortcut(
"zen-workspace-create",
"",
"",
ZEN_WORKSPACE_SHORTCUTS_GROUP,
nsKeyShortcutModifiers.fromObject({}),
"cmd_zenOpenWorkspaceCreation",
"zen-workspace-shortcut-create"
)
);
}
if (version < 19) {
data.push(
new KeyShortcut(
"zen-new-little-window",
@@ -1147,12 +1162,28 @@ class nsZenKeyboardShortcutsVersioner {
)
);
}
if (version < 19) {
if (version < 20) {
let hasWorkspaceCreate = false;
for (let shortcut of data) {
if (shortcut.getID() == "zen-new-little-window") {
shortcut._setZenGlobal(true);
break;
}
if (shortcut.getID() == "zen-workspace-create") {
hasWorkspaceCreate = true;
}
}
if (!hasWorkspaceCreate) {
data.push(
new KeyShortcut(
"zen-workspace-create",
"",
"",
ZEN_WORKSPACE_SHORTCUTS_GROUP,
nsKeyShortcutModifiers.fromObject({}),
"cmd_zenOpenWorkspaceCreation",
"zen-workspace-shortcut-create"
)
);
}
}
return data;

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

@@ -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.
@@ -1360,6 +1360,9 @@ class nsZenWindowSync {
_forZenEmptyTab: tab.hasAttribute("zen-empty-tab"),
});
newTab.id = tab.id;
if (!tab.hasAttribute("pending")) {
newTab.removeAttribute("pending");
}
this.#syncItemWithOriginal(
tab,
newTab,
@@ -1489,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;
@@ -1508,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

@@ -664,7 +664,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
) {
let newIndex = dropIndex;
let fromDifferentWindow = false;
movingTabs = Array.from(movingTabs || draggedTab)
let ownedTabs = Array.from(movingTabs || draggedTab)
.reverse()
.map(tab => {
if (!gBrowser.isTab(tab)) {
@@ -700,6 +700,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
return tab;
});
movingTabs = [...ownedTabs];
if (fromDifferentWindow) {
gBrowser.addRangeToMultiSelectedTabs(
gBrowser.tabContainer.dragAndDropElements[dropIndex],
@@ -822,7 +823,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
} catch (ex) {
console.error("Error moving tabs:", ex);
}
return [draggedTab, movingTabs];
return [draggedTab, ownedTabs];
}
onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI) {

View File

@@ -31,7 +31,7 @@ z-index: 1;
%include ../../compact-mode/windows-captions-fix-default.inc.css
}
@media -moz-pref('zen.view.experimental-no-window-controls') {
@media -moz-pref('zen.view.experimental-no-window-controls') or (not -moz-pref("browser.tabs.inTitlebar")) {
:root:not([zen-has-bookmarks]) & {
max-height: 0 !important;
overflow: hidden;

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;
}
@@ -1089,7 +1089,7 @@
&:active,
&[open] {
scale: 0.99;
scale: 0.985;
}
&[in-urlbar] {
@@ -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));

View File

@@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
[DEFAULT]
prefs = ["zen.live-folders.github.skip-new-pr-ui-check=true"]
["browser_github_live_folder.js"]

View File

@@ -65,10 +65,10 @@ add_task(async function test_fetch_items_url_construction() {
const fetchedUrl = new URL(instance.fetch.firstCall.args[0]);
const searchParams = fetchedUrl.searchParams;
Assert.ok(fetchedUrl.href.startsWith("https://github.com/issues/assigned"));
Assert.ok(fetchedUrl.href.startsWith("https://github.com/pulls"));
const query = searchParams.get("q");
Assert.ok(query.includes("state:open"), "Should include state:open");
Assert.ok(query.includes("is:open"), "Should include state:open");
Assert.ok(query.includes("is:pr"), "Should include is:PR");
Assert.ok(query.includes("author:@me"), "Should include author:@me");
Assert.ok(!query.includes("assignee:@me"), "Should NOT include assignee:@me");
@@ -176,3 +176,169 @@ add_task(async function test_fetch_network_error() {
sandbox.restore();
});
add_task(async function test_no_filter_enabled_returns_error() {
info(
"should short-circuit and return the no-filter error when every option is off"
);
let sandbox = sinon.createSandbox();
let instance = getGithubProviderForTest(sandbox, {
type: "pull-requests",
authorMe: false,
assignedMe: false,
reviewRequested: false,
});
const result = await instance.fetchItems();
Assert.equal(
result,
"zen-live-folder-github-no-filter",
"Should return the no-filter error id"
);
Assert.ok(
instance.fetch.notCalled,
"Should not issue a fetch when no filter is enabled"
);
sandbox.restore();
});
add_task(async function test_404_returns_no_auth() {
info("should treat a 404 as a missing-auth signal");
let sandbox = sinon.createSandbox();
let instance = getGithubProviderForTest(sandbox, {
type: "pull-requests",
authorMe: true,
});
instance.fetch.resolves({ status: 404, text: "" });
const result = await instance.fetchItems();
Assert.equal(
result,
"zen-live-folder-github-no-auth",
"Should return the no-auth error id"
);
sandbox.restore();
});
add_task(async function test_repo_excludes_emit_negative_repo_filters() {
info("should add -repo:<repo> clauses for each excluded repository");
let sandbox = sinon.createSandbox();
let instance = getGithubProviderForTest(sandbox, {
type: "pull-requests",
authorMe: true,
assignedMe: false,
reviewRequested: false,
repoExcludes: ["zen-browser/desktop", "foo/bar"],
});
instance.fetch.resolves({ status: 200, text: "<html></html>" });
await instance.fetchItems();
const fetchedUrl = new URL(instance.fetch.firstCall.args[0]);
const query = fetchedUrl.searchParams.get("q");
Assert.ok(
query.includes("-repo:zen-browser/desktop"),
"Should exclude zen-browser/desktop from the query"
);
Assert.ok(
query.includes("-repo:foo/bar"),
"Should exclude foo/bar from the query"
);
sandbox.restore();
});
add_task(async function test_pull_requests_json_api_parsing() {
info("should parse the new PR dashboard JSON payload");
let sandbox = sinon.createSandbox();
let instance = getGithubProviderForTest(sandbox, {
type: "pull-requests",
authorMe: true,
});
const payload = JSON.stringify({
payload: {
pullsDashboardSurfaceContentRoute: {
results: [
{
repoNameWithOwner: "zen-browser/desktop",
number: 42,
title: "Add live folders",
author: { displayLogin: "alice" },
permalink: "https://github.com/zen-browser/desktop/pull/42",
},
{
repoNameWithOwner: "zen-browser/desktop",
number: 43,
title: "Fix bug",
author: { displayLogin: "bob" },
permalink: "https://github.com/zen-browser/desktop/pull/43",
},
],
},
},
});
instance.fetch.resolves({ status: 200, text: payload });
const items = await instance.fetchItems();
Assert.equal(items.length, 2, "Should parse two PRs from the JSON payload");
Assert.equal(items[0].id, "zen-browser/desktop#42");
Assert.equal(items[0].title, "Add live folders");
Assert.equal(items[0].subtitle, "alice");
Assert.equal(items[0].url, "https://github.com/zen-browser/desktop/pull/42");
Assert.equal(items[1].id, "zen-browser/desktop#43");
Assert.ok(
instance.state.isJsonApi,
"Should mark the provider as using the JSON API"
);
sandbox.restore();
});
add_task(async function test_pull_requests_json_api_falls_back_to_html() {
info(
"should fall back to HTML parsing when an HTML response arrives unexpectedly"
);
let sandbox = sinon.createSandbox();
let instance = getGithubProviderForTest(sandbox, {
type: "pull-requests",
authorMe: true,
});
// Simulate a previous fetch having locked the provider into JSON-API mode.
instance.state.isJsonApi = true;
instance.fetch.resolves({
status: 200,
text: "<html><body>not JSON</body></html>",
});
const result = await instance.fetchItems();
Assert.equal(
instance.state.isJsonApi,
false,
"Should clear isJsonApi after seeing a non-JSON response"
);
Assert.equal(
result,
"zen-live-folder-failed-fetch",
"Should surface a fetch error so the user is prompted to retry"
);
sandbox.restore();
});

View File

@@ -162,9 +162,9 @@ add_task(async function test_max_items_limit() {
const rssXml = `
<rss version="2.0">
<channel>
<item><title>1</title><link>1</link><pubDate>${date}</pubDate></item>
<item><title>2</title><link>2</link><pubDate>${date}</pubDate></item>
<item><title>3</title><link>3</link><pubDate>${date}</pubDate></item>
<item><title>1</title><link>https://example.com/1</link><pubDate>${date}</pubDate></item>
<item><title>2</title><link>https://example.com/2</link><pubDate>${date}</pubDate></item>
<item><title>3</title><link>https://example.com/3</link><pubDate>${date}</pubDate></item>
</channel>
</rss>
`;
@@ -218,6 +218,113 @@ add_task(async function test_invalid_dates() {
sandbox.restore();
});
add_task(async function test_item_url_scheme_filtering() {
info("should drop items whose link uses a non-http(s) scheme");
let sandbox = sinon.createSandbox();
let instance = getRssProviderForTest(sandbox, { timeRange: 0 });
const date = new Date().toUTCString();
const rssXml = `
<rss version="2.0">
<channel>
<item>
<title>JavaScript scheme</title>
<link>javascript:alert(1)</link>
<pubDate>${date}</pubDate>
</item>
<item>
<title>Data scheme</title>
<link>data:text/html,&lt;script&gt;alert(1)&lt;/script&gt;</link>
<pubDate>${date}</pubDate>
</item>
<item>
<title>File scheme</title>
<link>file:///etc/passwd</link>
<pubDate>${date}</pubDate>
</item>
<item>
<title>about: scheme</title>
<link>about:config</link>
<pubDate>${date}</pubDate>
</item>
<item>
<title>chrome: scheme</title>
<link>chrome://browser/content/browser.xhtml</link>
<pubDate>${date}</pubDate>
</item>
<item>
<title>Invalid URL</title>
<link>not a url</link>
<pubDate>${date}</pubDate>
</item>
<item>
<title>Good https</title>
<link>https://example.com/good</link>
<pubDate>${date}</pubDate>
</item>
<item>
<title>Good http</title>
<link>http://example.com/good</link>
<pubDate>${date}</pubDate>
</item>
</channel>
</rss>
`;
instance.fetch.resolves({ text: rssXml });
const items = await instance.fetchItems();
Assert.equal(
items.length,
2,
"Only http(s) items should survive scheme filtering"
);
Assert.deepEqual(
items.map(i => i.url).sort(),
["http://example.com/good", "https://example.com/good"],
"Surviving items should be the http and https links"
);
sandbox.restore();
});
add_task(async function test_atom_item_url_scheme_filtering() {
info("should drop Atom entries whose link href uses a non-http(s) scheme");
let sandbox = sinon.createSandbox();
let instance = getRssProviderForTest(sandbox, { timeRange: 0 });
const updated = new Date().toISOString();
const atomXml = `
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Atom Feed</title>
<entry>
<title>Bad scheme</title>
<link href="javascript:alert(1)" />
<id>urn:uuid:bad</id>
<updated>${updated}</updated>
</entry>
<entry>
<title>Good scheme</title>
<link href="https://example.com/atom-good" />
<id>urn:uuid:good</id>
<updated>${updated}</updated>
</entry>
</feed>
`;
instance.fetch.resolves({ text: atomXml });
const items = await instance.fetchItems();
Assert.equal(items.length, 1, "Only the https Atom entry should remain");
Assert.equal(items[0].url, "https://example.com/atom-good");
sandbox.restore();
});
add_task(async function test_fetch_network_error() {
info("should return empty array on network error");

View File

@@ -5,3 +5,4 @@
[DEFAULT]
["browser_ub_actions_search.js"]
["browser_workspace_restrict_search.js"]

View File

@@ -0,0 +1,142 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
});
UrlbarTestUtils.init(this);
add_task(async function test_Workspace_Search_OneOff_Pref() {
const oneOffSearchButtons = UrlbarTestUtils.getOneOffSearchButtons(window);
async function openPopupAndWaitForRebuild(value = "") {
oneOffSearchButtons.invalidateCache();
const rebuildPromise = BrowserTestUtils.waitForEvent(
oneOffSearchButtons,
"rebuild"
);
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
waitForFocus,
value,
});
await rebuildPromise;
}
function getWorkspaceShortcut() {
return [...oneOffSearchButtons.localButtons].find(
button => button.source == UrlbarUtils.RESULT_SOURCE.WORKSPACES
);
}
try {
await SpecialPowers.pushPrefEnv({
set: [["zen.urlbar.hide-one-offs", false]],
});
await openPopupAndWaitForRebuild();
ok(
getWorkspaceShortcut(),
"The workspace shortcut should be visible when the pref is enabled"
);
await UrlbarTestUtils.promisePopupClose(window);
await SpecialPowers.pushPrefEnv({
set: [["browser.urlbar.shortcuts.workspaces", false]],
});
try {
await openPopupAndWaitForRebuild();
ok(
!getWorkspaceShortcut(),
"The workspace shortcut should be hidden when the pref is disabled"
);
await UrlbarTestUtils.promisePopupClose(window);
} finally {
await SpecialPowers.popPrefEnv();
}
} finally {
await SpecialPowers.popPrefEnv();
if (UrlbarTestUtils.isPopupOpen(window)) {
await UrlbarTestUtils.promisePopupClose(window);
}
}
});
add_task(async function test_Workspace_Restrict_Search() {
const originalWorkspaceId = gZenWorkspaces.activeWorkspace;
const workspaceName = "zen-urlbar-workspace-proof-617db8";
await gZenWorkspaces.createAndSaveWorkspace(workspaceName);
const createdWorkspace = gZenWorkspaces
.getWorkspaces()
.find(workspace => workspace.name == workspaceName);
ok(createdWorkspace, "Created the workspace used by the urlbar test");
registerCleanupFunction(async function () {
if (UrlbarTestUtils.isPopupOpen(window)) {
await UrlbarTestUtils.promisePopupClose(window, () =>
EventUtils.synthesizeKey("KEY_Escape")
);
}
if (gZenWorkspaces.activeWorkspace != originalWorkspaceId) {
await gZenWorkspaces.changeWorkspace(originalWorkspaceId);
}
if (
gZenWorkspaces
.getWorkspaces()
.some(workspace => workspace.uuid == createdWorkspace.uuid)
) {
await gZenWorkspaces.removeWorkspace(createdWorkspace.uuid);
}
});
await gZenWorkspaces.changeWorkspace(originalWorkspaceId);
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
waitForFocus,
value: "` " + workspaceName,
});
// Wait for the second search started when the typed token enters search mode.
await UrlbarTestUtils.promiseSearchComplete(window);
ok(gURLBar.searchMode, "The urlbar should enter search mode");
Assert.equal(
gURLBar.searchMode.source,
UrlbarUtils.RESULT_SOURCE.WORKSPACES,
"The typed token should enter workspace search mode"
);
Assert.equal(
gURLBar.searchMode.entry,
"typed",
"The workspace search mode should be entered by typing the token"
);
Assert.equal(gURLBar.value, workspaceName, "The token should be stripped");
const resultCount = UrlbarTestUtils.getResultCount(window);
ok(resultCount > 0, "Should show at least one workspace result");
const resultDetails = [];
for (let i = 0; i < resultCount; i++) {
resultDetails.push(await UrlbarTestUtils.getDetailsOfResultAt(window, i));
}
ok(
resultDetails.every(
({ result, source }) =>
source == UrlbarUtils.RESULT_SOURCE.WORKSPACES &&
result.providerName == "ZenUrlbarProviderGlobalActions"
),
"Typing the workspace token should limit results to workspace actions"
);
Assert.equal(
resultDetails[0].result.payload.prettyName,
workspaceName,
"The matching workspace should be shown first"
);
});

View File

@@ -155,6 +155,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
*/
async isActive(queryContext) {
return (
queryContext.searchMode?.source == UrlbarUtils.RESULT_SOURCE.WORKSPACES ||
queryContext.searchMode?.source ==
UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS ||
(lazy.enabledPref &&
@@ -241,10 +242,13 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
*
* @param {string} query The user's search query.
* @param {boolean} isPrefixed Whether the query is prefixed.
* @param {boolean} isWorkspaceSearch Whether this is a workspace search query
*/
async #findMatchingActions(query, isPrefixed) {
async #findMatchingActions(query, isPrefixed, isWorkspaceSearch) {
const window = lazy.BrowserWindowTracker.getTopWindow();
const actions = await this.#getAvailableActions(window);
const actions = isWorkspaceSearch
? this.#getWorkspaceActions(window)
: await this.#getAvailableActions(window);
let results = [];
for (let action of actions) {
if (isPrefixed && query.length < 1) {
@@ -341,13 +345,21 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
async startQuery(queryContext, addCallback) {
const query = queryContext.trimmedLowerCaseSearchString;
const isWorkspaceSearch =
queryContext.searchMode?.source == UrlbarUtils.RESULT_SOURCE.WORKSPACES;
const isPrefixed =
isWorkspaceSearch ||
queryContext.searchMode?.source == UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS;
if (!query && !isPrefixed) {
return;
}
const actionsResults = await this.#findMatchingActions(query, isPrefixed);
const actionsResults = await this.#findMatchingActions(
query,
isPrefixed,
isWorkspaceSearch
);
if (!actionsResults.length) {
return;
}
@@ -361,9 +373,12 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
zenCommand: action.command,
dynamicType: DYNAMIC_TYPE_NAME,
zenAction: true,
query: isPrefixed
? action.label.trimStart()
: queryContext.searchString,
// eslint-disable-next-line no-nested-ternary
query: isWorkspaceSearch
? action.extraPayload.prettyName
: isPrefixed
? action.label.trimStart()
: queryContext.searchString,
icon: action.icon,
shortcutContent:
ownerGlobal.gZenKeyboardShortcutsManager.getShortcutDisplayFromCommand(
@@ -378,7 +393,9 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
!isPrefixed;
let result = new lazy.UrlbarResult({
type: UrlbarUtils.RESULT_TYPE.DYNAMIC,
source: UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS,
source: isWorkspaceSearch
? UrlbarUtils.RESULT_SOURCE.WORKSPACES
: UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS,
payload,
highlights: payloadHighlights,
heuristic: shouldBePrioritized,
@@ -398,7 +415,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
zenUrlbarResultsLearner
.sortCommandsByPriority(finalResults)
.forEach(result => {
if (isPrefixed && i === 0 && query.length > 1) {
if (isPrefixed && !isWorkspaceSearch && i === 0 && query.length > 1) {
result.heuristic = true;
delete result.suggestedIndex;
}

View File

@@ -5,8 +5,8 @@
"binaryName": "zen",
"version": {
"product": "firefox",
"version": "150.0",
"candidate": "150.0",
"version": "150.0.2",
"candidate": "150.0.2",
"candidateBuild": 1
},
"buildOptions": {
@@ -20,7 +20,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.19.10b",
"displayVersion": "1.19.12b",
"github": {
"repo": "zen-browser/desktop"
},

View File

@@ -107,9 +107,9 @@ use std::env;
use std::fs;
use std::path::PathBuf;
const STATIC_PREFS: &str = "../engine/modules/libpref/init/zen-static-prefs.inc";
const FIREFOX_PREFS: &str = "../engine/browser/app/profile/firefox.js";
const DYNAMIC_PREFS: &str = "../engine/browser/app/profile/zen.js";
const STATIC_PREFS: &str = "modules/libpref/init/zen-static-prefs.inc";
const FIREFOX_PREFS: &str = "browser/app/profile/firefox.js";
const DYNAMIC_PREFS: &str = "browser/app/profile/zen.js";
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Preference {
@@ -124,12 +124,6 @@ struct Preference {
sticky: Option<bool>,
}
fn get_config_path() -> PathBuf {
let mut path = env::current_dir().expect("Failed to get current directory");
path.push("prefs");
path
}
fn ordered_prefs(mut prefs: Vec<Preference>) -> Vec<Preference> {
// Sort preferences by name
prefs.sort_by(|a, b| a.name.cmp(&b.name));
@@ -153,11 +147,10 @@ fn get_prefs_files_recursively(dir: &PathBuf, files: &mut Vec<PathBuf>) {
}
}
fn load_preferences() -> Vec<Preference> {
fn load_preferences(prefs_path: &PathBuf) -> Vec<Preference> {
let mut prefs = Vec::new();
let config_path = get_config_path();
let mut pref_files = Vec::new();
get_prefs_files_recursively(&config_path, &mut pref_files);
get_prefs_files_recursively(&prefs_path, &mut pref_files);
for file_path in pref_files {
let content = fs::read_to_string(&file_path).expect("Failed to read file");
let mut parsed_prefs: Vec<Preference> =
@@ -263,14 +256,9 @@ fn get_value(pref: &Preference) -> String {
}
}
fn write_preferences(prefs: &[Preference]) {
let config_path = get_config_path();
if !config_path.exists() {
fs::create_dir_all(&config_path).expect("Failed to create prefs directory");
}
let static_prefs_path = config_path.join(STATIC_PREFS);
let dynamic_prefs_path = config_path.join(DYNAMIC_PREFS);
fn write_preferences(engine_path: &PathBuf, prefs: &[Preference]) {
let static_prefs_path = engine_path.join(STATIC_PREFS);
let dynamic_prefs_path = engine_path.join(DYNAMIC_PREFS);
println!(
"Writing preferences to:\n Static: {}\n Dynamic: {}",
static_prefs_path.display(),
@@ -300,10 +288,10 @@ fn write_preferences(prefs: &[Preference]) {
fs::write(&dynamic_prefs_path, dynamic_content).expect("Failed to write dynamic prefs");
}
fn prepare_zen_prefs() {
fn prepare_zen_prefs(engine_path: &PathBuf) {
// Add `#include zen.js` to the bottom of the firefox.js file if it doesn't exist
let line = "#include zen.js";
let firefox_prefs_path = get_config_path().join(FIREFOX_PREFS);
let firefox_prefs_path = engine_path.join(FIREFOX_PREFS);
if let Ok(mut content) = fs::read_to_string(&firefox_prefs_path) {
if !content.contains(line) {
content.push_str(format!("\n{}\n", line).as_str());
@@ -351,15 +339,19 @@ fn expand_pref_values(prefs: &mut [Preference]) {
fn main() {
let args: Vec<String> = env::args().collect();
let root_path = if args.len() > 1 {
let prefs_path = if args.len() > 1 {
PathBuf::from(&args[1])
} else {
env::current_dir().expect("Failed to get current directory")
PathBuf::from("prefs")
};
let engine_path = if args.len() > 2 {
PathBuf::from(&args[2])
} else {
PathBuf::from("engine")
};
env::set_current_dir(&root_path).expect("Failed to change directory");
prepare_zen_prefs();
let mut preferences = load_preferences();
prepare_zen_prefs(&engine_path);
let mut preferences = load_preferences(&prefs_path);
expand_pref_values(&mut preferences);
write_preferences(&preferences);
write_preferences(&engine_path, &preferences);
}