mirror of
https://github.com/zen-browser/desktop.git
synced 2026-05-24 22:00:13 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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 mba’ete ñ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]=開啟設定檔管理員
|
||||
|
||||
@@ -1 +1 @@
|
||||
fb55808f9cdd2172649e551705008af4f98038fe
|
||||
73901ca17f4a2159dd4488cea8684e9abbfdcc89
|
||||
@@ -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
8
package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
6
prefs/zen/live-folders.yaml
Normal file
6
prefs/zen/live-folders.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
value: true
|
||||
|
||||
- name: zen.theme.acrylic-elements
|
||||
value: false
|
||||
value: "@IS_TWILIGHT@"
|
||||
|
||||
- name: zen.theme.disable-lightweight
|
||||
value: true
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -39,3 +39,6 @@
|
||||
|
||||
- name: zen.urlbar.suggestions.quick-actions
|
||||
value: true
|
||||
|
||||
- name: browser.urlbar.shortcuts.workspaces
|
||||
value: true
|
||||
@@ -60,4 +60,7 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) == 3:
|
||||
_, DUMPS_FOLDER, ENGINE_DUMPS_FOLDER = sys.argv
|
||||
main()
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
28
src/browser/components/urlbar/UrlbarTokenizer-sys-mjs.patch
Normal file
28
src/browser/components/urlbar/UrlbarTokenizer-sys-mjs.patch
Normal 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);
|
||||
},
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>`);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
|
||||
color-scheme: dark;
|
||||
--zen-urlbar-outline-offset: -2px;
|
||||
--zen-urlbar-filter: blur(40px) saturate(110%) brightness(25%) contrast(100%);
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
|
||||
color-scheme: light;
|
||||
--zen-urlbar-outline-offset: 0px;
|
||||
--zen-urlbar-filter: blur(40px) saturate(110%) brightness(75%) contrast(100%);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -67,7 +67,7 @@ if (!Services.appinfo.inSafeMode) {
|
||||
child: {
|
||||
esModuleURI: "resource:///actors/ZenBoostsChild.sys.mjs",
|
||||
events: {
|
||||
DOMDocElementInserted: { capture: true },
|
||||
DOMWindowCreated: {},
|
||||
unload: {},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
& label {
|
||||
max-width: 4rem;
|
||||
margin-left: 8px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
& image {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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,<script>alert(1)</script></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");
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
[DEFAULT]
|
||||
|
||||
["browser_ub_actions_search.js"]
|
||||
["browser_workspace_restrict_search.js"]
|
||||
|
||||
142
src/zen/tests/ub-actions/browser_workspace_restrict_search.js
Normal file
142
src/zen/tests/ub-actions/browser_workspace_restrict_search.js
Normal 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"
|
||||
);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user