mirror of
https://github.com/zen-browser/desktop.git
synced 2026-07-12 20:00:29 +00:00
Compare commits
51 Commits
1.19.9b
...
little-zen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd9381997 | ||
|
|
9c88e3a84f | ||
|
|
b999a932ac | ||
|
|
e9a0beae20 | ||
|
|
b93c2054c4 | ||
|
|
dcf272f620 | ||
|
|
3ffdf6b299 | ||
|
|
b052c1d804 | ||
|
|
818c448dfc | ||
|
|
5b16bb36d9 | ||
|
|
5b790a431a | ||
|
|
149419d1fd | ||
|
|
60a77413df | ||
|
|
ce1ee9228b | ||
|
|
1bea3d401c | ||
|
|
b6624612cd | ||
|
|
aa463e2cb0 | ||
|
|
6552f0b3c3 | ||
|
|
9950679f2c | ||
|
|
a9a582afae | ||
|
|
607551f394 | ||
|
|
3278a43751 | ||
|
|
c82d314913 | ||
|
|
a71a66c00b | ||
|
|
c0cd45bf1d | ||
|
|
685cddf7c2 | ||
|
|
562e6e5c3a | ||
|
|
27f40393d5 | ||
|
|
21f7fdaef4 | ||
|
|
04c5bc3478 | ||
|
|
f99b8af86d | ||
|
|
092ee7bc2a | ||
|
|
e3b0295e36 | ||
|
|
18c15ac0b8 | ||
|
|
c0620a95ef | ||
|
|
c939ee4a3d | ||
|
|
709691225a | ||
|
|
bb14e5d66f | ||
|
|
d15f5331ff | ||
|
|
cccbcf662e | ||
|
|
92dfe0babd | ||
|
|
4a7b517460 | ||
|
|
102dcb1993 | ||
|
|
040bed5ce5 | ||
|
|
6d3a59ac04 | ||
|
|
4d26b4d597 | ||
|
|
6aafd8cb34 | ||
|
|
2028a9b288 | ||
|
|
cd956d34b1 | ||
|
|
0cfc25ccb0 | ||
|
|
0199e256d6 |
@@ -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
|
||||
@@ -20,3 +20,5 @@ files:
|
||||
translation: browser/browser/preferences/zen-preferences.ftl
|
||||
- source: en-US/browser/browser/zen-folders.ftl
|
||||
translation: browser/browser/zen-folders.ftl
|
||||
- source: en-US/browser/browser/zen-boosts.ftl
|
||||
translation: browser/browser/zen-boosts.ftl
|
||||
|
||||
@@ -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
|
||||
|
||||
58
locales/en-US/browser/browser/zen-boosts.ftl
Normal file
58
locales/en-US/browser/browser/zen-boosts.ftl
Normal file
@@ -0,0 +1,58 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
zen-boost-edit-rename =
|
||||
.label = Rename Boost
|
||||
zen-boost-edit-shuffle =
|
||||
.label = Shuffle Vibes
|
||||
zen-boost-edit-reset =
|
||||
.label = Reset All Edits
|
||||
zen-boost-edit-delete =
|
||||
.label = Delete Boost
|
||||
zen-boost-size = Size
|
||||
zen-boost-zap = Zap
|
||||
zen-boost-code = Code
|
||||
zen-boost-back = Back
|
||||
zen-boost-shuffle =
|
||||
.tooltiptext = Shuffle Boost Settings
|
||||
zen-boost-invert =
|
||||
.tooltiptext = Smart Invert Colors
|
||||
zen-boost-controls =
|
||||
.tooltiptext = Advanced Color Controls
|
||||
zen-boost-disable =
|
||||
.tooltiptext = Disable Color Adjustments
|
||||
zen-boost-text-case-toggle =
|
||||
.tooltiptext = Toggle Text Case
|
||||
zen-boost-css-picker =
|
||||
.tooltiptext = Pick Selector
|
||||
zen-boost-css-inspector =
|
||||
.tooltiptext = Open Inspector
|
||||
zen-bootst-color-contrast = Contrast
|
||||
zen-bootst-color-brightness = Brightness
|
||||
zen-bootst-color-original-saturation = Original Saturation
|
||||
zen-add-zap-helper = Click elements on the page to <b>Zap</b> them
|
||||
zen-remove-zap-helper = ← Click to Unzap
|
||||
zen-select-this = Insert selector for this
|
||||
zen-select-related = Insert selector for related
|
||||
zen-select-cancel = Cancel
|
||||
zen-zap-this = Zap this
|
||||
zen-zap-related = Zap all related elements
|
||||
zen-zap-cancel = Cancel
|
||||
zen-zap-done = Done
|
||||
zen-unzap-tooltip =
|
||||
{
|
||||
$elementCount ->
|
||||
[0] No elements zapped
|
||||
[1] { $elementCount } element zapped
|
||||
*[other] { $elementCount } elements zapped
|
||||
}
|
||||
zen-boost-save =
|
||||
.tooltiptext = Export Boost
|
||||
zen-boost-load =
|
||||
.tooltiptext = Import Boost
|
||||
zen-panel-ui-boosts-exported-message = Boost exported!
|
||||
zen-site-data-boosts = Boosts
|
||||
zen-site-data-create-boost =
|
||||
.tooltiptext = Create new boost
|
||||
zen-boost-rename-boost-prompt = Rename Boost?
|
||||
@@ -151,3 +151,5 @@ zen-window-sync-migration-dialog-accept = Got It
|
||||
zen-appmenu-new-blank-window =
|
||||
.label = New blank window
|
||||
|
||||
zen-spaces-search-placeholder =
|
||||
.placeholder = Search your spaces...
|
||||
|
||||
8
package-lock.json
generated
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",
|
||||
|
||||
22
prefs/zen/boosts.yaml
Normal file
22
prefs/zen/boosts.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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.boosts.enabled
|
||||
value: "@IS_TWILIGHT@"
|
||||
|
||||
- name: zen.boosts.dissolve-on-zap
|
||||
value: true
|
||||
|
||||
- name: zen.boosts.invert-channel-floor
|
||||
value: 15
|
||||
cpptype: uint32_t
|
||||
mirror: once
|
||||
type: static
|
||||
|
||||
- name: zen.boosts.disable-on-anonymous-content
|
||||
value: true
|
||||
cpptype: bool
|
||||
mirror: once
|
||||
type: static
|
||||
|
||||
@@ -7,3 +7,6 @@
|
||||
|
||||
- name: zen.keyboard.shortcuts.disable-mainkeyset-clear
|
||||
value: false # for debugging
|
||||
|
||||
- name: zen.keyboard.shortcuts.global.enabled
|
||||
value: true
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
content/browser/zen-components/ZenKeyboardShortcuts.mjs (../../zen/kbs/ZenKeyboardShortcuts.mjs)
|
||||
- name: zen.live-folders.github.skip-new-pr-ui-check
|
||||
value: false
|
||||
@@ -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"/>
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-welcome.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-media-controls.css" />
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-download-box-animation.css" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-little-window.css" />
|
||||
</linkset>
|
||||
|
||||
# Startup "preloaded" scripts that requre globals such as gBrowser and gURLBar
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include ../../../zen/mods/jar.inc.mn
|
||||
#include ../../../zen/spaces/jar.inc.mn
|
||||
#include ../../../zen/tabs/jar.inc.mn
|
||||
#include ../../../zen/kbs/jar.inc.mn
|
||||
#include ../../../zen/glance/jar.inc.mn
|
||||
#include ../../../zen/folders/jar.inc.mn
|
||||
#include ../../../zen/welcome/jar.inc.mn
|
||||
@@ -18,4 +17,6 @@
|
||||
#include ../../../zen/images/jar.inc.mn
|
||||
#include ../../../zen/vendor/jar.inc.mn
|
||||
#include ../../../zen/fonts/jar.inc.mn
|
||||
#include ../../../zen/boosts/jar.inc.mn
|
||||
#include ../../../zen/live-folders/jar.inc.mn
|
||||
#include ../../../zen/little-window/jar.inc.mn
|
||||
|
||||
@@ -68,4 +68,6 @@
|
||||
<command id="cmd_zenNewLiveFolder" />
|
||||
|
||||
<command id="cmd_zenDuplicateTab" />
|
||||
|
||||
<command id="cmd_zenNewLittleWindow" />
|
||||
</commandset>
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
<link rel="localization" href="browser/zen-menubar.ftl"/>
|
||||
<link rel="localization" href="browser/zen-vertical-tabs.ftl"/>
|
||||
<link rel="localization" href="browser/zen-folders.ftl"/>
|
||||
<link rel="localization" href="browser/zen-boosts.ftl"/>
|
||||
<link rel="localization" href="browser/zen-live-folders.ftl"/>
|
||||
</linkset>
|
||||
|
||||
@@ -75,6 +75,14 @@
|
||||
data-l10n-id="unified-extensions-manage-extensions"
|
||||
hidden="true" />
|
||||
</vbox>
|
||||
<vbox class="zen-site-data-section">
|
||||
<hbox class="zen-site-data-section-header">
|
||||
<label data-l10n-id="zen-site-data-boosts" flex="1" />
|
||||
</hbox>
|
||||
<vbox id="zen-site-data-boost-list">
|
||||
<!-- settings will be inserted here -->
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox class="zen-site-data-section">
|
||||
<hbox class="zen-site-data-section-header">
|
||||
<label data-l10n-id="zen-site-data-settings" flex="1" />
|
||||
@@ -87,6 +95,8 @@
|
||||
<hbox id="zen-site-data-footer">
|
||||
<toolbarbutton id="zen-site-data-security-info"
|
||||
class="subviewbutton zen-interactive-button" />
|
||||
<toolbarbutton id="zen-site-data-boost" data-l10n-id="zen-site-data-create-boost"
|
||||
class="subviewbutton zen-interactive-button" />
|
||||
<toolbarbutton id="zen-site-data-actions"
|
||||
class="subviewbutton zen-interactive-button"
|
||||
closemenu="none"
|
||||
|
||||
23
src/browser/base/content/zen-panels/spaces-search.inc
Normal file
23
src/browser/base/content/zen-panels/spaces-search.inc
Normal file
@@ -0,0 +1,23 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
<panel id="zen-spaces-popup"
|
||||
nonnativepopover="true"
|
||||
type="arrow"
|
||||
orient="vertical"
|
||||
side="bottom"
|
||||
hidden="true"
|
||||
consumeoutsideclicks="never">
|
||||
<hbox class="zen-spaces-list-header" flex="1">
|
||||
<image class="zen-spaces-list-search-icon" src="chrome://global/skin/icons/search-glass.svg"/>
|
||||
<html:input id="zen-spaces-list-search"
|
||||
data-l10n-id="zen-spaces-search-placeholder"
|
||||
type="search" />
|
||||
</hbox>
|
||||
<scrollbox class="zen-spaces-list-scrollbox" flex="1">
|
||||
<vbox id="zen-spaces-list"></vbox>
|
||||
<hbox id="zen-spaces-search-no-results" hidden="true" flex="1"
|
||||
data-l10n-id="zen-spaces-search-no-results" />
|
||||
</scrollbox>
|
||||
</panel>
|
||||
@@ -5,6 +5,7 @@
|
||||
#include zen-panels/theme-picker.inc
|
||||
#include zen-panels/emojis-picker.inc
|
||||
#include zen-panels/folders-search.inc
|
||||
#include zen-panels/spaces-search.inc
|
||||
#include zen-panels/site-data.inc
|
||||
|
||||
#include zen-panels/popups.inc
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -8,13 +8,16 @@ const { nsZenMultiWindowFeature } = ChromeUtils.importESModule(
|
||||
{ global: "current" }
|
||||
);
|
||||
|
||||
const { nsKeyShortcutModifiers } = ChromeUtils.importESModule(
|
||||
"chrome://browser/content/zen-components/ZenKeyboardShortcuts.mjs",
|
||||
{
|
||||
global: "current",
|
||||
}
|
||||
const {
|
||||
nsKeyShortcutModifiers,
|
||||
ZenKeyboardShortcuts,
|
||||
VALID_SHORTCUT_GROUPS,
|
||||
} = ChromeUtils.importESModule(
|
||||
"resource:///modules/zen/ZenKeyboardShortcuts.sys.mjs"
|
||||
);
|
||||
|
||||
const gZenKeyboardShortcutsManager = ZenKeyboardShortcuts.manager;
|
||||
|
||||
var gZenMarketplaceManager = {
|
||||
async init() {
|
||||
const checkForUpdates = document.getElementById("zenThemeMarketplaceCheckForUpdates");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/tabbrowser/content/drag-and-drop.js b/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
index b93a8fcc8b76ed888524959cbe7d6865fe830f2f..fa192837d06e28bb847acbbd462ad3229ebdc2d2 100644
|
||||
index b93a8fcc8b76ed888524959cbe7d6865fe830f2f..9f39316e33daa8a272e48c84d9e07ad9df197586 100644
|
||||
--- a/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
+++ b/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
@@ -35,6 +35,9 @@
|
||||
@@ -47,7 +47,7 @@ index b93a8fcc8b76ed888524959cbe7d6865fe830f2f..fa192837d06e28bb847acbbd462ad322
|
||||
event.stopPropagation();
|
||||
+ if (draggedTab && dropEffect == "move") {
|
||||
+ this.handle_drop_transition?.(draggedTab._dragData.dropElement, draggedTab, movingTabs, draggedTab._dragData.dropBefore);
|
||||
+ gZenPinnedTabManager.moveToAnotherTabContainerIfNecessary(event, draggedTab, movingTabs, this._getDropIndex(event));
|
||||
+ [draggedTab, movingTabs] = gZenPinnedTabManager.moveToAnotherTabContainerIfNecessary(event, draggedTab, movingTabs, this._getDropIndex(event));
|
||||
+ }
|
||||
if (draggedTab && dropEffect == "copy") {
|
||||
let duplicatedDraggedTab;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js
|
||||
index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..3ac8d2facb224ae39d2199f2c5ec08b77e0c1dfd 100644
|
||||
index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..fb9ec4deb5871bc0ba57c323a413f07440e9aa42 100644
|
||||
--- a/browser/components/tabbrowser/content/tab.js
|
||||
+++ b/browser/components/tabbrowser/content/tab.js
|
||||
@@ -21,6 +21,7 @@
|
||||
@@ -151,7 +151,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..3ac8d2facb224ae39d2199f2c5ec08b7
|
||||
on_click(event) {
|
||||
if (event.button != 0) {
|
||||
return;
|
||||
@@ -617,14 +656,23 @@
|
||||
@@ -617,14 +656,31 @@
|
||||
trigger: "alt_click",
|
||||
});
|
||||
}
|
||||
@@ -159,9 +159,17 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..3ac8d2facb224ae39d2199f2c5ec08b7
|
||||
+ !event.target.classList.contains("tab-close-button") &&
|
||||
+ !event.target.classList.contains("tab-icon-overlay") &&
|
||||
+ !event.target.classList.contains("tab-audio-button") &&
|
||||
+ !this.splitView
|
||||
+ Services.prefs.getBoolPref("zen.splitView.enable-tab-click-split", false)
|
||||
+ ) {
|
||||
+ gZenViewSplitter.contextSplitTabs(this);
|
||||
+ if (!this.splitView) {
|
||||
+ gZenViewSplitter.contextSplitTabs(this);
|
||||
+ } else {
|
||||
+ gBrowser.selectedTab = this;
|
||||
+ gZenViewSplitter.removeTabFromGroup(this, undefined, {
|
||||
+ forUnsplit: true,
|
||||
+ changeTab: false
|
||||
+ });
|
||||
+ }
|
||||
+ }
|
||||
return;
|
||||
}
|
||||
@@ -176,7 +184,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..3ac8d2facb224ae39d2199f2c5ec08b7
|
||||
gBrowser.multiSelectedTabsCount > 0 &&
|
||||
!event.target.classList.contains("tab-close-button") &&
|
||||
!event.target.classList.contains("tab-icon-overlay") &&
|
||||
@@ -636,8 +684,9 @@
|
||||
@@ -636,8 +692,9 @@
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -188,7 +196,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..3ac8d2facb224ae39d2199f2c5ec08b7
|
||||
) {
|
||||
if (this.activeMediaBlocked) {
|
||||
if (this.multiselected) {
|
||||
@@ -655,7 +704,7 @@
|
||||
@@ -655,7 +712,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,7 +205,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..3ac8d2facb224ae39d2199f2c5ec08b7
|
||||
if (this.multiselected) {
|
||||
gBrowser.removeMultiSelectedTabs(
|
||||
lazy.TabMetrics.userTriggeredContext(
|
||||
@@ -675,6 +724,14 @@
|
||||
@@ -675,6 +732,14 @@
|
||||
// (see tabbrowser-tabs 'click' handler).
|
||||
gBrowser.tabContainer._blockDblClick = true;
|
||||
}
|
||||
@@ -212,7 +220,7 @@ index 2e02bad1a7c89b4c3b5aee1e14c13bb953a64eb6..3ac8d2facb224ae39d2199f2c5ec08b7
|
||||
}
|
||||
|
||||
on_dblclick(event) {
|
||||
@@ -698,6 +755,8 @@
|
||||
@@ -698,6 +763,8 @@
|
||||
animate: true,
|
||||
triggeringEvent: event,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
|
||||
index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff606d98f1e 100644
|
||||
index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..3a21c40eb70621968b5fbfc4b9b6d8412310b7b7 100644
|
||||
--- a/browser/components/tabbrowser/content/tabbrowser.js
|
||||
+++ b/browser/components/tabbrowser/content/tabbrowser.js
|
||||
@@ -413,6 +413,7 @@
|
||||
@@ -466,7 +466,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
);
|
||||
tabsFragment.appendChild(tabGroup.node);
|
||||
}
|
||||
@@ -4598,9 +4729,23 @@
|
||||
@@ -4598,9 +4729,21 @@
|
||||
// to remove the old selected tab.
|
||||
if (tabToSelect) {
|
||||
let leftoverTab = this.selectedTab;
|
||||
@@ -482,15 +482,13 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
+ gZenWorkspaces._initialTab._shouldRemove = true;
|
||||
+ }
|
||||
+ }
|
||||
+ } else {
|
||||
+ gZenWorkspaces._keepSelectedTab = !window.__isNewZenWindow;
|
||||
}
|
||||
+ delete window.__isNewZenWindow;
|
||||
+ this._hasAlreadyInitializedZenSessionStore = true;
|
||||
|
||||
if (tabs.length > 1 || !tabs[0].selected) {
|
||||
this._updateTabsAfterInsert();
|
||||
@@ -4791,11 +4936,14 @@
|
||||
@@ -4791,11 +4934,14 @@
|
||||
if (ownerTab) {
|
||||
tab.owner = ownerTab;
|
||||
}
|
||||
@@ -506,7 +504,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
if (
|
||||
!bulkOrderedOpen &&
|
||||
((openerTab &&
|
||||
@@ -4807,7 +4955,7 @@
|
||||
@@ -4807,7 +4953,7 @@
|
||||
let lastRelatedTab =
|
||||
openerTab && this._lastRelatedTabMap.get(openerTab);
|
||||
let previousTab = lastRelatedTab || openerTab || this.selectedTab;
|
||||
@@ -515,7 +513,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
tabGroup = previousTab.group;
|
||||
}
|
||||
if (
|
||||
@@ -4823,7 +4971,7 @@
|
||||
@@ -4823,7 +4969,7 @@
|
||||
previousTab.splitview
|
||||
) + 1;
|
||||
} else if (previousTab.visible) {
|
||||
@@ -524,7 +522,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
} else if (previousTab == FirefoxViewHandler.tab) {
|
||||
elementIndex = 0;
|
||||
}
|
||||
@@ -4851,14 +4999,14 @@
|
||||
@@ -4851,14 +4997,14 @@
|
||||
}
|
||||
// Ensure index is within bounds.
|
||||
if (tab.pinned) {
|
||||
@@ -543,7 +541,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
if (pinned && !itemAfter?.pinned) {
|
||||
itemAfter = null;
|
||||
@@ -4875,7 +5023,7 @@
|
||||
@@ -4875,7 +5021,7 @@
|
||||
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
|
||||
@@ -552,7 +550,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
if (
|
||||
(this.isTab(itemAfter) && itemAfter.group == tabGroup) ||
|
||||
this.isSplitViewWrapper(itemAfter)
|
||||
@@ -4906,7 +5054,11 @@
|
||||
@@ -4906,7 +5052,11 @@
|
||||
const tabContainer = pinned
|
||||
? this.tabContainer.pinnedTabsContainer
|
||||
: this.tabContainer;
|
||||
@@ -564,7 +562,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
|
||||
if (tab.group?.collapsed) {
|
||||
@@ -4921,6 +5073,7 @@
|
||||
@@ -4921,6 +5071,7 @@
|
||||
if (pinned) {
|
||||
this._updateTabBarForPinnedTabs();
|
||||
}
|
||||
@@ -572,7 +570,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
TabBarVisibility.update();
|
||||
}
|
||||
@@ -5469,6 +5622,7 @@
|
||||
@@ -5469,6 +5620,7 @@
|
||||
telemetrySource,
|
||||
} = {}
|
||||
) {
|
||||
@@ -580,7 +578,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
|
||||
// can be considered equivalent to closing the window.
|
||||
if (
|
||||
@@ -5558,6 +5712,7 @@
|
||||
@@ -5558,6 +5710,7 @@
|
||||
if (lastToClose) {
|
||||
this.removeTab(lastToClose, aParams);
|
||||
}
|
||||
@@ -588,7 +586,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -5603,6 +5758,14 @@
|
||||
@@ -5603,6 +5756,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -603,7 +601,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
let isVisibleTab = aTab.visible;
|
||||
// We have to sample the tab width now, since _beginRemoveTab might
|
||||
// end up modifying the DOM in such a way that aTab gets a new
|
||||
@@ -5610,6 +5773,9 @@
|
||||
@@ -5610,6 +5771,9 @@
|
||||
// state).
|
||||
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
|
||||
let isLastTab = this.#isLastTabInWindow(aTab);
|
||||
@@ -613,7 +611,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
if (
|
||||
!this._beginRemoveTab(aTab, {
|
||||
closeWindowFastpath: true,
|
||||
@@ -5621,13 +5787,14 @@
|
||||
@@ -5621,13 +5785,14 @@
|
||||
telemetrySource,
|
||||
})
|
||||
) {
|
||||
@@ -629,7 +627,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
let lockTabSizing =
|
||||
!this.tabContainer.verticalMode &&
|
||||
!aTab.pinned &&
|
||||
@@ -5658,7 +5825,13 @@
|
||||
@@ -5658,7 +5823,13 @@
|
||||
// We're not animating, so we can cancel the animation stopwatch.
|
||||
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
|
||||
aTab._closeTimeAnimTimerId = null;
|
||||
@@ -644,7 +642,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5792,7 +5965,7 @@
|
||||
@@ -5792,7 +5963,7 @@
|
||||
closeWindowWithLastTab != null
|
||||
? closeWindowWithLastTab
|
||||
: !window.toolbar.visible ||
|
||||
@@ -653,7 +651,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
if (closeWindow) {
|
||||
// We've already called beforeunload on all the relevant tabs if we get here,
|
||||
@@ -5816,6 +5989,7 @@
|
||||
@@ -5816,6 +5987,7 @@
|
||||
|
||||
newTab = true;
|
||||
}
|
||||
@@ -661,7 +659,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
aTab._endRemoveArgs = [closeWindow, newTab];
|
||||
|
||||
// swapBrowsersAndCloseOther will take care of closing the window without animation.
|
||||
@@ -5856,13 +6030,7 @@
|
||||
@@ -5856,13 +6028,7 @@
|
||||
aTab._mouseleave();
|
||||
|
||||
if (newTab) {
|
||||
@@ -676,7 +674,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
} else {
|
||||
TabBarVisibility.update();
|
||||
}
|
||||
@@ -5995,6 +6163,7 @@
|
||||
@@ -5995,6 +6161,7 @@
|
||||
this.tabs[i]._tPos = i;
|
||||
}
|
||||
|
||||
@@ -684,7 +682,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
if (!this._windowIsClosing) {
|
||||
// update tab close buttons state
|
||||
this.tabContainer._updateCloseButtons();
|
||||
@@ -6180,6 +6349,7 @@
|
||||
@@ -6180,6 +6347,7 @@
|
||||
memory_after: await getTotalMemoryUsage(),
|
||||
time_to_unload_in_ms: timeElapsed,
|
||||
});
|
||||
@@ -692,7 +690,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6225,6 +6395,7 @@
|
||||
@@ -6225,6 +6393,7 @@
|
||||
}
|
||||
|
||||
let excludeTabs = new Set(aExcludeTabs);
|
||||
@@ -700,7 +698,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
// If this tab has a successor, it should be selectable, since
|
||||
// hiding or closing a tab removes that tab as a successor.
|
||||
@@ -6237,15 +6408,22 @@
|
||||
@@ -6237,15 +6406,22 @@
|
||||
!excludeTabs.has(aTab.owner) &&
|
||||
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
|
||||
) {
|
||||
@@ -725,7 +723,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
let tab = this.tabContainer.findNextTab(aTab, {
|
||||
direction: 1,
|
||||
filter: _tab => remainingTabs.includes(_tab),
|
||||
@@ -6259,7 +6437,7 @@
|
||||
@@ -6259,7 +6435,7 @@
|
||||
}
|
||||
|
||||
if (tab) {
|
||||
@@ -734,7 +732,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
|
||||
// If no qualifying visible tab was found, see if there is a tab in
|
||||
@@ -6280,7 +6458,7 @@
|
||||
@@ -6280,7 +6456,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
@@ -743,7 +741,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
|
||||
_blurTab(aTab) {
|
||||
@@ -6291,7 +6469,7 @@
|
||||
@@ -6291,7 +6467,7 @@
|
||||
* @returns {boolean}
|
||||
* False if swapping isn't permitted, true otherwise.
|
||||
*/
|
||||
@@ -752,7 +750,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
// Do not allow transfering a private tab to a non-private window
|
||||
// and vice versa.
|
||||
if (
|
||||
@@ -6345,6 +6523,7 @@
|
||||
@@ -6345,6 +6521,7 @@
|
||||
// fire the beforeunload event in the process. Close the other
|
||||
// window if this was its last tab.
|
||||
if (
|
||||
@@ -760,7 +758,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
!remoteBrowser._beginRemoveTab(aOtherTab, {
|
||||
adoptedByTab: aOurTab,
|
||||
closeWindowWithLastTab: true,
|
||||
@@ -6356,7 +6535,7 @@
|
||||
@@ -6356,7 +6533,7 @@
|
||||
// If this is the last tab of the window, hide the window
|
||||
// immediately without animation before the docshell swap, to avoid
|
||||
// about:blank being painted.
|
||||
@@ -769,7 +767,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
if (closeWindow) {
|
||||
let win = aOtherTab.ownerGlobal;
|
||||
win.windowUtils.suppressAnimation(true);
|
||||
@@ -6484,11 +6663,13 @@
|
||||
@@ -6484,11 +6661,13 @@
|
||||
}
|
||||
|
||||
// Finish tearing down the tab that's going away.
|
||||
@@ -783,7 +781,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
this.setTabTitle(aOurTab);
|
||||
|
||||
@@ -6690,10 +6871,10 @@
|
||||
@@ -6690,10 +6869,10 @@
|
||||
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
|
||||
}
|
||||
|
||||
@@ -796,7 +794,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
aTab.selected ||
|
||||
aTab.closing ||
|
||||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
|
||||
@@ -6753,7 +6934,8 @@
|
||||
@@ -6753,7 +6932,8 @@
|
||||
* @param {object} [aOptions={}]
|
||||
* Key-value pairs that will be serialized into the features string.
|
||||
*/
|
||||
@@ -806,7 +804,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
if (this.tabs.length == 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -6770,7 +6952,7 @@
|
||||
@@ -6770,7 +6950,7 @@
|
||||
// tell a new window to take the "dropped" tab
|
||||
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
args.appendElement(aTab.splitview ?? aTab);
|
||||
@@ -815,7 +813,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
private: PrivateBrowsingUtils.isWindowPrivate(window),
|
||||
features: Object.entries(aOptions)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
@@ -6778,6 +6960,8 @@
|
||||
@@ -6778,6 +6958,8 @@
|
||||
openerWindow: window,
|
||||
args,
|
||||
});
|
||||
@@ -824,7 +822,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6890,7 +7074,7 @@
|
||||
@@ -6890,7 +7072,7 @@
|
||||
* `true` if element is a `<tab-group>`
|
||||
*/
|
||||
isTabGroup(element) {
|
||||
@@ -833,7 +831,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6975,8 +7159,8 @@
|
||||
@@ -6975,8 +7157,8 @@
|
||||
}
|
||||
|
||||
// Don't allow mixing pinned and unpinned tabs.
|
||||
@@ -844,7 +842,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
} else {
|
||||
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
|
||||
}
|
||||
@@ -7005,13 +7189,19 @@
|
||||
@@ -7005,13 +7187,19 @@
|
||||
this.#handleTabMove(
|
||||
element,
|
||||
() => {
|
||||
@@ -866,7 +864,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
let useAfter = false;
|
||||
if (this.isTab(element)) {
|
||||
useAfter = neighbor && tabIndex > element._tPos;
|
||||
@@ -7076,23 +7266,31 @@
|
||||
@@ -7076,23 +7264,31 @@
|
||||
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
|
||||
if (this.isTabGroupLabel(targetElement)) {
|
||||
targetElement = targetElement.group;
|
||||
@@ -904,7 +902,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
} else if (!element.pinned && targetElement && targetElement.pinned) {
|
||||
// If the caller asks to move an unpinned element next to a pinned
|
||||
// tab, move the unpinned element to be the first unpinned element
|
||||
@@ -7105,12 +7303,35 @@
|
||||
@@ -7105,12 +7301,35 @@
|
||||
// move the tab group right before the first unpinned tab.
|
||||
// 4. Moving a tab group and the first unpinned tab is grouped:
|
||||
// move the tab group right before the first unpinned tab's tab group.
|
||||
@@ -941,7 +939,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
// We want to include the splitview wrapper if it's the targetElement, but
|
||||
// not in the case where we want to reverse tabs within the same splitview.
|
||||
@@ -7119,6 +7340,7 @@
|
||||
@@ -7119,6 +7338,7 @@
|
||||
}
|
||||
|
||||
let getContainer = () =>
|
||||
@@ -949,7 +947,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
element.pinned
|
||||
? this.tabContainer.pinnedTabsContainer
|
||||
: this.tabContainer;
|
||||
@@ -7127,11 +7349,15 @@
|
||||
@@ -7127,11 +7347,15 @@
|
||||
element,
|
||||
() => {
|
||||
if (moveBefore) {
|
||||
@@ -966,7 +964,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
},
|
||||
metricsContext
|
||||
@@ -7205,11 +7431,15 @@
|
||||
@@ -7205,11 +7429,15 @@
|
||||
* @param {TabMetricsContext} [metricsContext]
|
||||
*/
|
||||
moveTabToExistingGroup(aTab, aGroup, metricsContext) {
|
||||
@@ -985,7 +983,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
if (aTab.group && aTab.group.id === aGroup.id) {
|
||||
return;
|
||||
@@ -7281,6 +7511,7 @@
|
||||
@@ -7281,6 +7509,7 @@
|
||||
|
||||
let state = {
|
||||
tabIndex: tab._tPos,
|
||||
@@ -993,7 +991,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
};
|
||||
if (tab.visible) {
|
||||
state.elementIndex = tab.elementIndex;
|
||||
@@ -7312,7 +7543,7 @@
|
||||
@@ -7312,7 +7541,7 @@
|
||||
let changedSplitView =
|
||||
previousTabState.splitViewId != currentTabState.splitViewId;
|
||||
|
||||
@@ -1002,7 +1000,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
tab.dispatchEvent(
|
||||
new CustomEvent("TabMove", {
|
||||
bubbles: true,
|
||||
@@ -7354,6 +7585,10 @@
|
||||
@@ -7354,6 +7583,10 @@
|
||||
|
||||
moveActionCallback();
|
||||
|
||||
@@ -1013,7 +1011,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
// Clear tabs cache after moving nodes because the order of tabs may have
|
||||
// changed.
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
@@ -7404,7 +7639,22 @@
|
||||
@@ -7404,7 +7637,22 @@
|
||||
* @returns {object}
|
||||
* The new tab in the current window, null if the tab couldn't be adopted.
|
||||
*/
|
||||
@@ -1037,7 +1035,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
// Swap the dropped tab with a new one we create and then close
|
||||
// it in the other window (making it seem to have moved between
|
||||
// windows). We also ensure that the tab we create to swap into has
|
||||
@@ -7447,6 +7697,8 @@
|
||||
@@ -7447,6 +7695,8 @@
|
||||
}
|
||||
params.skipLoad = true;
|
||||
let newTab = this.addWebTab("about:blank", params);
|
||||
@@ -1046,7 +1044,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
aTab.container.tabDragAndDrop.finishAnimateTabMove();
|
||||
|
||||
@@ -8149,7 +8401,7 @@
|
||||
@@ -8149,7 +8399,7 @@
|
||||
// preventDefault(). It will still raise the window if appropriate.
|
||||
return;
|
||||
}
|
||||
@@ -1055,7 +1053,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
window.focus();
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
@@ -8166,7 +8418,6 @@
|
||||
@@ -8166,7 +8416,6 @@
|
||||
|
||||
on_TabGroupCollapse(aEvent) {
|
||||
aEvent.target.tabs.forEach(tab => {
|
||||
@@ -1063,7 +1061,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8500,7 +8751,9 @@
|
||||
@@ -8500,7 +8749,9 @@
|
||||
|
||||
let filter = this._tabFilters.get(tab);
|
||||
if (filter) {
|
||||
@@ -1073,7 +1071,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
|
||||
let listener = this._tabListeners.get(tab);
|
||||
if (listener) {
|
||||
@@ -9306,6 +9559,7 @@
|
||||
@@ -9306,6 +9557,7 @@
|
||||
aWebProgress.isTopLevel
|
||||
) {
|
||||
this.mTab.setAttribute("busy", "true");
|
||||
@@ -1081,7 +1079,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
gBrowser._tabAttrModified(this.mTab, ["busy"]);
|
||||
this.mTab._notselectedsinceload = !this.mTab.selected;
|
||||
}
|
||||
@@ -9386,6 +9640,7 @@
|
||||
@@ -9386,6 +9638,7 @@
|
||||
// known defaults. Note we use the original URL since about:newtab
|
||||
// redirects to a prerendered page.
|
||||
const shouldRemoveFavicon =
|
||||
@@ -1089,7 +1087,7 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
!this.mBrowser.mIconURL &&
|
||||
!ignoreBlank &&
|
||||
!(originalLocation.spec in FAVICON_DEFAULTS);
|
||||
@@ -9560,13 +9815,6 @@
|
||||
@@ -9560,13 +9813,6 @@
|
||||
this.mBrowser.originalURI = aRequest.originalURI;
|
||||
}
|
||||
|
||||
@@ -1103,11 +1101,12 @@ index 2643e1a2aa14ba5cb4a64a92e1c2dfa5f07d242f..d16fe62c8a67913dd1499b2665a22ff6
|
||||
}
|
||||
|
||||
let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
|
||||
@@ -10450,7 +10698,7 @@ var TabContextMenu = {
|
||||
@@ -10450,7 +10696,8 @@ var TabContextMenu = {
|
||||
);
|
||||
contextUnpinSelectedTabs.hidden =
|
||||
!this.contextTab.pinned || !this.multiselected;
|
||||
-
|
||||
+ gZenViewSplitter.updateContextMenuItems();
|
||||
+ gZenPinnedTabManager.updatePinnedTabContextMenu(this.contextTab);
|
||||
// Build Ask Chat items
|
||||
TabContextMenu.GenAI.buildTabMenu(
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs
|
||||
index b665adb1a1ce8bbae8df4cbea6c3248c3e4fb431..7a73907957d38caba5b650749abf80a01b51559d 100644
|
||||
index b665adb1a1ce8bbae8df4cbea6c3248c3e4fb431..1de7f9461b8ccbd4680b917e6dd5ba3c02f69a94 100644
|
||||
--- a/browser/components/urlbar/UrlbarView.sys.mjs
|
||||
+++ b/browser/components/urlbar/UrlbarView.sys.mjs
|
||||
@@ -640,7 +640,7 @@ export class UrlbarView {
|
||||
@@ -11,7 +11,18 @@ index b665adb1a1ce8bbae8df4cbea6c3248c3e4fb431..7a73907957d38caba5b650749abf80a0
|
||||
// Try to reuse the cached top-sites context. If it's not cached, then
|
||||
// there will be a gap of time between when the input is focused and
|
||||
// when the view opens that can be perceived as flicker.
|
||||
@@ -2988,6 +2988,8 @@ export class UrlbarView {
|
||||
@@ -777,10 +777,6 @@ export class UrlbarView {
|
||||
}
|
||||
|
||||
// If search mode isn't active, close the view.
|
||||
- if (!this.input.searchMode) {
|
||||
- this.close();
|
||||
- return;
|
||||
- }
|
||||
|
||||
// Search mode is active. If the one-offs should be shown, make sure they
|
||||
// are enabled and show the view.
|
||||
@@ -2988,6 +2984,8 @@ export class UrlbarView {
|
||||
if (row?.hasAttribute("row-selectable")) {
|
||||
row?.toggleAttribute("selected", true);
|
||||
}
|
||||
@@ -20,7 +31,7 @@ index b665adb1a1ce8bbae8df4cbea6c3248c3e4fb431..7a73907957d38caba5b650749abf80a0
|
||||
if (element != row) {
|
||||
row?.toggleAttribute("descendant-selected", true);
|
||||
}
|
||||
@@ -3477,7 +3479,7 @@ export class UrlbarView {
|
||||
@@ -3477,7 +3475,7 @@ export class UrlbarView {
|
||||
}
|
||||
|
||||
#enableOrDisableRowWrap() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs
|
||||
index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f5c811a82 100644
|
||||
index b23244f9d3278918b016bb3fcab19687bc2e292a..e5de81b3060a1ee76b1a6aff2e4ae1ca50f2caa9 100644
|
||||
--- a/browser/components/urlbar/content/UrlbarInput.mjs
|
||||
+++ b/browser/components/urlbar/content/UrlbarInput.mjs
|
||||
@@ -90,6 +90,13 @@ const lazy = XPCOMUtils.declareLazy({
|
||||
@@ -75,7 +75,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
}
|
||||
|
||||
if (isCanonized) {
|
||||
@@ -2696,6 +2728,42 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2696,6 +2728,45 @@ export class UrlbarInput extends HTMLElement {
|
||||
await this.#updateLayoutBreakoutDimensions();
|
||||
}
|
||||
|
||||
@@ -84,7 +84,10 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
+ }
|
||||
+
|
||||
+ get zenUrlbarBehavior() {
|
||||
+ if (this.document.documentElement.hasAttribute("inDOMFullscreen")) {
|
||||
+ if (
|
||||
+ this.document.documentElement.hasAttribute("inDOMFullscreen") ||
|
||||
+ this.document.documentElement.hasAttribute("zen-little-window")
|
||||
+ ) {
|
||||
+ return "float";
|
||||
+ }
|
||||
+ return lazy.ZEN_URLBAR_BEHAVIOR;
|
||||
@@ -118,7 +121,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
startLayoutExtend() {
|
||||
if (!this.#allowBreakout || this.hasAttribute("breakout-extend")) {
|
||||
// Do not expand if the Urlbar does not support being expanded or it is
|
||||
@@ -2710,6 +2778,13 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2710,6 +2781,13 @@ export class UrlbarInput extends HTMLElement {
|
||||
|
||||
this.setAttribute("breakout-extend", "true");
|
||||
|
||||
@@ -132,7 +135,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
// Enable the animation only after the first extend call to ensure it
|
||||
// doesn't run when opening a new window.
|
||||
if (!this.hasAttribute("breakout-extend-animate")) {
|
||||
@@ -2729,6 +2804,27 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2729,6 +2807,27 @@ export class UrlbarInput extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,7 +163,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
this.removeAttribute("breakout-extend");
|
||||
this.#updateTextboxPosition();
|
||||
}
|
||||
@@ -2759,7 +2855,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -2759,7 +2858,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
forceUnifiedSearchButtonAvailable = false
|
||||
) {
|
||||
let prevState = this.getAttribute("pageproxystate");
|
||||
@@ -169,7 +172,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
this.setAttribute("pageproxystate", state);
|
||||
this._inputContainer.setAttribute("pageproxystate", state);
|
||||
this._identityBox?.setAttribute("pageproxystate", state);
|
||||
@@ -3031,10 +3127,12 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3031,10 +3130,12 @@ export class UrlbarInput extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
this.style.top = px(
|
||||
@@ -182,7 +185,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3093,9 +3191,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3093,9 +3194,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,7 +197,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
);
|
||||
this.style.setProperty(
|
||||
"--urlbar-height",
|
||||
@@ -3597,6 +3696,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3597,6 +3699,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
|
||||
_toggleActionOverride(event) {
|
||||
@@ -202,7 +205,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (
|
||||
event.keyCode == KeyEvent.DOM_VK_SHIFT ||
|
||||
event.keyCode == KeyEvent.DOM_VK_ALT ||
|
||||
@@ -3709,8 +3809,8 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -3709,8 +3812,8 @@ export class UrlbarInput extends HTMLElement {
|
||||
if (!this.#isAddressbar) {
|
||||
return val;
|
||||
}
|
||||
@@ -213,7 +216,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
: val;
|
||||
// Only trim value if the directionality doesn't change to RTL and we're not
|
||||
// showing a strikeout https protocol.
|
||||
@@ -4006,6 +4106,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4006,6 +4109,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
resultDetails = null,
|
||||
browser = this.window.gBrowser.selectedBrowser
|
||||
) {
|
||||
@@ -221,7 +224,19 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (this.#isAddressbar) {
|
||||
this.#prepareAddressbarLoad(
|
||||
url,
|
||||
@@ -4117,6 +4218,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4088,6 +4192,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
* @returns {"current" | "tabshifted" | "tab" | "save" | "window"}
|
||||
*/
|
||||
_whereToOpen(event) {
|
||||
+ if (this.document.documentElement.hasAttribute("zen-little-window")) {
|
||||
+ // Little windows are single-tab popups -- never spawn extra tabs
|
||||
+ // or new windows from the urlbar.
|
||||
+ return "current";
|
||||
+ }
|
||||
let isKeyboardEvent = KeyboardEvent.isInstance(event);
|
||||
let reuseEmpty = isKeyboardEvent;
|
||||
let where = undefined;
|
||||
@@ -4117,6 +4226,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
reuseEmpty = true;
|
||||
}
|
||||
@@ -232,7 +247,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (
|
||||
where == "tab" &&
|
||||
reuseEmpty &&
|
||||
@@ -4124,6 +4229,9 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4124,6 +4237,9 @@ export class UrlbarInput extends HTMLElement {
|
||||
) {
|
||||
where = "current";
|
||||
}
|
||||
@@ -242,7 +257,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
return where;
|
||||
}
|
||||
|
||||
@@ -4378,6 +4486,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4378,6 +4494,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
this.setResultForCurrentValue(null);
|
||||
this.handleCommand();
|
||||
this.controller.clearLastQueryContextCache();
|
||||
@@ -250,7 +265,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
|
||||
this._suppressStartQuery = false;
|
||||
});
|
||||
@@ -4385,7 +4494,6 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4385,7 +4502,6 @@ export class UrlbarInput extends HTMLElement {
|
||||
contextMenu.addEventListener("popupshowing", () => {
|
||||
// Close the results pane when the input field contextual menu is open,
|
||||
// because paste and go doesn't want a result selection.
|
||||
@@ -258,7 +273,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
|
||||
let controller =
|
||||
this.document.commandDispatcher.getControllerForCommand("cmd_paste");
|
||||
@@ -4541,7 +4649,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4541,7 +4657,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
if (!engineName && !source && !this.hasAttribute("searchmode")) {
|
||||
return;
|
||||
}
|
||||
@@ -271,7 +286,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (this._searchModeIndicatorTitle) {
|
||||
this._searchModeIndicatorTitle.textContent = "";
|
||||
this._searchModeIndicatorTitle.removeAttribute("data-l10n-id");
|
||||
@@ -4851,6 +4963,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4851,6 +4971,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
|
||||
this.document.l10n.setAttributes(
|
||||
this.inputField,
|
||||
@@ -279,7 +294,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
l10nId,
|
||||
l10nId == "urlbar-placeholder-with-name"
|
||||
? { name: engineName }
|
||||
@@ -4964,6 +5077,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -4964,6 +5085,11 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
|
||||
_on_click(event) {
|
||||
@@ -291,7 +306,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
switch (event.target) {
|
||||
case this.inputField:
|
||||
case this._inputContainer:
|
||||
@@ -5042,7 +5160,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5042,7 +5168,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +315,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
this.view.autoOpen({ event });
|
||||
} else {
|
||||
if (this._untrimOnFocusAfterKeydown) {
|
||||
@@ -5082,9 +5200,16 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5082,9 +5208,16 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
|
||||
_on_mousedown(event) {
|
||||
@@ -318,7 +333,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
if (
|
||||
event.composedTarget != this.inputField &&
|
||||
event.composedTarget != this._inputContainer
|
||||
@@ -5094,6 +5219,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5094,6 +5227,10 @@ export class UrlbarInput extends HTMLElement {
|
||||
|
||||
this.focusedViaMousedown = !this.focused;
|
||||
this._preventClickSelectsAll = this.focused;
|
||||
@@ -329,7 +344,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
|
||||
// Keep the focus status, since the attribute may be changed
|
||||
// upon calling this.focus().
|
||||
@@ -5129,7 +5258,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5129,7 +5266,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
}
|
||||
// Don't close the view when clicking on a tab; we may want to keep the
|
||||
// view open on tab switch, and the TabSelect event arrived earlier.
|
||||
@@ -338,7 +353,7 @@ index b23244f9d3278918b016bb3fcab19687bc2e292a..ade1f031bbb68202a37e6c9d3071a73f
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5411,7 +5540,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
@@ -5411,7 +5548,7 @@ export class UrlbarInput extends HTMLElement {
|
||||
// When we are in actions search mode we can show more results so
|
||||
// increase the limit.
|
||||
let maxResults =
|
||||
|
||||
@@ -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
|
||||
@@ -1,22 +1,37 @@
|
||||
diff --git a/browser/modules/BrowserWindowTracker.sys.mjs b/browser/modules/BrowserWindowTracker.sys.mjs
|
||||
index 9aecab66d8f23fac9f16cea2120a5fe903ae1122..692f2bfe3899a58925789503a6bb2a547cdbf7f3 100644
|
||||
index 9aecab66d8f23fac9f16cea2120a5fe903ae1122..e023c27bcb027d29ba9b3469eca5957d42040c46 100644
|
||||
--- a/browser/modules/BrowserWindowTracker.sys.mjs
|
||||
+++ b/browser/modules/BrowserWindowTracker.sys.mjs
|
||||
@@ -330,6 +330,7 @@ export const BrowserWindowTracker = {
|
||||
@@ -210,7 +210,8 @@ export const BrowserWindowTracker = {
|
||||
!win.closed &&
|
||||
(options.allowPopups || win.toolbar.visible) &&
|
||||
(options.allowTaskbarTabs ||
|
||||
- !win.document.documentElement.hasAttribute("taskbartab")) &&
|
||||
+ (!win.document.documentElement.hasAttribute("taskbartab") &&
|
||||
+ !win.document.documentElement.hasAttribute("zen-little-window"))) &&
|
||||
(!("private" in options) ||
|
||||
lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
|
||||
lazy.PrivateBrowsingUtils.isWindowPrivate(win) == options.private)
|
||||
@@ -330,6 +331,8 @@ export const BrowserWindowTracker = {
|
||||
args = null,
|
||||
remote = undefined,
|
||||
fission = undefined,
|
||||
+ zenSyncedWindow = true,
|
||||
+ zenLittleWindow = false,
|
||||
} = options;
|
||||
|
||||
args = lazy.AIWindow.handleAIWindowOptions(options);
|
||||
@@ -386,6 +387,12 @@ export const BrowserWindowTracker = {
|
||||
@@ -386,6 +389,16 @@ export const BrowserWindowTracker = {
|
||||
windowFeatures,
|
||||
args
|
||||
);
|
||||
+ win._zenStartupSyncFlag = Services.prefs.getBoolPref("zen.window-sync.prefer-unsynced-windows")
|
||||
+ ? (zenSyncedWindow ? 'unsynced' : 'synced')
|
||||
+ : (zenSyncedWindow ? 'synced' : 'unsynced');
|
||||
+ if (zenLittleWindow) {
|
||||
+ win._zenStartupLittleWindow = true;
|
||||
+ win._zenStartupSyncFlag = 'unsynced';
|
||||
+ }
|
||||
+ if (win._zenStartupSyncFlag === 'unsynced' && openerWindow) {
|
||||
+ win._zenStartupUnsyncedUserContextId = openerWindow.gZenWorkspaces.getCurrentSpaceContainerId();
|
||||
+ }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/modules/URILoadingHelper.sys.mjs b/browser/modules/URILoadingHelper.sys.mjs
|
||||
index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee91598080730a 100644
|
||||
index a005dbdf84609622ef8054f73f78c0c290e76125..2d347ac12d53ae97b61750d421a489ce10af3376 100644
|
||||
--- a/browser/modules/URILoadingHelper.sys.mjs
|
||||
+++ b/browser/modules/URILoadingHelper.sys.mjs
|
||||
@@ -224,6 +224,7 @@ function openInWindow(url, params, sourceWindow) {
|
||||
@@ -19,7 +19,17 @@ index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee9159
|
||||
where = "tab";
|
||||
targetBrowser = null;
|
||||
} else if (
|
||||
@@ -974,7 +975,7 @@ export const URILoadingHelper = {
|
||||
@@ -724,7 +725,8 @@ export const URILoadingHelper = {
|
||||
"navigator:browser" &&
|
||||
(!skipPopups || top.toolbar.visible) &&
|
||||
(!skipTaskbarTabs ||
|
||||
- !top.document.documentElement.hasAttribute("taskbartab")) &&
|
||||
+ (!top.document.documentElement.hasAttribute("taskbartab") &&
|
||||
+ !top.document.documentElement.hasAttribute("zen-little-window"))) &&
|
||||
(!forceNonPrivate || !PrivateBrowsingUtils.isWindowPrivate(top))
|
||||
) {
|
||||
return top;
|
||||
@@ -974,7 +976,7 @@ export const URILoadingHelper = {
|
||||
ignoreQueryString || replaceQueryString,
|
||||
ignoreFragmentWhenComparing
|
||||
);
|
||||
@@ -28,7 +38,7 @@ index a005dbdf84609622ef8054f73f78c0c290e76125..d5bf6fb51c9af5e60f69a73612ee9159
|
||||
for (let i = 0; i < browsers.length; i++) {
|
||||
let browser = browsers[i];
|
||||
let browserCompare = cleanURL(
|
||||
@@ -1030,7 +1031,7 @@ export const URILoadingHelper = {
|
||||
@@ -1030,7 +1032,7 @@ export const URILoadingHelper = {
|
||||
);
|
||||
aSplitView.ownerGlobal.focus();
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#zen-welcome-start-button,
|
||||
.zen-toast button,
|
||||
.zen-current-workspace-indicator-chevron,
|
||||
.pinned-tabs-container-separator toolbarbutton {
|
||||
.pinned-tabs-container-separator toolbarbutton,
|
||||
.zen-permission-popup-boost-editor-button {
|
||||
-moz-context-properties: fill, fill-opacity !important;
|
||||
fill: currentColor !important;
|
||||
}
|
||||
@@ -39,10 +40,21 @@
|
||||
.close-icon,
|
||||
.zen-glance-sidebar-close,
|
||||
.zen-theme-picker-custom-list-item-remove,
|
||||
#appMenu-quit-button2 {
|
||||
#appMenu-quit-button2,
|
||||
#zen-boost-delete {
|
||||
list-style-image: url("close.svg") !important;
|
||||
}
|
||||
|
||||
#PanelUI-zen-emojis-picker-none,
|
||||
#zen-emojis-picker-none {
|
||||
list-style-image: url("trash.svg");
|
||||
}
|
||||
|
||||
#PanelUI-zen-gradient-generator-color-remove,
|
||||
#zen-gradient-generator-color-remove {
|
||||
list-style-image: url("unpin.svg") !important;
|
||||
}
|
||||
|
||||
#PanelUI-zen-emojis-picker-none {
|
||||
list-style-image: url("trash.svg");
|
||||
}
|
||||
@@ -117,7 +129,8 @@
|
||||
}
|
||||
|
||||
.zen-current-workspace-indicator-chevron,
|
||||
#PanelUI-zen-gradient-generator-color-page-right {
|
||||
#PanelUI-zen-gradient-generator-color-page-right,
|
||||
.zen-permission-popup-boost-editor-button {
|
||||
list-style-image: url("arrow-right.svg");
|
||||
}
|
||||
|
||||
@@ -412,7 +425,8 @@
|
||||
}
|
||||
|
||||
.panel-header > .subviewbutton-back,
|
||||
#PanelUI-zen-gradient-generator-color-page-left {
|
||||
#PanelUI-zen-gradient-generator-color-page-left,
|
||||
#zen-boost-back {
|
||||
list-style-image: url("arrow-left.svg") !important;
|
||||
}
|
||||
|
||||
@@ -504,6 +518,30 @@
|
||||
&:where([starred]) image {
|
||||
list-style-image: url("permissions-fill.svg");
|
||||
}
|
||||
&[boosting] image {
|
||||
color: var(--color-accent-primary);
|
||||
list-style-image: url("permissions-fill.svg");
|
||||
}
|
||||
|
||||
position: relative;
|
||||
|
||||
& .zen-site-data-boost-animation {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media not (prefers-reduced-motion: reduce) {
|
||||
#zen-site-data-icon-button[boosting] .zen-site-data-boost-animation {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--color-accent-primary);
|
||||
list-style-image: url("chrome://browser/content/zen-images/boost-indicator.svg");
|
||||
transform: translateX(-20%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.geo-icon {
|
||||
@@ -758,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;
|
||||
}
|
||||
|
||||
@@ -878,6 +917,34 @@
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
#zen-site-data-boost {
|
||||
border-radius: 6px;
|
||||
appearance: none;
|
||||
padding: 6px 10px 6px 10px;
|
||||
|
||||
position: relative;
|
||||
|
||||
list-style-image: url("paintbrush.svg");
|
||||
|
||||
& .toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media -moz-pref("zen.boosts.enabled", false) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& image {
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.boost-brush {
|
||||
list-style-image: url("boost.svg") !important;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#zen-site-data-security-info {
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
fill: currentColor;
|
||||
@@ -937,3 +1004,79 @@
|
||||
list-style-image: url("link.svg");
|
||||
fill-opacity: 0.65;
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle {
|
||||
list-style-image: url("text-title-case.svg");
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="uppercase"] {
|
||||
list-style-image: url("text-uppercase.svg");
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="lowercase"] {
|
||||
list-style-image: url("text-lowercase.svg");
|
||||
}
|
||||
|
||||
#zen-boost-code {
|
||||
list-style-image: url("brackets-curly.svg");
|
||||
}
|
||||
|
||||
#zen-boost-zap {
|
||||
list-style-image: url("bolt.svg");
|
||||
}
|
||||
|
||||
#zen-boost-controls {
|
||||
list-style-image: url("sliders.svg");
|
||||
}
|
||||
|
||||
#zen-boost-invert {
|
||||
list-style-image: url("lightbulb.svg");
|
||||
}
|
||||
|
||||
#zen-boost-disable {
|
||||
list-style-image: url("block.svg");
|
||||
}
|
||||
|
||||
#zen-boost-close {
|
||||
list-style-image: url("close.svg");
|
||||
|
||||
@media (-moz-platform: macos) {
|
||||
list-style-image: url("close-filled-round.svg");
|
||||
}
|
||||
|
||||
@media (-moz-platform: windows) {
|
||||
list-style-image: none;
|
||||
list-style: none;
|
||||
display: inline-flex;
|
||||
|
||||
&::before {
|
||||
font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets";
|
||||
content: "\e8bb";
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-magic-theme {
|
||||
list-style-image: url("sparkles.svg");
|
||||
}
|
||||
|
||||
#zen-boost-shuffle {
|
||||
list-style-image: url("arrow-rotate-anticlockwise.svg");
|
||||
}
|
||||
|
||||
#zen-boost-css-picker {
|
||||
list-style-image: url("eyedropper.svg");
|
||||
}
|
||||
|
||||
#zen-boost-css-inspector {
|
||||
list-style-image: url("hammer.svg");
|
||||
}
|
||||
|
||||
#zen-boost-save {
|
||||
list-style-image: url("downloads.svg");
|
||||
}
|
||||
|
||||
#zen-boost-load {
|
||||
list-style-image: url("open.svg");
|
||||
}
|
||||
|
||||
@@ -7,20 +7,27 @@
|
||||
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/nucleo/arrow-down.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/nucleo/arrow-left.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/nucleo/arrow-right.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-rotate-anticlockwise.svg (../shared/zen-icons/nucleo/arrow-rotate-anticlockwise.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/nucleo/arrow-up.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/nucleo/autoplay-media-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg)
|
||||
* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg)
|
||||
* skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg)
|
||||
* skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg)
|
||||
* skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg)
|
||||
* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg)
|
||||
* skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg)
|
||||
* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg)
|
||||
* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/nucleo/camera.svg)
|
||||
* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/nucleo/canvas-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/nucleo/canvas.svg)
|
||||
* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/nucleo/chevron.svg)
|
||||
* skin/classic/browser/zen-icons/close-filled-round.svg (../shared/zen-icons/nucleo/close-filled-round.svg)
|
||||
* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/nucleo/close.svg)
|
||||
* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/nucleo/container-tab.svg)
|
||||
* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/nucleo/cookies-fill.svg)
|
||||
@@ -44,6 +51,7 @@
|
||||
* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/nucleo/extension-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/nucleo/extension-fill.svg)
|
||||
* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/nucleo/extension.svg)
|
||||
* skin/classic/browser/zen-icons/eyedropper.svg (../shared/zen-icons/nucleo/eyedropper.svg)
|
||||
* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/nucleo/face-sun.svg)
|
||||
* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/nucleo/folder.svg)
|
||||
* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/nucleo/forget.svg)
|
||||
@@ -53,12 +61,14 @@
|
||||
* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/nucleo/geo-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/nucleo/geo-fill.svg)
|
||||
* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/nucleo/geo.svg)
|
||||
* skin/classic/browser/zen-icons/hammer.svg (../shared/zen-icons/nucleo/hammer.svg)
|
||||
* skin/classic/browser/zen-icons/heart-circle-fill.svg (../shared/zen-icons/nucleo/heart-circle-fill.svg)
|
||||
* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/nucleo/help.svg)
|
||||
* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/nucleo/history.svg)
|
||||
* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/nucleo/home.svg)
|
||||
* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/nucleo/info.svg)
|
||||
* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/nucleo/library.svg)
|
||||
* skin/classic/browser/zen-icons/lightbulb.svg (../shared/zen-icons/nucleo/lightbulb.svg)
|
||||
* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/nucleo/link.svg)
|
||||
* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/nucleo/mail.svg)
|
||||
* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/nucleo/manage.svg)
|
||||
@@ -78,6 +88,8 @@
|
||||
* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/nucleo/new-tab-image.svg)
|
||||
* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/nucleo/open.svg)
|
||||
* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/nucleo/page-portrait.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush-fill.svg (../shared/zen-icons/nucleo/paintbrush-fill.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush.svg (../shared/zen-icons/nucleo/paintbrush.svg)
|
||||
* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/nucleo/palette.svg)
|
||||
* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/nucleo/passwords.svg)
|
||||
* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/nucleo/permissions-fill.svg)
|
||||
@@ -110,13 +122,19 @@
|
||||
* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg)
|
||||
* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg)
|
||||
* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg)
|
||||
* skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg)
|
||||
* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg)
|
||||
* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg)
|
||||
* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/nucleo/split.svg)
|
||||
* skin/classic/browser/zen-icons/square-wand-sparkle.svg (../shared/zen-icons/nucleo/square-wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/nucleo/tab-audio-blocked-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/nucleo/tab-audio-muted-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/nucleo/tab-audio-playing-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/nucleo/tab.svg)
|
||||
* skin/classic/browser/zen-icons/text-lowercase.svg (../shared/zen-icons/nucleo/text-lowercase.svg)
|
||||
* skin/classic/browser/zen-icons/text-size.svg (../shared/zen-icons/nucleo/text-size.svg)
|
||||
* skin/classic/browser/zen-icons/text-title-case.svg (../shared/zen-icons/nucleo/text-title-case.svg)
|
||||
* skin/classic/browser/zen-icons/text-uppercase.svg (../shared/zen-icons/nucleo/text-uppercase.svg)
|
||||
* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/nucleo/tool-profiler.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/nucleo/tracking-protection-fill.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/nucleo/tracking-protection.svg)
|
||||
@@ -125,6 +143,7 @@
|
||||
* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/nucleo/unpin.svg)
|
||||
* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/nucleo/video-blocked-fill.svg)
|
||||
* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/nucleo/video-fill.svg)
|
||||
* skin/classic/browser/zen-icons/wand-sparkle.svg (../shared/zen-icons/nucleo/wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/nucleo/window.svg)
|
||||
* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/nucleo/xr-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/nucleo/xr-fill.svg)
|
||||
@@ -136,20 +155,27 @@
|
||||
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/nucleo/arrow-down.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/nucleo/arrow-left.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/nucleo/arrow-right.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-rotate-anticlockwise.svg (../shared/zen-icons/nucleo/arrow-rotate-anticlockwise.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/nucleo/arrow-up.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/nucleo/autoplay-media-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg)
|
||||
* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg)
|
||||
* skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg)
|
||||
* skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg)
|
||||
* skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg)
|
||||
* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg)
|
||||
* skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg)
|
||||
* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg)
|
||||
* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/nucleo/camera.svg)
|
||||
* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/nucleo/canvas-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/nucleo/canvas.svg)
|
||||
* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/nucleo/chevron.svg)
|
||||
* skin/classic/browser/zen-icons/close-filled-round.svg (../shared/zen-icons/nucleo/close-filled-round.svg)
|
||||
* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/nucleo/close.svg)
|
||||
* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/nucleo/container-tab.svg)
|
||||
* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/nucleo/cookies-fill.svg)
|
||||
@@ -173,6 +199,7 @@
|
||||
* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/nucleo/extension-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/nucleo/extension-fill.svg)
|
||||
* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/nucleo/extension.svg)
|
||||
* skin/classic/browser/zen-icons/eyedropper.svg (../shared/zen-icons/nucleo/eyedropper.svg)
|
||||
* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/nucleo/face-sun.svg)
|
||||
* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/nucleo/folder.svg)
|
||||
* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/nucleo/forget.svg)
|
||||
@@ -182,12 +209,14 @@
|
||||
* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/nucleo/geo-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/nucleo/geo-fill.svg)
|
||||
* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/nucleo/geo.svg)
|
||||
* skin/classic/browser/zen-icons/hammer.svg (../shared/zen-icons/nucleo/hammer.svg)
|
||||
* skin/classic/browser/zen-icons/heart-circle-fill.svg (../shared/zen-icons/nucleo/heart-circle-fill.svg)
|
||||
* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/nucleo/help.svg)
|
||||
* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/nucleo/history.svg)
|
||||
* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/nucleo/home.svg)
|
||||
* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/nucleo/info.svg)
|
||||
* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/nucleo/library.svg)
|
||||
* skin/classic/browser/zen-icons/lightbulb.svg (../shared/zen-icons/nucleo/lightbulb.svg)
|
||||
* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/nucleo/link.svg)
|
||||
* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/nucleo/mail.svg)
|
||||
* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/nucleo/manage.svg)
|
||||
@@ -207,6 +236,8 @@
|
||||
* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/nucleo/new-tab-image.svg)
|
||||
* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/nucleo/open.svg)
|
||||
* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/nucleo/page-portrait.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush-fill.svg (../shared/zen-icons/nucleo/paintbrush-fill.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush.svg (../shared/zen-icons/nucleo/paintbrush.svg)
|
||||
* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/nucleo/palette.svg)
|
||||
* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/nucleo/passwords.svg)
|
||||
* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/nucleo/permissions-fill.svg)
|
||||
@@ -239,13 +270,19 @@
|
||||
* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg)
|
||||
* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg)
|
||||
* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg)
|
||||
* skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg)
|
||||
* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg)
|
||||
* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg)
|
||||
* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/nucleo/split.svg)
|
||||
* skin/classic/browser/zen-icons/square-wand-sparkle.svg (../shared/zen-icons/nucleo/square-wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/nucleo/tab-audio-blocked-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/nucleo/tab-audio-muted-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/nucleo/tab-audio-playing-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/nucleo/tab.svg)
|
||||
* skin/classic/browser/zen-icons/text-lowercase.svg (../shared/zen-icons/nucleo/text-lowercase.svg)
|
||||
* skin/classic/browser/zen-icons/text-size.svg (../shared/zen-icons/nucleo/text-size.svg)
|
||||
* skin/classic/browser/zen-icons/text-title-case.svg (../shared/zen-icons/nucleo/text-title-case.svg)
|
||||
* skin/classic/browser/zen-icons/text-uppercase.svg (../shared/zen-icons/nucleo/text-uppercase.svg)
|
||||
* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/nucleo/tool-profiler.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/nucleo/tracking-protection-fill.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/nucleo/tracking-protection.svg)
|
||||
@@ -254,6 +291,7 @@
|
||||
* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/nucleo/unpin.svg)
|
||||
* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/nucleo/video-blocked-fill.svg)
|
||||
* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/nucleo/video-fill.svg)
|
||||
* skin/classic/browser/zen-icons/wand-sparkle.svg (../shared/zen-icons/nucleo/wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/nucleo/window.svg)
|
||||
* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/nucleo/xr-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/nucleo/xr-fill.svg)
|
||||
@@ -265,20 +303,27 @@
|
||||
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/nucleo/arrow-down.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/nucleo/arrow-left.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/nucleo/arrow-right.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-rotate-anticlockwise.svg (../shared/zen-icons/nucleo/arrow-rotate-anticlockwise.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/nucleo/arrow-up.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/nucleo/autoplay-media-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg)
|
||||
* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg)
|
||||
* skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg)
|
||||
* skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg)
|
||||
* skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg)
|
||||
* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg)
|
||||
* skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg)
|
||||
* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg)
|
||||
* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/nucleo/camera.svg)
|
||||
* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/nucleo/canvas-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/nucleo/canvas.svg)
|
||||
* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/nucleo/chevron.svg)
|
||||
* skin/classic/browser/zen-icons/close-filled-round.svg (../shared/zen-icons/nucleo/close-filled-round.svg)
|
||||
* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/nucleo/close.svg)
|
||||
* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/nucleo/container-tab.svg)
|
||||
* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/nucleo/cookies-fill.svg)
|
||||
@@ -302,6 +347,7 @@
|
||||
* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/nucleo/extension-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/nucleo/extension-fill.svg)
|
||||
* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/nucleo/extension.svg)
|
||||
* skin/classic/browser/zen-icons/eyedropper.svg (../shared/zen-icons/nucleo/eyedropper.svg)
|
||||
* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/nucleo/face-sun.svg)
|
||||
* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/nucleo/folder.svg)
|
||||
* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/nucleo/forget.svg)
|
||||
@@ -311,12 +357,14 @@
|
||||
* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/nucleo/geo-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/nucleo/geo-fill.svg)
|
||||
* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/nucleo/geo.svg)
|
||||
* skin/classic/browser/zen-icons/hammer.svg (../shared/zen-icons/nucleo/hammer.svg)
|
||||
* skin/classic/browser/zen-icons/heart-circle-fill.svg (../shared/zen-icons/nucleo/heart-circle-fill.svg)
|
||||
* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/nucleo/help.svg)
|
||||
* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/nucleo/history.svg)
|
||||
* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/nucleo/home.svg)
|
||||
* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/nucleo/info.svg)
|
||||
* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/nucleo/library.svg)
|
||||
* skin/classic/browser/zen-icons/lightbulb.svg (../shared/zen-icons/nucleo/lightbulb.svg)
|
||||
* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/nucleo/link.svg)
|
||||
* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/nucleo/mail.svg)
|
||||
* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/nucleo/manage.svg)
|
||||
@@ -336,6 +384,8 @@
|
||||
* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/nucleo/new-tab-image.svg)
|
||||
* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/nucleo/open.svg)
|
||||
* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/nucleo/page-portrait.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush-fill.svg (../shared/zen-icons/nucleo/paintbrush-fill.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush.svg (../shared/zen-icons/nucleo/paintbrush.svg)
|
||||
* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/nucleo/palette.svg)
|
||||
* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/nucleo/passwords.svg)
|
||||
* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/nucleo/permissions-fill.svg)
|
||||
@@ -368,13 +418,19 @@
|
||||
* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg)
|
||||
* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg)
|
||||
* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg)
|
||||
* skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg)
|
||||
* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg)
|
||||
* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg)
|
||||
* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/nucleo/split.svg)
|
||||
* skin/classic/browser/zen-icons/square-wand-sparkle.svg (../shared/zen-icons/nucleo/square-wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/nucleo/tab-audio-blocked-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/nucleo/tab-audio-muted-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/nucleo/tab-audio-playing-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/nucleo/tab.svg)
|
||||
* skin/classic/browser/zen-icons/text-lowercase.svg (../shared/zen-icons/nucleo/text-lowercase.svg)
|
||||
* skin/classic/browser/zen-icons/text-size.svg (../shared/zen-icons/nucleo/text-size.svg)
|
||||
* skin/classic/browser/zen-icons/text-title-case.svg (../shared/zen-icons/nucleo/text-title-case.svg)
|
||||
* skin/classic/browser/zen-icons/text-uppercase.svg (../shared/zen-icons/nucleo/text-uppercase.svg)
|
||||
* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/nucleo/tool-profiler.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/nucleo/tracking-protection-fill.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/nucleo/tracking-protection.svg)
|
||||
@@ -383,6 +439,7 @@
|
||||
* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/nucleo/unpin.svg)
|
||||
* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/nucleo/video-blocked-fill.svg)
|
||||
* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/nucleo/video-fill.svg)
|
||||
* skin/classic/browser/zen-icons/wand-sparkle.svg (../shared/zen-icons/nucleo/wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/nucleo/window.svg)
|
||||
* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/nucleo/xr-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/nucleo/xr-fill.svg)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="polyline1"
|
||||
d="M 1.88,3.305 2.288,6.25 5.232,5.843 M 3,13.071 c 1.304,1.919 3.505,3.179 6,3.179 4.004,0 7.25,-3.246 7.25,-7.25 0,-4.004 -3.246,-7.25 -7.25,-7.25 -3.031,0 -5.627,1.86 -6.71,4.5" />
|
||||
</g>
|
||||
</svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/block.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/block.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="circle1"
|
||||
d="M 16.25,9 A 7.25,7.25 0 0 1 9,16.25 7.25,7.25 0 0 1 1.75,9 7.25,7.25 0 0 1 9,1.75 7.25,7.25 0 0 1 16.25,9 Z M 14.127,3.8729999 3.8729999,14.127" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,5 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="1.5" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><polyline points="14 6.25 16.25 4 14 1.75" data-color="color-2"></polyline><path d="M6.596,2.75h-1.846c-1.105,0-2,.896-2,2V13.25c0,1.104,.895,2,2,2H13.25c1.105,0,2-.896,2-2v-4.846"></path><polyline points="11 6.25 8.75 4 11 1.75" data-color="color-2"></polyline></g></svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/bolt.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/bolt.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="M 14.7505,7.25 H 9.49905 L 9.81065,1.9868 C 9.82325,1.7732 9.55085,1.6734 9.42255,1.8446 l -6.3729,8.5055 C 2.92615,10.5149 3.04376,10.75 3.24976,10.75 h 5.25139 l -0.3116,5.2632 c -0.0126,0.2136 0.2598,0.3134 0.3881,0.1422 L 14.9506,7.6499 C 15.0741,7.4851 14.9565,7.25 14.7505,7.25 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
28
src/browser/themes/shared/zen-icons/nucleo/boost.svg
Normal file
28
src/browser/themes/shared/zen-icons/nucleo/boost.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 14.7 16.72"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3">
|
||||
<path
|
||||
id="circle3"
|
||||
class="cls-1"
|
||||
d="M 3.4,14.17 A 0.86000001,0.86000001 0 0 1 2.54,15.03 0.86000001,0.86000001 0 0 1 1.6799999,14.17 0.86000001,0.86000001 0 0 1 2.54,13.31 0.86000001,0.86000001 0 0 1 3.4,14.17 Z M 10.53,11.6 C 11.76,10.89 13.88,8.39 14.12,7.61 14.46,6.52 13.63,5.97 12.86,5.52 12.22,5.13 10.95,6.95 10.25,7.72 m -0.01,0 C 10.79,7.17 11.75,5.7 11.75,5.18 11.75,2.67 6.38,-0.99 6.8,1.16 7.09,2.63 5.72,5.24 5.17,6.11 M 5.08,6.35 C 4.76,6.03 4.24,6.02 3.91,6.33 L 3.22,6.98 C 2.88,7.3 2.87,7.85 3.2,8.18 l 1.69,1.69 -3.54,2.65 c -1.02,0.73 -1.15,2.2 -0.27,3.09 v 0 c 0.87,0.88 2.31,0.79 3.06,-0.18 l 2.71,-3.63 1.6,1.6 c 0.33,0.33 0.85,0.33 1.18,0 l 0.64,-0.63 c 0.33,-0.33 0.33,-0.86 0,-1.19 L 5.06,6.33 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="path2"
|
||||
d="m 11.75,15.25 h 1 c 1.105,0 2,-0.895 2,-2 V 10.625 C 14.75,9.728 15.478,9 16.375,9 15.478,9 14.75,8.272 14.75,7.375 V 4.75 c 0,-1.105 -0.895,-2 -2,-2 h -1 m -5.5,12.5 h -1 c -1.105,0 -2,-0.895 -2,-2 V 10.625 C 3.25,9.728 2.522,9 1.625,9 2.522,9 3.25,8.272 3.25,7.375 V 4.75 c 0,-1.105 0.895,-2 2,-2 h 1" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,28 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="20px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="20px"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="context-fill"
|
||||
stroke="none"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="m 480,-424 116,116 q 11,11 28,11 17,0 28,-11 11,-11 11,-28 0,-17 -11,-28 L 536,-480 652,-596 q 11,-11 11,-28 0,-17 -11,-28 -11,-11 -28,-11 -17,0 -28,11 L 480,-536 364,-652 q -11,-11 -28,-11 -17,0 -28,11 -11,11 -11,28 0,17 11,28 l 116,116 -116,116 q -11,11 -11,28 0,17 11,28 11,11 28,11 17,0 28,-11 z m 0,344 Q 397,-80 324,-111.5 251,-143 197,-197 143,-251 111.5,-324 80,-397 80,-480 q 0,-83 31.5,-156 31.5,-73 85.5,-127 54,-54 127,-85.5 73,-31.5 156,-31.5 83,0 156,31.5 73,31.5 127,85.5 54,54 85.5,127 31.5,73 31.5,156 0,83 -31.5,156 -31.5,73 -85.5,127 -54,54 -127,85.5 Q 563,-80 480,-80 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,5 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" stroke="context-fill" stroke-opacity="context-fill-opacity"><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m9.235 5.735-6.107 6.107a2.142 2.142 0 1 0 3.03 3.03l6.107-6.107M3.128 14.872 1.75 16.25"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m12.265 8.765 2.857-2.857a2.142 2.142 0 1 0-3.03-3.03L9.235 5.735M7.75 4.25l6 6"/></svg>
|
||||
5
src/browser/themes/shared/zen-icons/nucleo/hammer.svg
Normal file
5
src/browser/themes/shared/zen-icons/nucleo/hammer.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" stroke="context-fill" stroke-opacity="context-fill-opacity"><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m11.529 8.721-6.563 6.563a1.59 1.59 0 0 1-2.25 0h0a1.59 1.59 0 0 1 0-2.25l6.563-6.563"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m16.207 8.944-1.521 1.521a1 1 0 0 1-1.414 0l-7.53-7.531L6.676 2l4.191.493a1 1 0 0 1 .59.286l4.75 4.75a1 1 0 0 1 0 1.414Z"/></svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/lightbulb.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/lightbulb.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="M 14,6.75 C 14,3.637 11.154,1.188 7.922,1.863 5.99,2.266 4.447,3.856 4.088,5.796 3.654,8.14 4.859,10.255 6.75,11.211 v 3.039 c 0,1.105 0.895,2 2,2 h 0.5 c 1.105,0 2,-0.895 2,-2 V 11.211 C 12.88,10.387 14,8.701 14,6.75 Z m -7.25,6.5 h 4.5" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,27 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="16px"
|
||||
height="16px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="path2"
|
||||
d="M 8.97,9.291 C 8.301,8.62 7.416,8.25 6.476,8.25 H 6.47 C 5.534,8.252 4.653,8.622 3.99,9.293 c -0.952,0.962 -1.119,1.933 -1.252,2.712 -0.149,0.869 -0.247,1.442 -1.347,2.043 -0.266,0.145 -0.418,0.435 -0.387,0.736 0.032,0.301 0.241,0.554 0.53,0.641 1.058,0.318 2.185,0.58 3.306,0.58 1.438,0 2.867,-0.43 4.13,-1.724 1.371,-1.375 1.371,-3.614 0,-4.989 z m 7.348,-7.36 c -0.79,-0.789 -2.166,-0.79 -2.957,0 L 8.216,7.076 c 0.673,0.25 1.294,0.631 1.817,1.156 0.525,0.527 0.897,1.145 1.14,1.802 l 5.146,-5.146 c 0.815,-0.815 0.815,-2.142 0,-2.958 z" />
|
||||
</g>
|
||||
</svg>
|
||||
36
src/browser/themes/shared/zen-icons/nucleo/paintbrush.svg
Normal file
36
src/browser/themes/shared/zen-icons/nucleo/paintbrush.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="16px"
|
||||
height="16px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="path2"
|
||||
d="m 1.75,14.706 c 2.703,0.812 4.896,0.88 6.689,-0.955 C 9.52,12.666 9.52,10.906 8.439,9.82 7.358,8.734 5.613,8.718 4.523,9.82 2.75,11.612 4.298,13.314 1.75,14.706 Z M 6.956,9.044 13.534,2.466 c 0.621,-0.621 1.629,-0.621 2.25,0 v 0 c 0.621,0.621 0.621,1.629 0,2.25 l -6.578,6.578" />
|
||||
</g>
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3" />
|
||||
</svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/sliders.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/sliders.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g8">
|
||||
<path
|
||||
id="circle8"
|
||||
d="M 8.5,14.25 A 1.75,1.75 0 0 1 6.75,16 1.75,1.75 0 0 1 5,14.25 1.75,1.75 0 0 1 6.75,12.5 1.75,1.75 0 0 1 8.5,14.25 Z m 0,-10.5 A 1.75,1.75 0 0 1 6.75,5.5 1.75,1.75 0 0 1 5,3.75 1.75,1.75 0 0 1 6.75,2 1.75,1.75 0 0 1 8.5,3.75 Z M 12.75,9 A 1.75,1.75 0 0 1 11,10.75 1.75,1.75 0 0 1 9.25,9 1.75,1.75 0 0 1 11,7.25 1.75,1.75 0 0 1 12.75,9 Z m 3.5,5.25 h -5 M 5,14.25 H 1.75 m 14.5,-10.5 h -5 M 5,3.75 H 1.75 M 1.75,9 H 9 m 6.25,0 h 1" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,36 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg7"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs7" />
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g7">
|
||||
<path
|
||||
id="path7"
|
||||
d="m 6.25,9.75 2,2 m -1.74799,-9 H 4.75 c -1.105,0 -2,0.896 -2,2 V 9.00732 M 8.992,15.25 h 4.258 c 1.105,0 2,-0.896 2,-2 V 10.616 M 7.95696,8.04302 2.04396,13.956 c -0.39,0.39 -0.39,1.023 0,1.414 l 0.586,0.585 c 0.39,0.391 1.024,0.391 1.414,0 l 5.913,-5.912 c 0.39004,-0.39108 0.39004,-1.02402 0,-1.41502 l -0.586,-0.58496 c -0.39,-0.3911 -1.023,-0.3911 -1.414,0 z M 15.25,2.5 C 15.664,2.5 16,2.164 16,1.75 16,1.336 15.664,1 15.25,1 14.836,1 14.5,1.336 14.5,1.75 c 0,0.414 0.336,0.75 0.75,0.75 z M 16.659,5.98996 15.396,5.56894 14.975,4.30606 c -0.137,-0.408 -0.812,-0.408 -0.949,0 l -0.421,1.26288 -1.263,0.42102 c -0.204,0.068 -0.342,0.259 -0.342,0.474 0,0.215 0.138,0.406 0.342,0.474 l 1.263,0.42102 0.421,1.263 c 0.068,0.204 0.26,0.34198 0.475,0.34198 0.215,0 0.406,-0.13798 0.475,-0.34198 l 0.421,-1.263 1.263,-0.42102 c 0.204,-0.068 0.342,-0.259 0.342,-0.474 0,-0.215 -0.139,-0.406 -0.343,-0.474 z M 11.744,2.49201 10.798,2.17701 10.482,1.22999 c -0.102,-0.306002 -0.609,-0.306002 -0.711,0 L 9.45499,2.17701 8.509,2.49201 c -0.153,0.051 -0.25699,0.19402 -0.25699,0.35602 0,0.162 0.10399,0.30496 0.25699,0.35596 l 0.94599,0.315 0.31601,0.94702 c 0.051,0.153 0.19401,0.25598 0.355,0.25598 0.161,0 0.305,-0.10398 0.355,-0.25598 l 0.316,-0.94702 0.946,-0.315 C 11.896,3.15299 12,3.01003 12,2.84803 12,2.68603 11.897,2.54301 11.744,2.49201 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3">
|
||||
<path
|
||||
id="path3"
|
||||
d="m 14.75,6.75 v 6.303 c 0,1.214 -0.984,2.197 -2.197,2.197 v 0 c -0.728,0 -1.373,-0.354 -1.773,-0.899 M 12.45,6.75 h 0.204999 C 13.81563,6.75 14.75,7.68437 14.75,8.845 v 0.81 c 0,1.16063 -0.93437,2.095 -2.095001,2.095 H 12.45 c -1.16063,0 -2.095,-0.93437 -2.095,-2.095 v -0.81 c 0,-1.16063 0.93437,-2.095 2.095,-2.095 z M 7.25,9.5 c 0,1.518 -1.232,2.75 -2.75,2.75 H 4.3386 C 3.5993,12.25 3,11.6507 3,10.9114 v 0 C 3,10.2678 3.458,9.7153 4.0904,9.596 L 6.3677,9.1961 C 6.9062,9.0945 7.2576,8.581 7.1689,8.0499 M 3.234,7.234 C 3.72,6.711 4.412,6.5 5.115,6.5 v 0 c 1.179,0 2.134,0.956 2.134,2.134 v 3.616" />
|
||||
</g>
|
||||
</svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/text-size.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/text-size.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="line2"
|
||||
d="M 1.38,11.75 H 5.7160001 M 6.346,13.25 3.616,6.75 H 3.48 l -2.73,6.5 m 9.49,-2.5 h 6.040001 M 17.25,13.25 13.565,3.75 h -0.61 l -3.685,9.5" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="m 15.75,6.75 v 6.303 c 0,1.214 -0.984,2.197 -2.197,2.197 v 0 c -0.728,0 -1.373,-0.354 -1.773,-0.899 M 13.45,6.75 h 0.204999 C 14.81563,6.75 15.75,7.68437 15.75,8.845 v 0.81 c 0,1.16063 -0.93437,2.095 -2.095001,2.095 H 13.45 c -1.16063,0 -2.095,-0.93437 -2.095,-2.095 v -0.81 c 0,-1.16063 0.93437,-2.095 2.095,-2.095 z M 2.5899999,10.25 H 8.2279997 M 9.068,12.25 5.498,3.75 H 5.32 l -3.57,8.5" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="M 16.53,6.048 C 16.201,5.615 15.361,4.75 13.905,4.75 c -2.136,0 -3.655,1.522 -3.655,4.304 0,2.444 1.492,4.196 3.439,4.196 1.125,0 3.266,-0.173 3.266,-3.958 H 13.797 M 1.84,11.25 H 7.4780002 M 8.318,13.25 4.748,4.75 H 4.57 L 1,13.25" />
|
||||
</g>
|
||||
</svg>
|
||||
36
src/browser/themes/shared/zen-icons/nucleo/wand-sparkle.svg
Normal file
36
src/browser/themes/shared/zen-icons/nucleo/wand-sparkle.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs5" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2" />
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g5">
|
||||
<path
|
||||
id="path5"
|
||||
d="M 9.25,2.5 C 9.664,2.5 10,2.16 10,1.75 10,1.34 9.664,1 9.25,1 8.836,1 8.5,1.34 8.5,1.75 8.5,2.16 8.836,2.5 9.25,2.5 Z m 7.408,9.49 -1.263,-0.42 -0.421,-1.2599 c -0.137,-0.41004 -0.812,-0.41004 -0.949,0 l -0.421,1.2599 -1.263,0.42 c -0.204,0.0701 -0.342,0.26 -0.342,0.47 0,0.2201 0.138,0.4099 0.342,0.4799 l 1.263,0.4201 0.421,1.26 c 0.068,0.21 0.26,0.34 0.475,0.34 0.215,0 0.406,-0.13 0.475,-0.34 l 0.421,-1.26 1.263,-0.4201 c 0.204,-0.0699 0.342,-0.2598 0.342,-0.4799 0,-0.21 -0.139,-0.3999 -0.343,-0.47 z M 7.24297,3.48999 6.29697,3.18006 5.98097,2.22998 c -0.102,-0.3099 -0.609,-0.3099 -0.711,0 L 4.95396,3.18006 4.00797,3.48999 c -0.153,0.05 -0.25699,0.19999 -0.25699,0.35999 0,0.16 0.10399,0.29997 0.25699,0.34997 L 4.95396,4.52014 5.26997,5.4701 c 0.051,0.15 0.194,0.25 0.355,0.25 0.161,0 0.305,-0.1 0.355,-0.25 l 0.316,-0.94996 0.946,-0.32019 c 0.153,-0.0499 0.25699,-0.18987 0.25699,-0.34997 0,-0.1601 -0.10299,-0.30989 -0.25599,-0.35999 z m 3.14403,1.87 2.25,2.25 M 12.5717,2.92528 2.91893,12.583 c -0.3899,0.39 -0.3903,1.0221 -0.0011,1.4127 l 1.08521,1.0892 c 0.391,0.39 1.02399,0.39 1.41499,0 L 15.0701,5.42687 c 0.3898,-0.3901 0.3903,-1.02212 0.0011,-1.41272 L 13.9874,2.92638 C 13.597,2.53458 12.9627,2.53408 12.5717,2.92528 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
31
src/docshell/base/BrowsingContext-h.patch
Normal file
31
src/docshell/base/BrowsingContext-h.patch
Normal file
@@ -0,0 +1,31 @@
|
||||
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
|
||||
index dc64d5c13628cb7393705ebf96110705b045471f..497c2e646e2bde4d7732fa28efe7496264d58fa0 100644
|
||||
--- a/docshell/base/BrowsingContext.h
|
||||
+++ b/docshell/base/BrowsingContext.h
|
||||
@@ -263,6 +263,8 @@ struct EmbedderColorSchemes {
|
||||
FIELD(HistoryEntryCount, uint32_t) \
|
||||
FIELD(HasRestoreData, bool) \
|
||||
FIELD(SessionStoreEpoch, uint32_t) \
|
||||
+ FIELD(ZenBoostsData, nscolor) \
|
||||
+ FIELD(IsZenBoostsInverted, bool) \
|
||||
/* Whether we can execute scripts in this BrowsingContext. Has no effect \
|
||||
* unless scripts are also allowed in the parent WindowContext. */ \
|
||||
FIELD(AllowJavascript, bool) \
|
||||
@@ -674,6 +676,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
|
||||
bool FullscreenAllowed() const;
|
||||
|
||||
+ auto ZenBoostsData() const { return GetZenBoostsData(); }
|
||||
+ auto IsZenBoostsInverted() const { return GetIsZenBoostsInverted(); }
|
||||
float FullZoom() const { return GetFullZoom(); }
|
||||
float TextZoom() const { return GetTextZoom(); }
|
||||
|
||||
@@ -1274,6 +1278,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
}
|
||||
|
||||
void DidSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aOldValue);
|
||||
+ void DidSet(FieldIndex<IDX_ZenBoostsData>, nscolor aOldValue);
|
||||
+ void DidSet(FieldIndex<IDX_IsZenBoostsInverted>, bool aOldValue);
|
||||
|
||||
using CanSetResult = syncedcontext::CanSetResult;
|
||||
|
||||
14
src/dom/chrome-webidl/BrowsingContext-webidl.patch
Normal file
14
src/dom/chrome-webidl/BrowsingContext-webidl.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
index 3bb17157c1dba1ca75ad9cfee2c21fcafb2dd0ee..a43f8aa1e4c808418f1cc7ccc69ced12c9ebfef0 100644
|
||||
--- a/dom/chrome-webidl/BrowsingContext.webidl
|
||||
+++ b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
@@ -169,6 +169,9 @@ interface BrowsingContext {
|
||||
|
||||
[SetterThrows] attribute float textZoom;
|
||||
|
||||
+ [SetterThrows] attribute long zenBoostsData;
|
||||
+ [SetterThrows] attribute boolean isZenBoostsInverted;
|
||||
+
|
||||
// Override the dots-per-CSS-pixel scaling factor in this BrowsingContext
|
||||
// and all of its descendants. May only be set on the top BC, and should
|
||||
// only be set from the parent process.
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
21
src/gfx/layers/AnimationInfo-cpp.patch
Normal file
21
src/gfx/layers/AnimationInfo-cpp.patch
Normal file
@@ -0,0 +1,21 @@
|
||||
diff --git a/gfx/layers/AnimationInfo.cpp b/gfx/layers/AnimationInfo.cpp
|
||||
index dd67af463d383dd98da80ce44f41b395e4a9a7cf..9da3849630a80fea2b254659a218e5ed38b44bc0 100644
|
||||
--- a/gfx/layers/AnimationInfo.cpp
|
||||
+++ b/gfx/layers/AnimationInfo.cpp
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "mozilla/MotionPathUtils.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/ScrollContainerFrame.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
@@ -344,7 +345,7 @@ static void SetAnimatable(NonCustomCSSPropertyId aProperty,
|
||||
// resolve currentColor at this moment.
|
||||
nscolor foreground =
|
||||
aFrame->Style()->GetVisitedDependentColor(&nsStyleText::mColor);
|
||||
- aAnimatable = aAnimationValue.GetColor(foreground);
|
||||
+ aAnimatable = zen::nsZenBoostsBackend::FilterColorFromPresContext(aAnimationValue.GetColor(foreground), aFrame->PresContext());
|
||||
break;
|
||||
}
|
||||
case eCSSProperty_opacity:
|
||||
21
src/layout/base/PresShell-cpp.patch
Normal file
21
src/layout/base/PresShell-cpp.patch
Normal file
@@ -0,0 +1,21 @@
|
||||
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
|
||||
index dd4df91b14137c2aa1ffd978faf1f4c91e234f8a..99976e9416d8a5b814bbb3a5421899f450ceeee5 100644
|
||||
--- a/layout/base/PresShell.cpp
|
||||
+++ b/layout/base/PresShell.cpp
|
||||
@@ -136,6 +136,7 @@
|
||||
#include "mozilla/layers/WebRenderLayerManager.h"
|
||||
#include "mozilla/layers/WebRenderUserData.h"
|
||||
#include "mozilla/layout/ScrollAnchorContainer.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
#include "nsAnimationManager.h"
|
||||
#include "nsAutoLayoutPhase.h"
|
||||
#include "nsCOMArray.h"
|
||||
@@ -5546,7 +5547,7 @@ nscolor PresShell::GetDefaultBackgroundColorToDraw() const {
|
||||
if (!mPresContext) {
|
||||
return NS_RGB(255, 255, 255);
|
||||
}
|
||||
- return mPresContext->DefaultBackgroundColor();
|
||||
+ return zen::nsZenBoostsBackend::FilterColorFromPresContext(mPresContext->DefaultBackgroundColor(), mPresContext);
|
||||
}
|
||||
|
||||
void PresShell::UpdateCanvasBackground() {
|
||||
33
src/layout/generic/ViewportFrame-cpp.patch
Normal file
33
src/layout/generic/ViewportFrame-cpp.patch
Normal file
@@ -0,0 +1,33 @@
|
||||
diff --git a/layout/generic/ViewportFrame.cpp b/layout/generic/ViewportFrame.cpp
|
||||
index ea018fff9f549cd8e07b20f4c8073391a6fdf40d..acb9428cf2276ed04064484600024d9e2c7e2bb7 100644
|
||||
--- a/layout/generic/ViewportFrame.cpp
|
||||
+++ b/layout/generic/ViewportFrame.cpp
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsPlaceholderFrame.h"
|
||||
#include "nsSubDocumentFrame.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
@@ -299,15 +300,20 @@ ViewportFrame::BuildDisplayListForViewTransitionsAndNACTopLayer(
|
||||
}
|
||||
}
|
||||
|
||||
+ bool isAnonContent = false;
|
||||
if (dom::Element* container = doc->GetCustomContentContainer()) {
|
||||
if (nsIFrame* frame = container->GetPrimaryFrame()) {
|
||||
MOZ_ASSERT(frame->StyleDisplay()->mTopLayer != StyleTopLayer::None,
|
||||
"ua.css should ensure this");
|
||||
MOZ_ASSERT(frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
|
||||
+ isAnonContent = frame->ContentIsRootOfNativeAnonymousSubtree();
|
||||
BuildDisplayListForTopLayerFrame(aBuilder, frame, &topLayerList);
|
||||
}
|
||||
}
|
||||
|
||||
+ if (auto zenBackend = zen::nsZenBoostsBackend::GetInstance()) {
|
||||
+ zenBackend->mCurrentFrameIsAnonymousContent = isAnonContent;
|
||||
+ }
|
||||
return MaybeWrapTopLayerList(
|
||||
aBuilder, uint16_t(TopLayerIndex::ViewTransitionsAndAnonymousContent),
|
||||
topLayerList);
|
||||
23
src/layout/painting/nsDisplayList-cpp.patch
Normal file
23
src/layout/painting/nsDisplayList-cpp.patch
Normal file
@@ -0,0 +1,23 @@
|
||||
diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp
|
||||
index c045de789d924d0fd652b81f4fef37cb0bc19112..ac3d3904626132257a1ffce4a8d7bd6b8c12728e 100644
|
||||
--- a/layout/painting/nsDisplayList.cpp
|
||||
+++ b/layout/painting/nsDisplayList.cpp
|
||||
@@ -81,6 +81,7 @@
|
||||
#include "mozilla/layers/WebRenderLayerManager.h"
|
||||
#include "mozilla/layers/WebRenderMessages.h"
|
||||
#include "mozilla/layers/WebRenderScrollData.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
#include "nsCSSProps.h"
|
||||
#include "nsCSSRendering.h"
|
||||
#include "nsCSSRenderingGradients.h"
|
||||
@@ -1252,6 +1253,10 @@ void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame,
|
||||
docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
|
||||
}
|
||||
|
||||
+ if (auto zenBackend = zen::nsZenBoostsBackend::GetInstance(); zenBackend && !mIsInChromePresContext) {
|
||||
+ zenBackend->onPresShellEntered(pc->Document());
|
||||
+ }
|
||||
+
|
||||
state->mTouchEventPrefEnabledDoc = dom::TouchEvent::PrefEnabled(docShell);
|
||||
|
||||
if (auto* vt = pc->Document()->GetActiveViewTransition()) {
|
||||
44
src/layout/style/StyleColor-cpp.patch
Normal file
44
src/layout/style/StyleColor-cpp.patch
Normal file
@@ -0,0 +1,44 @@
|
||||
diff --git a/layout/style/StyleColor.cpp b/layout/style/StyleColor.cpp
|
||||
index 95c7ae6abea5032bef0466e8d59d212374d7a4d0..8dbfbb846b786d51af288989163aacfae12e787c 100644
|
||||
--- a/layout/style/StyleColor.cpp
|
||||
+++ b/layout/style/StyleColor.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsStyleStruct.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -21,6 +22,8 @@ bool StyleColor::MaybeTransparent() const {
|
||||
template <>
|
||||
StyleAbsoluteColor StyleColor::ResolveColor(
|
||||
const StyleAbsoluteColor& aForegroundColor) const {
|
||||
+ auto ResolveColorInner = [this,
|
||||
+ &aForegroundColor]() -> StyleAbsoluteColor {
|
||||
if (IsAbsolute()) {
|
||||
return AsAbsolute();
|
||||
}
|
||||
@@ -30,6 +33,8 @@ StyleAbsoluteColor StyleColor::ResolveColor(
|
||||
}
|
||||
|
||||
return Servo_ResolveColor(this, &aForegroundColor);
|
||||
+ };
|
||||
+ return zen::nsZenBoostsBackend::ResolveStyleColor(ResolveColorInner());
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -68,10 +73,11 @@ nscolor StyleAbsoluteColor::ToColor() const {
|
||||
auto green = std::clamp(srgb.components._1, 0.0f, 1.0f);
|
||||
auto blue = std::clamp(srgb.components._2, 0.0f, 1.0f);
|
||||
|
||||
- return NS_RGBA(nsStyleUtil::FloatToColorComponent(red),
|
||||
+ return zen::nsZenBoostsBackend::ResolveStyleColor(
|
||||
+ NS_RGBA(nsStyleUtil::FloatToColorComponent(red),
|
||||
nsStyleUtil::FloatToColorComponent(green),
|
||||
nsStyleUtil::FloatToColorComponent(blue),
|
||||
- nsStyleUtil::FloatToColorComponent(srgb.alpha));
|
||||
+ nsStyleUtil::FloatToColorComponent(srgb.alpha)));
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/toolkit/components/prompts/content/commonDialog.css b/toolkit/components/prompts/content/commonDialog.css
|
||||
index d811fb62d502cf6fc0bf8163f11e1d264dee9e82..9fd96c560342b3417299ee3c6afda30ddcec76c0 100644
|
||||
index d811fb62d502cf6fc0bf8163f11e1d264dee9e82..79e44af5d6d37bdb756bc1737d279e8769abb50e 100644
|
||||
--- a/toolkit/components/prompts/content/commonDialog.css
|
||||
+++ b/toolkit/components/prompts/content/commonDialog.css
|
||||
@@ -3,7 +3,8 @@
|
||||
@@ -12,7 +12,15 @@ index d811fb62d502cf6fc0bf8163f11e1d264dee9e82..9fd96c560342b3417299ee3c6afda30d
|
||||
}
|
||||
|
||||
dialog[insecureauth] {
|
||||
@@ -91,11 +92,12 @@ dialog[insecureauth] {
|
||||
@@ -49,7 +50,6 @@ dialog[insecureauth] {
|
||||
.sizeDetermined #infoRow,
|
||||
.sizeDetermined #infoContainer {
|
||||
/* Allow stuff to shrink */
|
||||
- min-height: 0;
|
||||
}
|
||||
|
||||
.sizeDetermined #infoRow {
|
||||
@@ -91,11 +91,12 @@ dialog[insecureauth] {
|
||||
--grid-padding: 16px;
|
||||
/* All the inner items should have 4px inline margin, leading to 1.16em spacing
|
||||
* between the dialog and its contents, and 8px horizontal spacing between items. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
27
src/widget/cocoa/nsCocoaWindow-mm.patch
Normal file
27
src/widget/cocoa/nsCocoaWindow-mm.patch
Normal file
@@ -0,0 +1,27 @@
|
||||
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
|
||||
index 515177a3a97094142593a98fb1b3023acf1ccb87..fb6f932547f9523a240f95915b7440dcdfa16975 100644
|
||||
--- a/widget/cocoa/nsCocoaWindow.mm
|
||||
+++ b/widget/cocoa/nsCocoaWindow.mm
|
||||
@@ -5240,7 +5240,7 @@ static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
|
||||
// calls to ...orderFront: in TRY blocks. See bmo bug 470864.
|
||||
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
|
||||
mWindow.contentView.needsDisplay = YES;
|
||||
- if (!nativeParentWindow || mPopupLevel != PopupLevel::Parent) {
|
||||
+ if (!mZenShowLocked && (!nativeParentWindow || mPopupLevel != PopupLevel::Parent)) {
|
||||
[mWindow orderFront:nil];
|
||||
}
|
||||
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
||||
@@ -5287,7 +5287,12 @@ static unsigned int WindowMaskForBorderStyle(BorderStyle aBorderStyle) {
|
||||
// We don't want most alwaysontop / alert windows to pull focus when
|
||||
// they're opened, as these tend to be for peripheral indicators and
|
||||
// displays.
|
||||
- if ((mAlwaysOnTop && mPiPType != PiPType::DocumentPiP) || mIsAlert) {
|
||||
+ if (mZenShowLocked) {
|
||||
+ // Zen: window-control service has this widget locked-hidden;
|
||||
+ // skip the native order-front but let the rest of Show()'s
|
||||
+ // bookkeeping run normally.
|
||||
+ } else if ((mAlwaysOnTop && mPiPType != PiPType::DocumentPiP) ||
|
||||
+ mIsAlert) {
|
||||
[mWindow orderFront:nil];
|
||||
} else {
|
||||
[mWindow makeKeyAndOrderFront:nil];
|
||||
18
src/widget/gtk/nsWindow-cpp.patch
Normal file
18
src/widget/gtk/nsWindow-cpp.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
|
||||
index 89950bd72c77ed961e59faa2fadb8f21a78fd23a..8c74bfa08af3c16cc05e11ea34902cbcdde3c64a 100644
|
||||
--- a/widget/gtk/nsWindow.cpp
|
||||
+++ b/widget/gtk/nsWindow.cpp
|
||||
@@ -1076,7 +1076,12 @@ void nsWindow::Show(bool aState) {
|
||||
}
|
||||
#endif
|
||||
|
||||
- NativeShow(aState);
|
||||
+ // Zen: skip the actual GTK show when window-control has the widget
|
||||
+ // locked-hidden; mIsShown is already set above so Mozilla's
|
||||
+ // bookkeeping treats the widget as shown.
|
||||
+ if (!(aState && mZenShowLocked)) {
|
||||
+ NativeShow(aState);
|
||||
+ }
|
||||
RefreshWindowClass();
|
||||
}
|
||||
|
||||
28
src/widget/nsIWidget-h.patch
Normal file
28
src/widget/nsIWidget-h.patch
Normal file
@@ -0,0 +1,28 @@
|
||||
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
|
||||
index c22e055b9254ed1c8943c232a8339c574563dffc..0a0cb7cad4e878f93c03078564dac051dde1d014 100644
|
||||
--- a/widget/nsIWidget.h
|
||||
+++ b/widget/nsIWidget.h
|
||||
@@ -698,6 +698,14 @@ class nsIWidget : public nsSupportsWeakReference {
|
||||
*/
|
||||
virtual void Show(bool aState) = 0;
|
||||
|
||||
+ /**
|
||||
+ * Zen: when set to true, Show(true) calls on this widget become
|
||||
+ * no-ops until SetZenShowLocked(false) is called. Used to keep a
|
||||
+ * window invisible while it's being set up.
|
||||
+ */
|
||||
+ void SetZenShowLocked(bool aLocked) { mZenShowLocked = aLocked; }
|
||||
+ bool IsZenShowLocked() const { return mZenShowLocked; }
|
||||
+
|
||||
/**
|
||||
* Whether or not a widget must be recreated after being hidden to show
|
||||
* again properly.
|
||||
@@ -2419,6 +2427,8 @@ class nsIWidget : public nsSupportsWeakReference {
|
||||
mozilla::Maybe<FullscreenSavedState> mSavedBounds;
|
||||
|
||||
bool mUpdateCursor;
|
||||
+ // Zen: when true, Show(true) is a no-op. See SetZenShowLocked().
|
||||
+ bool mZenShowLocked = false;
|
||||
bool mIMEHasFocus;
|
||||
bool mIMEHasQuit;
|
||||
// if the window is fully occluded (rendering may be paused in response)
|
||||
59
src/widget/windows/nsWindow-cpp.patch
Normal file
59
src/widget/windows/nsWindow-cpp.patch
Normal file
@@ -0,0 +1,59 @@
|
||||
diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
|
||||
index baed7d09e31291b26a1d0a1ddfd63b57f0ce67d0..9daa1140e9538427d002c6f8ff99cd68265249cc 100644
|
||||
--- a/widget/windows/nsWindow.cpp
|
||||
+++ b/widget/windows/nsWindow.cpp
|
||||
@@ -1761,28 +1761,33 @@ void nsWindow::Show(bool aState) {
|
||||
// cursor.
|
||||
SetCursor(Cursor{eCursor_standard});
|
||||
|
||||
- switch (mFrameState->GetSizeMode()) {
|
||||
- case nsSizeMode_Fullscreen:
|
||||
- ::ShowWindow(mWnd, SW_SHOW);
|
||||
- break;
|
||||
- case nsSizeMode_Maximized:
|
||||
- ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
|
||||
- break;
|
||||
- case nsSizeMode_Minimized:
|
||||
- ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
|
||||
- break;
|
||||
- default:
|
||||
- if (CanTakeFocus() &&
|
||||
- (!mAlwaysOnTop || mPiPType == PiPType::DocumentPiP)) {
|
||||
- ::ShowWindow(mWnd, SW_SHOWNORMAL);
|
||||
- } else {
|
||||
- ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
|
||||
- // Don't flicker the window if we're restoring session
|
||||
- if (!sIsRestoringSession) {
|
||||
- (void)GetAttention(2);
|
||||
+ // Zen: skip the actual ShowWindow() call when window-control
|
||||
+ // has the widget locked-hidden; mIsVisible is already set above
|
||||
+ // so Mozilla's bookkeeping treats the widget as shown.
|
||||
+ if (!mZenShowLocked) {
|
||||
+ switch (mFrameState->GetSizeMode()) {
|
||||
+ case nsSizeMode_Fullscreen:
|
||||
+ ::ShowWindow(mWnd, SW_SHOW);
|
||||
+ break;
|
||||
+ case nsSizeMode_Maximized:
|
||||
+ ::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
|
||||
+ break;
|
||||
+ case nsSizeMode_Minimized:
|
||||
+ ::ShowWindow(mWnd, SW_SHOWMINIMIZED);
|
||||
+ break;
|
||||
+ default:
|
||||
+ if (CanTakeFocus() &&
|
||||
+ (!mAlwaysOnTop || mPiPType == PiPType::DocumentPiP)) {
|
||||
+ ::ShowWindow(mWnd, SW_SHOWNORMAL);
|
||||
+ } else {
|
||||
+ ::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
|
||||
+ // Don't flicker the window if we're restoring session
|
||||
+ if (!sIsRestoringSession) {
|
||||
+ (void)GetAttention(2);
|
||||
+ }
|
||||
}
|
||||
- }
|
||||
- break;
|
||||
+ break;
|
||||
+ }
|
||||
}
|
||||
|
||||
if (!mHasBeenShown) {
|
||||
@@ -16,3 +16,5 @@ category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 applicatio
|
||||
#include common/Components.manifest
|
||||
#include sessionstore/SessionComponents.manifest
|
||||
#include live-folders/LiveFoldersComponents.manifest
|
||||
#include kbs/KbsComponents.manifest
|
||||
#include little-window/LittleWindowComponents.manifest
|
||||
|
||||
124
src/zen/boosts/ZenBoostStyles.sys.mjs
Normal file
124
src/zen/boosts/ZenBoostStyles.sys.mjs
Normal file
@@ -0,0 +1,124 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const lazy = XPCOMUtils.declareLazy({
|
||||
styleSheetService: {
|
||||
service: "@mozilla.org/content/style-sheet-service;1",
|
||||
iid: Ci.nsIStyleSheetService,
|
||||
},
|
||||
});
|
||||
|
||||
const AGENT_SHEET = Ci.nsIStyleSheetService.AGENT_SHEET;
|
||||
|
||||
export class nsZenBoostStyles {
|
||||
#stylesCache = new Map();
|
||||
|
||||
/**
|
||||
* Retrieves the CSS style string for a given boost configuration.
|
||||
* Caches styles to optimize performance.
|
||||
*
|
||||
* @param {object} boostData - The boost configuration data.
|
||||
* @param {string} domain - The domain associated with the boost.
|
||||
* @returns {string} The generated CSS style string.
|
||||
*/
|
||||
getStyleForBoost(boostData, domain) {
|
||||
if (this.#stylesCache.has(domain)) {
|
||||
return this.#stylesCache.get(domain);
|
||||
}
|
||||
|
||||
const rawStyle = this.#generateStyleString(boostData);
|
||||
if (!rawStyle || rawStyle.trim() === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styleUri = this.#convertStyleToDataUri(rawStyle);
|
||||
this.#cacheStyle(styleUri, domain);
|
||||
return this.getStyleForBoost(boostData, domain);
|
||||
}
|
||||
|
||||
invalidateStyleForDomain(domain) {
|
||||
if (this.#stylesCache.has(domain)) {
|
||||
const { uri } = this.#stylesCache.get(domain);
|
||||
lazy.styleSheetService.unregisterSheet(uri, AGENT_SHEET);
|
||||
this.#stylesCache.delete(domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a CSS style string based on the boost configuration.
|
||||
*
|
||||
* @param {object} boostData - The boost configuration data.
|
||||
* @returns {string} The generated CSS style string.
|
||||
* @private
|
||||
*/
|
||||
#generateStyleString(boostData) {
|
||||
let style = ``;
|
||||
|
||||
const fontFamily =
|
||||
boostData.fontFamily != ""
|
||||
? `font-family: ${boostData.fontFamily} !important;`
|
||||
: ``;
|
||||
const fontCase =
|
||||
boostData.textCaseOverride != "none"
|
||||
? `text-transform: ${boostData.textCaseOverride} !important;`
|
||||
: ``;
|
||||
|
||||
let zapBlocks = "";
|
||||
if (boostData.zapSelectors) {
|
||||
for (const selector of boostData.zapSelectors) {
|
||||
zapBlocks += `${selector}:not([zen-zap-unhide]){ display: none !important; }\n`;
|
||||
}
|
||||
|
||||
if (zapBlocks != "") {
|
||||
style += `/* Zen-Zaps */\n`;
|
||||
style += `${zapBlocks}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
if (fontCase != "" || fontFamily != "") {
|
||||
style += `/* Text Format */\n`;
|
||||
style += `body * {\n`;
|
||||
style += `${fontFamily}\n`;
|
||||
style += `${fontCase}\n`;
|
||||
style += `}\n`;
|
||||
}
|
||||
|
||||
if ((boostData.customCSS || "").trim() != "") {
|
||||
style += `/* USER CSS */\n`;
|
||||
style += `${boostData.customCSS || ""}\n`;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a raw CSS style string into a data URI.
|
||||
*
|
||||
* @param {string} rawStyle - The raw CSS style string.
|
||||
* @returns {string} The data URI representing the CSS style.
|
||||
* @private
|
||||
*/
|
||||
#convertStyleToDataUri(rawStyle) {
|
||||
const encodedStyle = encodeURIComponent(rawStyle);
|
||||
return Services.io.newURI(`data:text/css;charset=utf-8,${encodedStyle}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetches the style from the data URI and caches it.
|
||||
*
|
||||
* @param {string} styleUri - The data URI of the CSS style.
|
||||
* @param {string} domain - The domain associated with the boost.
|
||||
* @private
|
||||
*/
|
||||
#cacheStyle(styleUri, domain) {
|
||||
this.#stylesCache.set(domain, {
|
||||
uuid: Services.uuid.generateUUID().toString(),
|
||||
uri: styleUri,
|
||||
});
|
||||
}
|
||||
}
|
||||
1327
src/zen/boosts/ZenBoostsEditor.mjs
Normal file
1327
src/zen/boosts/ZenBoostsEditor.mjs
Normal file
File diff suppressed because it is too large
Load Diff
773
src/zen/boosts/ZenBoostsManager.sys.mjs
Normal file
773
src/zen/boosts/ZenBoostsManager.sys.mjs
Normal file
@@ -0,0 +1,773 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs";
|
||||
import { nsZenBoostStyles } from "resource:///modules/zen/boosts/ZenBoostStyles.sys.mjs";
|
||||
|
||||
class nsZenBoostsManager {
|
||||
registeredDomains = new Map(); // <domain, { boosts: <id, boostEntry>, activeBoostID: null }>
|
||||
#stylesManager = new nsZenBoostStyles();
|
||||
|
||||
#saveFilename = "zen-boosts.jsonlz4";
|
||||
|
||||
#file = null;
|
||||
|
||||
constructor() {
|
||||
this.#readBoostsFromStore(this.notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object} New domain entry with empty boost map and active boost id
|
||||
*/
|
||||
#createDomainEntry() {
|
||||
return {
|
||||
boostEntries: new Map(), // <id, boostEntry>
|
||||
activeBoostId: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Will get or create a domain entry for the given domain
|
||||
*
|
||||
* @param {string} domain - The domain of which the data will be fetched
|
||||
* @returns {object} The domain entry
|
||||
*/
|
||||
#getOrCreateDomainEntry(domain) {
|
||||
if (!this.registeredDomains.has(domain)) {
|
||||
this.registeredDomains.set(domain, this.#createDomainEntry());
|
||||
}
|
||||
return this.registeredDomains.get(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will get a domain entry for the given domain
|
||||
*
|
||||
* @param {string} domain - The domain of which the data will be fetched
|
||||
* @returns {object|null} The domain entry
|
||||
*/
|
||||
#getDomainEntry(domain) {
|
||||
if (!this.registeredDomains.has(domain)) {
|
||||
return null;
|
||||
}
|
||||
return this.registeredDomains.get(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will delete the domain entry for a domain
|
||||
*
|
||||
* @param {string} domain - The given domain
|
||||
*/
|
||||
#deleteDomainEntry(domain) {
|
||||
if (this.registeredDomains.has(domain)) {
|
||||
this.registeredDomains.delete(domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the active boost id for a given domain
|
||||
*
|
||||
* @param {string} domain - The target domain
|
||||
* @returns {string | null} Will return the active boost id or null
|
||||
*/
|
||||
getActiveBoostId(domain) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
if (domainEntry) {
|
||||
return domainEntry.activeBoostId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a boost for the specified domain and persists the change to disk.
|
||||
*
|
||||
* @param {object} boost - The targeted boost.
|
||||
*/
|
||||
deleteBoost(boost) {
|
||||
const { domain, id } = boost;
|
||||
|
||||
if (this.registeredDomains.has(domain)) {
|
||||
let domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
domainEntry.boostEntries.delete(id);
|
||||
}
|
||||
if (domainEntry.activeBoostId == id) {
|
||||
domainEntry.activeBoostId = null;
|
||||
}
|
||||
|
||||
if (domainEntry.boostEntries.size === 0) {
|
||||
this.#deleteDomainEntry(domain);
|
||||
}
|
||||
}
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify(true);
|
||||
|
||||
this.#writeToDisk(this.registeredDomains);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object} Returns a new empty boost entry
|
||||
*/
|
||||
getEmptyBoostEntry() {
|
||||
return {
|
||||
boostData: {
|
||||
boostName: "My Boost",
|
||||
|
||||
dotAngleDeg: 0,
|
||||
dotPos: { x: null, y: null },
|
||||
dotDistance: 0,
|
||||
|
||||
brightness: 0.5,
|
||||
saturation: 0.5,
|
||||
contrast: 0.75,
|
||||
|
||||
fontFamily: "",
|
||||
|
||||
enableColorBoost: false,
|
||||
smartInvert: false,
|
||||
|
||||
// Choses theme based on Zen's workspace theme
|
||||
autoTheme: false,
|
||||
|
||||
textCaseOverride: "none",
|
||||
|
||||
zapSelectors: [],
|
||||
customCSS: "",
|
||||
|
||||
changeWasMade: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create a new boost
|
||||
*
|
||||
* @param {string} domain - The domain which will be affected by the boost
|
||||
* @returns {object|null} The created boost with { id, domain, boostEntry: { boostData } } or null
|
||||
*/
|
||||
createNewBoost(domain) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
const boostEntry = this.getEmptyBoostEntry(domain);
|
||||
|
||||
const domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
domainEntry.boostEntries.set(id, boostEntry);
|
||||
|
||||
const boost = { id, domain, boostEntry };
|
||||
return boost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the boost configuration for the specified domain from storage.
|
||||
*
|
||||
* @param {string} domain - The domain for which to load the boost
|
||||
* @returns {object[] | null} All boosts for the domain or null
|
||||
*/
|
||||
loadBoostsFromStore(domain) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
}
|
||||
|
||||
const boosts = [];
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
domainEntry.boostEntries.forEach((value, key) => {
|
||||
const boost = { id: key, domain, boostEntry: value };
|
||||
boosts.push(boost);
|
||||
});
|
||||
return boosts;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the boost for the specified domain and id from storage.
|
||||
* If no boost is present, a new one will be created.
|
||||
*
|
||||
* @param {string} domain - The domain of the boost
|
||||
* @param {string} id - The id of the boost
|
||||
* @returns {object} Returns the boost with { id, domain, boostEntry: { boostData } }
|
||||
*/
|
||||
loadBoostFromStore(domain, id) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
}
|
||||
if (!id) {
|
||||
console.error("[ZenBoostsManager] ID expected but got null.");
|
||||
}
|
||||
|
||||
const domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
const boostEntry = domainEntry.boostEntries.get(id);
|
||||
return { id, domain, boostEntry };
|
||||
}
|
||||
const boost = this.createNewBoost(domain);
|
||||
return boost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the active boost for the specified domain from storage.
|
||||
*
|
||||
* @param {string} domain - The domain of the boost
|
||||
* @returns {object | null} Returns the boost with { id, domain, boostEntry: { boostData } } or null
|
||||
*/
|
||||
loadActiveBoostFromStore(domain) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
}
|
||||
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
if (domainEntry.boostEntries.size === 0) {
|
||||
return this.createNewBoost(domain);
|
||||
}
|
||||
|
||||
if (domainEntry.boostEntries.has(domainEntry.activeBoostId)) {
|
||||
const boostEntry = domainEntry.boostEntries.get(
|
||||
domainEntry.activeBoostId
|
||||
);
|
||||
return { id: domainEntry.activeBoostId, domain, boostEntry };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the zap selector to the selectors list and updates the website.
|
||||
*
|
||||
* @param {string} selector - Selector which will hide the elements
|
||||
* @param {string} domain - Domain of the target boost
|
||||
*/
|
||||
addZapSelectorToActive(selector, domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
console.error("[ZenBoostsManager] Active boost is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
|
||||
if (!boostData.zapSelectors) {
|
||||
boostData.zapSelectors = [];
|
||||
}
|
||||
if (!boostData.zapSelectors.includes(selector)) {
|
||||
boostData.zapSelectors.push(selector);
|
||||
}
|
||||
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the zap selector to the selectors list and updates the website.
|
||||
*
|
||||
* @param {string} selector - Selector which will no longer hide the elements
|
||||
* @param {string} domain - Domain of the target boost
|
||||
*/
|
||||
removeZapSelectorToActive(selector, domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
console.error("[ZenBoostsManager] Active boost is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
|
||||
if (boostData.zapSelectors && boostData.zapSelectors.includes(selector)) {
|
||||
const i = boostData.zapSelectors.indexOf(selector);
|
||||
if (i !== -1) {
|
||||
boostData.zapSelectors.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the boost at the domain with the id active
|
||||
*
|
||||
* @param {string} domain The target domain
|
||||
* @param {string} id The target boost id
|
||||
*/
|
||||
makeBoostActiveForDomain(domain, id) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
domainEntry.activeBoostId = id;
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, "zen-boosts-active-change", { id });
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the boost activeness at the domain with the id active
|
||||
*
|
||||
* @param {string} domain The target domain
|
||||
* @param {string} id The target boost id
|
||||
*/
|
||||
toggleBoostActiveForDomain(domain, id) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
if (domainEntry.activeBoostId === id) {
|
||||
domainEntry.activeBoostId = null;
|
||||
Services.obs.notifyObservers(null, "zen-boosts-active-change", {
|
||||
id: null,
|
||||
});
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify(true);
|
||||
} else {
|
||||
domainEntry.activeBoostId = id;
|
||||
Services.obs.notifyObservers(null, "zen-boosts-active-change", {
|
||||
id,
|
||||
});
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all zap selectors from a boost
|
||||
*
|
||||
* @param {string} domain - Target boost domain
|
||||
*/
|
||||
clearZapSelectorsForActive(domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
console.error("[ZenBoostsManager] Active boost is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
boostData.zapSelectors = [];
|
||||
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the boost for a domain in memory and notifies observers of the change.
|
||||
*
|
||||
* @param {object} boost - The target boost
|
||||
*/
|
||||
updateBoost(boost) {
|
||||
const { domain, id, boostEntry } = boost;
|
||||
|
||||
const domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
domainEntry.boostEntries.set(id, boostEntry);
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all observers that boost data has been updated.
|
||||
* This triggers a 'zen-boosts-update' notification event.
|
||||
*
|
||||
* @param {boolean} unloadStyles - Whether to unload styles during the update.
|
||||
*/
|
||||
notify(unloadStyles = false) {
|
||||
Services.obs.notifyObservers(null, "zen-boosts-update", { unloadStyles });
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a boost configuration to persistent storage and notifies observers.
|
||||
*
|
||||
* @param {object | null} boost - The boost data object to save. If null, only saves existing boosts.
|
||||
*/
|
||||
saveBoostToStore(boost) {
|
||||
if (boost) {
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
this.#writeToDisk(this.registeredDomains);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all boosts from persistent storage and updates the registered boosts map.
|
||||
*
|
||||
* @param {Function} done - Callback function to execute after reading is complete.
|
||||
* @private
|
||||
*/
|
||||
#readBoostsFromStore(done) {
|
||||
this.#readFromDisk().then(data => {
|
||||
this.registeredDomains = data;
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path where boost data is stored in the user's profile directory.
|
||||
*
|
||||
* @returns {string} The full path to the boost storage file.
|
||||
* @private
|
||||
*/
|
||||
get #storePath() {
|
||||
const profilePath = PathUtils.profileDir;
|
||||
return PathUtils.join(profilePath, this.#saveFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory path where user css is stored in the user's profile directory.
|
||||
*
|
||||
* @returns {string} The full path to the boost userCSS directory.
|
||||
* @private
|
||||
*/
|
||||
get #cssPath() {
|
||||
const profilePath = PathUtils.profileDir;
|
||||
return PathUtils.join(profilePath, "zen-boosts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads boost data from disk, decompresses it, and converts it to a Map.
|
||||
*
|
||||
* @returns {Promise<Map>} A promise that resolves to a Map of domain to boost data.
|
||||
* @private
|
||||
*/
|
||||
async #readFromDisk() {
|
||||
this.#file = new JSONFile({
|
||||
path: this.#storePath,
|
||||
compression: "lz4",
|
||||
});
|
||||
|
||||
await this.#file.load();
|
||||
|
||||
const raw = this.#file.data ?? {};
|
||||
const map = new Map();
|
||||
|
||||
for (const [domain, entry] of Object.entries(raw)) {
|
||||
const boostsMap = new Map();
|
||||
for (const [id, boostEntry] of Object.entries(entry.boostEntries ?? {})) {
|
||||
// Reuinite the user css with the boost data if any exists
|
||||
const userCSS = await this.#readBoostCSS(id);
|
||||
if (userCSS) {
|
||||
boostEntry.boostData.customCSS = userCSS;
|
||||
}
|
||||
|
||||
boostsMap.set(id, boostEntry);
|
||||
}
|
||||
|
||||
map.set(domain, {
|
||||
activeBoostId: entry.activeBoostId ?? null,
|
||||
boostEntries: boostsMap,
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the user CSS of a boost with the given id from the dedicated folder.
|
||||
* Returns null if the file doesn't exist.
|
||||
*
|
||||
* @param {string} id - The id of the boost
|
||||
* @returns {string|null} Returns the user CSS or null
|
||||
*/
|
||||
async #readBoostCSS(id) {
|
||||
const fileName = `${id}.css`;
|
||||
const directoryPath = this.#cssPath;
|
||||
const savePath = PathUtils.join(directoryPath, fileName);
|
||||
|
||||
await IOUtils.makeDirectory(directoryPath, { createAncestors: true });
|
||||
|
||||
if (await IOUtils.exists(savePath)) {
|
||||
const css = await IOUtils.readUTF8(savePath);
|
||||
return css;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes boost data to disk by converting the Map to JSON and compressing it.
|
||||
*
|
||||
* @param {Map} map - The Map of domain to boost data to write to disk.
|
||||
* @private
|
||||
*/
|
||||
#writeToDisk(map) {
|
||||
const obj = {};
|
||||
|
||||
for (const [domain, entry] of map) {
|
||||
const boostsObj = {};
|
||||
for (const [id, boostEntry] of entry.boostEntries) {
|
||||
// Split the user css from the boost data
|
||||
boostsObj[id] = structuredClone(boostEntry);
|
||||
delete boostsObj[id].boostData.customCSS;
|
||||
|
||||
this.#writeBoostCSS(id, boostEntry.boostData.customCSS);
|
||||
}
|
||||
obj[domain] = {
|
||||
activeBoostId: entry.activeBoostId ?? null,
|
||||
boostEntries: boostsObj,
|
||||
};
|
||||
}
|
||||
|
||||
this.#file.data = obj;
|
||||
this.#file.saveSoon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the user CSS of a boost with the given id to a dedicated folder.
|
||||
*
|
||||
* @param {string} id - The id of the boost
|
||||
* @param {string} css - The user CSS
|
||||
*/
|
||||
async #writeBoostCSS(id, css) {
|
||||
const fileName = `${id}.css`;
|
||||
const directoryPath = this.#cssPath;
|
||||
const savePath = PathUtils.join(directoryPath, fileName);
|
||||
|
||||
await IOUtils.makeDirectory(directoryPath, { createAncestors: true });
|
||||
await IOUtils.writeUTF8(savePath, css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any boost is registered and active for the specified domain.
|
||||
*
|
||||
* @param {string} domain - The domain to check for any registered and active boost.
|
||||
* @returns {boolean} True if a boost exists for the domain and is active, false otherwise.
|
||||
*/
|
||||
registeredBoostForDomain(domain) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
return domainEntry.boostEntries.has(domainEntry.activeBoostId);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a boost can be created for the given URI.
|
||||
* Only HTTP and HTTPS schemes are supported for boosting.
|
||||
*
|
||||
* @param {nsIURI} uri - The URI to check for boost eligibility.
|
||||
* @returns {boolean} True if the URI scheme is http or https, false otherwise.
|
||||
*/
|
||||
canBoostSite(uri) {
|
||||
if (!uri || !uri.schemeIs) {
|
||||
return false;
|
||||
}
|
||||
return uri.schemeIs("http") || uri.schemeIs("https");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets from cache or creates and caches a new style sheet for the given boost data.
|
||||
*
|
||||
* @param {string} domain - The domain of the boosts.
|
||||
* @returns {nsIStyleSheet} The style sheet corresponding to the boost data.
|
||||
*/
|
||||
getStyleSheetForBoost(domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
return this.#stylesManager.getStyleForBoost(boostData, domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the boost editor in a new popup window.
|
||||
*
|
||||
* @param {Window} parentWindow - The parent browser window
|
||||
* @param {Boost} boost - The boost which will be edited
|
||||
* @param {nsIURI} domainUri - The boost which will be edited
|
||||
* @returns {Window|null} The instanced editor window
|
||||
*/
|
||||
openBoostWindow(parentWindow, boost, domainUri) {
|
||||
if (!this.canBoostSite(domainUri)) {
|
||||
console.error(
|
||||
"[ZenBoostsManager] Cannot open editor for boost with invalid domain."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const domain = boost.domain;
|
||||
const { availLeft, availWidth } = parentWindow.screen;
|
||||
const screenX = parentWindow.screenX;
|
||||
const screenY = parentWindow.screenY;
|
||||
const width = parentWindow.outerWidth;
|
||||
const height = parentWindow.outerHeight;
|
||||
const editorWidth = 185;
|
||||
const editorHeight = 565;
|
||||
const pad = 20;
|
||||
|
||||
let left = screenX + width + pad;
|
||||
if (this.#areTabsOnRightSide()) {
|
||||
left = screenX - (editorWidth + pad);
|
||||
}
|
||||
|
||||
let top = screenY + height / 2 - editorHeight / 2;
|
||||
|
||||
if (left + editorWidth > availLeft + availWidth || left < availLeft) {
|
||||
left = screenX + width - (editorWidth + pad);
|
||||
if (this.#areTabsOnRightSide()) {
|
||||
left = screenX + pad;
|
||||
}
|
||||
}
|
||||
|
||||
const editor = Services.ww.openWindow(
|
||||
parentWindow,
|
||||
"chrome://browser/content/zen-components/windows/zen-boost-editor.xhtml",
|
||||
null,
|
||||
`left=${left},top=${top},chrome,alwaysontop,resizable=no,minimizable=no,dependent,dialog=yes`,
|
||||
null
|
||||
);
|
||||
|
||||
// Close the editor if the tab is switched
|
||||
parentWindow.gBrowser.tabContainer.addEventListener(
|
||||
"TabSelect",
|
||||
editor.close.bind(editor),
|
||||
{
|
||||
once: true,
|
||||
}
|
||||
);
|
||||
|
||||
const progressListener = {
|
||||
onLocationChange: webProgress => {
|
||||
if (webProgress.isTopLevel) {
|
||||
editor.close();
|
||||
parentWindow.gBrowser.removeTabsProgressListener(progressListener);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
parentWindow.gBrowser.addProgressListener(progressListener);
|
||||
|
||||
// Give the domain
|
||||
editor.domain = domain;
|
||||
editor.openerWindow = parentWindow;
|
||||
editor.focus();
|
||||
|
||||
// Make boost active
|
||||
this.makeBoostActiveForDomain(domain, boost.id);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn a file save dialog and export the selected boost
|
||||
*
|
||||
* @param {Window} parentWindow The window that will instance the file picker
|
||||
* @param {object} boostData The data of the boost to be exported
|
||||
* @returns {Promise<void>} Returns a promise which will be resolved after the export action is complete
|
||||
*/
|
||||
exportBoost(parentWindow, boostData) {
|
||||
// From: firefox-main/browser/base/content/browser-commands.js:354
|
||||
// https://searchfox.org/firefox-main/source/browser/base/content/browser-commands.js#355:~:text=try%20%7B-,const,fp%2Eopen%28fpCallback%29%3B
|
||||
|
||||
const nsIFilePicker = Ci.nsIFilePicker;
|
||||
const fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
|
||||
fp.init(
|
||||
parentWindow.browsingContext,
|
||||
`Exporting Boost ${boostData.boostName}...`,
|
||||
nsIFilePicker.modeSave
|
||||
);
|
||||
|
||||
// Sanitizing filename
|
||||
// From: https://gist.github.com/barbietunnie/7bc6d48a424446c44ff4#:~:text=bytes%22%29%3B-,var,%7D
|
||||
const illegalRe = /[\/\?<>\\:\*\|":]/g;
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
|
||||
let sanitized = boostData.boostName
|
||||
.replace(illegalRe, "")
|
||||
.replace(controlRe, "")
|
||||
.replace(reservedRe, "")
|
||||
.replace(windowsReservedRe, "");
|
||||
|
||||
// Replace if resulting filename is empty
|
||||
if (!sanitized) {
|
||||
sanitized = "New Boost";
|
||||
}
|
||||
|
||||
fp.defaultString = sanitized;
|
||||
fp.defaultExtension = "json";
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
|
||||
return new Promise(resolve => {
|
||||
fp.open(async result => {
|
||||
if (result === nsIFilePicker.returnOK && fp.file) {
|
||||
try {
|
||||
const boostJSON = JSON.stringify(boostData);
|
||||
await IOUtils.writeUTF8(fp.file.path, boostJSON);
|
||||
resolve(true);
|
||||
} catch (ex) {
|
||||
console.error("Export failed:", ex);
|
||||
resolve(false);
|
||||
}
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn a file open dialog and import the selected boost
|
||||
*
|
||||
* @param {Window} parentWindow The window that will instance the file picker
|
||||
* @returns {Promise<object | null>} Returns a promise with the boost data or null
|
||||
*/
|
||||
importBoost(parentWindow) {
|
||||
const nsIFilePicker = Ci.nsIFilePicker;
|
||||
const fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
|
||||
fp.init(
|
||||
parentWindow.browsingContext,
|
||||
"Importing Boost from JSON",
|
||||
nsIFilePicker.modeOpen
|
||||
);
|
||||
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
|
||||
return new Promise(resolve => {
|
||||
fp.open(async result => {
|
||||
if (result === nsIFilePicker.returnOK && fp.file) {
|
||||
try {
|
||||
const fileContent = await IOUtils.readUTF8(fp.file.path);
|
||||
resolve(JSON.parse(fileContent));
|
||||
} catch (e) {
|
||||
console.error("Import failed:", e);
|
||||
resolve(null);
|
||||
}
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine if tabs are on the right side.
|
||||
* From: ZenDownloadAnimation.mjs
|
||||
*/
|
||||
#areTabsOnRightSide() {
|
||||
return Services.prefs.getBoolPref("zen.tabs.vertical.right-side");
|
||||
}
|
||||
}
|
||||
|
||||
export const gZenBoostsManager = new nsZenBoostsManager();
|
||||
787
src/zen/boosts/ZenSelectorComponent.sys.mjs
Normal file
787
src/zen/boosts/ZenSelectorComponent.sys.mjs
Normal file
@@ -0,0 +1,787 @@
|
||||
/* 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/. */
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "overlayLocalization", () => {
|
||||
return new Localization(["browser/zen-boosts.ftl"], true);
|
||||
});
|
||||
|
||||
export class SelectorComponent {
|
||||
document = null;
|
||||
window = null;
|
||||
#initialized = false;
|
||||
#content = null;
|
||||
|
||||
#currentState = null;
|
||||
#selectedElement = null;
|
||||
#lastOverElement = null;
|
||||
|
||||
#relatedValueIndex = 0;
|
||||
|
||||
static STATES = {
|
||||
SELECTING: "selecting",
|
||||
SELECTED: "selected",
|
||||
};
|
||||
|
||||
#zenContentIDs = [];
|
||||
#onSelect = null;
|
||||
#localizationArray = [
|
||||
{ id: "zen-select-this" },
|
||||
{ id: "zen-select-related" },
|
||||
{ id: "zen-select-cancel" },
|
||||
];
|
||||
|
||||
safeAreaPadding = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
|
||||
/**
|
||||
* @param {Document} document Webpage document
|
||||
* @param {ZenBoostsChild} zenBoostsChild Boost JSActor child
|
||||
* @param {string[]} additionalZenContentIDs Additional IDs that will be considered as non website content
|
||||
* @param {Function<string>} onSelect Callback for when a selection was made. The parameter is the css selector
|
||||
* @param {object[]} localizationArray An array of 3 { id: 'l10n-id' } fluent IDs for localization going from the inputs left to right
|
||||
*/
|
||||
constructor(
|
||||
document,
|
||||
zenBoostsChild,
|
||||
additionalZenContentIDs,
|
||||
onSelect,
|
||||
localizationArray = null
|
||||
) {
|
||||
this.document = document;
|
||||
this.window = document.ownerGlobal;
|
||||
this.zenBoostsChild = zenBoostsChild;
|
||||
this.#onSelect = onSelect;
|
||||
|
||||
if (localizationArray != null) {
|
||||
this.#localizationArray = localizationArray;
|
||||
}
|
||||
|
||||
const baseSelectorIDs = ["select-controls", "select-controls-container"];
|
||||
this.#zenContentIDs = [...additionalZenContentIDs, ...baseSelectorIDs];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the zap mode and inserts anonymous content
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.#initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#content = this.document.insertAnonymousContent();
|
||||
this.#content.root.appendChild(this.fragment);
|
||||
this.#initializeElements();
|
||||
this.setState(SelectorComponent.STATES.SELECTING);
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all anonymous content and events
|
||||
*/
|
||||
#initializeElements() {
|
||||
this.hoverDiv = this.getElementById("hover-div");
|
||||
this.selectorComponent = this.getElementById("select-component");
|
||||
|
||||
this.cancelButton = this.getElementById("select-cancel");
|
||||
this.cancelButton.addEventListener("click", this.#cancelSelect.bind(this));
|
||||
|
||||
this.selectThisButton = this.getElementById("select-this");
|
||||
this.selectThisButton.addEventListener(
|
||||
"click",
|
||||
this.#handleSelect.bind(this)
|
||||
);
|
||||
|
||||
this.selectRelatedSlider = this.getElementById("select-related");
|
||||
this.selectRelatedSlider.addEventListener(
|
||||
"click",
|
||||
this.#handleSelect.bind(this)
|
||||
);
|
||||
|
||||
// Initialize the related elements button
|
||||
this.selectRelatedSlider.addEventListener("mousemove", e => {
|
||||
const r = e.currentTarget.getBoundingClientRect();
|
||||
const mouseX = e.clientX;
|
||||
|
||||
let value = (mouseX - r.left) / r.width;
|
||||
value = Math.max(0, Math.min(1, value));
|
||||
|
||||
e.target.style = `--related-elements-value: ${value * 100}%;`;
|
||||
|
||||
const lastIndex = this.#relatedValueIndex;
|
||||
this.#relatedValueIndex = Math.round(value * 8);
|
||||
|
||||
if (lastIndex != this.#relatedValueIndex) {
|
||||
this.updateHighlight();
|
||||
this.#updatePathTextField();
|
||||
}
|
||||
});
|
||||
|
||||
this.selectRelatedSlider.addEventListener("mouseout", e => {
|
||||
e.currentTarget.style.removeProperty("--related-elements-value");
|
||||
|
||||
this.#relatedValueIndex = 0;
|
||||
this.updateHighlight();
|
||||
this.#updatePathTextField();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the zap mode
|
||||
*
|
||||
* @param {STATES} newState New state
|
||||
* @param {*} data Optional additional data
|
||||
*/
|
||||
setState(newState, data = null) {
|
||||
this.#currentState = newState;
|
||||
|
||||
switch (newState) {
|
||||
case SelectorComponent.STATES.SELECTED:
|
||||
this.#selectedElement = data;
|
||||
this.#relatedValueIndex = 0; // Reset index
|
||||
|
||||
this.#hideHoverDiv();
|
||||
this.#showSelectorComponent();
|
||||
this.updateHighlight();
|
||||
this.#updatePathTextField();
|
||||
break;
|
||||
case SelectorComponent.STATES.SELECTING:
|
||||
this.#selectedElement = null;
|
||||
this.#showHoverDiv();
|
||||
this.#hideSelectorComponent();
|
||||
this.removeHighlight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
get content() {
|
||||
if (!this.#content || Cu.isDeadWrapper(this.#content)) {
|
||||
return null;
|
||||
}
|
||||
return this.#content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an anonymous element by id
|
||||
*
|
||||
* @param {string} id The id of the element
|
||||
*/
|
||||
getElementById(id) {
|
||||
return this.content.root.getElementById(id);
|
||||
}
|
||||
|
||||
get markup() {
|
||||
// Fetch localizations
|
||||
let [thisElement, relatedElements, cancelAction] =
|
||||
lazy.overlayLocalization.formatMessagesSync(this.#localizationArray);
|
||||
|
||||
return `
|
||||
<template>
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/content/zen-selector.css" />
|
||||
<div id="select-component">
|
||||
<div id="select-controls">
|
||||
<input type="button" id="select-this" value="${thisElement.value}"/>
|
||||
<input type="button" id="select-related" value="${relatedElements.value}"/>
|
||||
<input type="button" id="select-cancel" value="${cancelAction.value}"/>
|
||||
</div>
|
||||
<div id="selector-preview">
|
||||
<p id="selector-element-preview-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hover-div"></div>
|
||||
<div id="highlight-container"></div>
|
||||
<div id="highlight-shadow" style="display:none;"></div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
get fragment() {
|
||||
if (!this.template) {
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(this.markup, "text/html");
|
||||
this.template = this.document.importNode(
|
||||
doc.querySelector("template"),
|
||||
true
|
||||
);
|
||||
}
|
||||
let fragment = this.template.content.cloneNode(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the addition of the current zap selector
|
||||
*/
|
||||
#handleSelect() {
|
||||
const cssPath = this.getSelectionPath(
|
||||
this.document,
|
||||
this.#relatedValueIndex,
|
||||
this.#selectedElement
|
||||
);
|
||||
|
||||
this.removeHighlight();
|
||||
this.#resetHoverDiv();
|
||||
|
||||
// The highlight should be gone before the onSelect is called
|
||||
this.window.requestAnimationFrame(() => {
|
||||
this.setState(SelectorComponent.STATES.SELECTING);
|
||||
if (cssPath) {
|
||||
this.#onSelect(cssPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancles the current selection operation
|
||||
*/
|
||||
#cancelSelect() {
|
||||
this.setState(SelectorComponent.STATES.SELECTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the selection highlight
|
||||
*/
|
||||
updateHighlight() {
|
||||
this.removeHighlight();
|
||||
this.showHighlight(this.getSelection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights a selection of elements on the website
|
||||
*
|
||||
* @param {List} selection A list of the web elements that should be highlighted
|
||||
*/
|
||||
showHighlight(selection) {
|
||||
const highlightContainerDiv = this.getElementById("highlight-container");
|
||||
highlightContainerDiv.style.display = "initial";
|
||||
|
||||
let counter = 0;
|
||||
for (const element of selection) {
|
||||
if (counter >= 100) {
|
||||
break;
|
||||
} // Avoid too many instanced objects
|
||||
counter++;
|
||||
|
||||
const padding = 5;
|
||||
const elementMeasurement = element?.getBoundingClientRect() ?? undefined;
|
||||
if (elementMeasurement == undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const highlightDiv = this.document.createElement("div");
|
||||
highlightDiv.classList.add("highlight");
|
||||
|
||||
Object.assign(highlightDiv.style, {
|
||||
left: `${elementMeasurement.left - padding}px`,
|
||||
top: `${elementMeasurement.top - padding}px`,
|
||||
width: `${elementMeasurement.width + padding * 2}px`,
|
||||
height: `${elementMeasurement.height + padding * 2}px`,
|
||||
});
|
||||
|
||||
highlightContainerDiv.appendChild(highlightDiv);
|
||||
}
|
||||
|
||||
this.getElementById("highlight-shadow").style.display = "initial";
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the highlight
|
||||
*/
|
||||
removeHighlight() {
|
||||
const highlightContainerDiv = this.getElementById("highlight-container");
|
||||
highlightContainerDiv.style.display = "none";
|
||||
|
||||
// Clear all children elements
|
||||
highlightContainerDiv.innerHTML = "";
|
||||
this.getElementById("highlight-shadow").style.display = "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the path display text on the selector component
|
||||
* based on the current selection
|
||||
*/
|
||||
#updatePathTextField() {
|
||||
const maxPathLength = 64;
|
||||
const selection = this.getSelection();
|
||||
const selectionPath = this.getSelectionPath(
|
||||
this.document,
|
||||
this.#relatedValueIndex,
|
||||
this.#selectedElement
|
||||
);
|
||||
|
||||
if (!selectionPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pathText = `<b>[${selection.length}]</b> ${selectionPath.substring(0, Math.min(maxPathLength, selectionPath.length))}`;
|
||||
this.getElementById("selector-element-preview-text").setHTML(pathText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners and removes the overlay from the Anonymous Content
|
||||
*/
|
||||
tearDown() {
|
||||
if (this.#content) {
|
||||
try {
|
||||
this.document.removeAnonymousContent(this.#content);
|
||||
} catch {
|
||||
/* This might fail but that's not an issue */
|
||||
}
|
||||
}
|
||||
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
this.#initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the hover selection box
|
||||
*/
|
||||
#hideHoverDiv() {
|
||||
this.hoverDiv.style.display = "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the hover selection box
|
||||
*/
|
||||
#showHoverDiv() {
|
||||
this.hoverDiv.style.display = "initial";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the hover selection box bounds
|
||||
*/
|
||||
#resetHoverDiv() {
|
||||
Object.assign(this.getElementById("hover-div").style, {
|
||||
top: `0px`,
|
||||
left: `0px`,
|
||||
width: `0px`,
|
||||
height: `0px`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the selector component
|
||||
*/
|
||||
#hideSelectorComponent() {
|
||||
this.selectorComponent.style.visibility = "hidden";
|
||||
this.selectorComponent.setAttribute("is-appearing", "false");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the selector component
|
||||
*/
|
||||
#showSelectorComponent() {
|
||||
this.selectorComponent.style.visibility = "visible";
|
||||
this.#setSelectorComponentPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aligned and clamped position for the zap component on the document
|
||||
* relative to #selectedElement
|
||||
*/
|
||||
#setSelectorComponentPosition() {
|
||||
const bounds = this.#selectedElement.getBoundingClientRect();
|
||||
const distance = 8;
|
||||
|
||||
const rect = this.selectorComponent.getBoundingClientRect();
|
||||
const zapComponentWidth = rect.width;
|
||||
const zapComponentHeight = rect.height;
|
||||
|
||||
const windowWidth = this.window.innerWidth;
|
||||
const windowHeight = this.window.innerHeight;
|
||||
const windowPadding = 10;
|
||||
|
||||
this.selectorComponent.setAttribute("is-appearing", "false");
|
||||
|
||||
// This clamps the position so the zap component never goes out of the client bounds and adds a small padding
|
||||
const top = this.clamp(
|
||||
bounds.top + bounds.height + distance,
|
||||
windowPadding + this.safeAreaPadding.top,
|
||||
windowHeight -
|
||||
zapComponentHeight -
|
||||
windowPadding -
|
||||
this.safeAreaPadding.bottom
|
||||
);
|
||||
const left = this.clamp(
|
||||
bounds.left + bounds.width / 2 - zapComponentWidth / 2,
|
||||
windowPadding + this.safeAreaPadding.left,
|
||||
windowWidth -
|
||||
zapComponentWidth -
|
||||
windowPadding -
|
||||
this.safeAreaPadding.right
|
||||
);
|
||||
|
||||
Object.assign(this.selectorComponent.style, {
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
});
|
||||
|
||||
// Adjust transform origin for animation
|
||||
const targetCenterX = bounds.left + bounds.width / 2;
|
||||
const targetBottomY = bounds.top + bounds.height;
|
||||
let originX = this.clamp(targetCenterX - left, 0, zapComponentWidth);
|
||||
let originY = this.clamp(targetBottomY - top, 0, zapComponentHeight);
|
||||
|
||||
this.selectorComponent.style.transformOrigin = `${originX}px ${originY}px`;
|
||||
|
||||
this.window.requestAnimationFrame(() => {
|
||||
this.selectorComponent.setAttribute("is-appearing", "true");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles page events while the overlay is active
|
||||
*
|
||||
* @param {Event} event The event which will be handled by the overlay
|
||||
* @param {boolean} prevent True if the event should be prevented
|
||||
*/
|
||||
handleEvent(event, prevent) {
|
||||
let isZenContent = false;
|
||||
if (event?.originalTarget?.closest) {
|
||||
const closestID = event.originalTarget.closest("div")?.id ?? "";
|
||||
isZenContent = this.#zenContentIDs.includes(closestID);
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case "click":
|
||||
this.#handleClick(event, isZenContent);
|
||||
break;
|
||||
case "mousemove":
|
||||
this.#handleMouseMove(event, isZenContent);
|
||||
break;
|
||||
case "scroll":
|
||||
this.#handlePageChange(event);
|
||||
return;
|
||||
case "resize":
|
||||
this.#handlePageChange(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Let the interactable ids pass through
|
||||
if (isZenContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevent) {
|
||||
// From ScreenshotsComponentChild.sys.mjs:103
|
||||
// Preventing a pointerdown event throws an error in debug builds.
|
||||
// See https://searchfox.org/mozilla-central/rev/b41bb321fe4bd7d03926083698ac498ebec0accf/widget/WidgetEventImpl.cpp#566-572
|
||||
// Don't prevent the default context menu.
|
||||
if (!["contextmenu", "pointerdown"].includes(event.type)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a page change to update the highlight and selector component position
|
||||
*/
|
||||
#handlePageChange() {
|
||||
if (this.#currentState !== SelectorComponent.STATES.SELECTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateHighlight();
|
||||
this.#setSelectorComponentPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse move event
|
||||
*
|
||||
* @param {Event} event Mouse move event params
|
||||
* @param {boolean} isZenContent Flag if the target element is a zen related element
|
||||
*/
|
||||
#handleMouseMove(event, isZenContent) {
|
||||
if (this.#lastOverElement === event.target) {
|
||||
return;
|
||||
}
|
||||
if (!isZenContent) {
|
||||
this.#lastOverElement = event.target;
|
||||
if (this.#currentState === SelectorComponent.STATES.SELECTING) {
|
||||
this.#showHoverDiv();
|
||||
}
|
||||
} else {
|
||||
this.#hideHoverDiv();
|
||||
}
|
||||
|
||||
if (
|
||||
this.#currentState !== SelectorComponent.STATES.SELECTING ||
|
||||
!event.target
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = event.target.getBoundingClientRect();
|
||||
const padding = 5;
|
||||
|
||||
Object.assign(this.getElementById("hover-div").style, {
|
||||
top: `${bounds.top - padding}px`,
|
||||
left: `${bounds.left - padding}px`,
|
||||
width: `${bounds.width + padding * 2}px`,
|
||||
height: `${bounds.height + padding * 2}px`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse click event
|
||||
*
|
||||
* @param {Event} event Mouse move event params
|
||||
* @param {boolean} isZenContent Flag if the target element is a zen related element
|
||||
*/
|
||||
#handleClick(event, isZenContent) {
|
||||
// Safeguards for protecting anonymous content from being zapped
|
||||
if (
|
||||
event.target === this.document.documentElement ||
|
||||
event.target === this.document.body ||
|
||||
!this.document.documentElement.contains(event.target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.#currentState === SelectorComponent.STATES.SELECTING &&
|
||||
!isZenContent
|
||||
) {
|
||||
this.setState(SelectorComponent.STATES.SELECTED, event.target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x Value
|
||||
* @param {number} min Minimum limit
|
||||
* @param {number} max Maximum limit
|
||||
* @returns {number} A value which always lies between min and max
|
||||
*/
|
||||
clamp(x, min, max) {
|
||||
return Math.min(Math.max(x, min), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* When selecting an area to zap there can be a set of zapped elements
|
||||
* since related elements can be included.
|
||||
* This method returns all targeted elements for the zapping process.
|
||||
*
|
||||
* @returns {Element[]} An array of selected elements
|
||||
*/
|
||||
getSelection() {
|
||||
const selector = this.getSelectionPath(
|
||||
this.document,
|
||||
this.#relatedValueIndex,
|
||||
this.#selectedElement
|
||||
);
|
||||
if (!selector) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.document.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for retreiving the css path from the selected element and taking
|
||||
* the related objects into account
|
||||
*
|
||||
* @param {Element} document
|
||||
* @param {Element} relatedValueIndex
|
||||
* @param {Element} selectedElement
|
||||
*/
|
||||
getSelectionPath(document, relatedValueIndex, selectedElement) {
|
||||
let path = [];
|
||||
|
||||
const cssescape = str => CSS.escape(str);
|
||||
|
||||
// Body and Html nodes are not considered valid here
|
||||
const isValidNode = element => {
|
||||
if (!element) {
|
||||
return false;
|
||||
} else if (element.tagName.toLowerCase() === "body") {
|
||||
return false;
|
||||
} else if (element.tagName.toLowerCase() === "html") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const nthChild = element => {
|
||||
if (!element) {
|
||||
return "";
|
||||
}
|
||||
if (!element.parentNode) {
|
||||
return "";
|
||||
}
|
||||
const parent = element.parentNode;
|
||||
const index = Array.prototype.indexOf.call(parent.children, element) + 1;
|
||||
|
||||
if (index === 1) {
|
||||
return ":first-child";
|
||||
}
|
||||
if (index === parent.children.length) {
|
||||
return ":last-child";
|
||||
}
|
||||
return `:nth-child(${index})`;
|
||||
};
|
||||
|
||||
const getIdentification = (element, specifity = 0) => {
|
||||
if (!element) {
|
||||
return "";
|
||||
}
|
||||
const id = specifity < 2 && element.id ? `#${cssescape(element.id)}` : "";
|
||||
const cls =
|
||||
specifity < 1 && element.classList.length
|
||||
? "." + [...element.classList].map(c => cssescape(c)).join(".")
|
||||
: "";
|
||||
const tag = element.tagName ? element.tagName.toLowerCase() : "";
|
||||
return `${tag}${id}${cls}`;
|
||||
};
|
||||
|
||||
const traverse = (element, specifity = 0, pathArray) => {
|
||||
let currentElement = element;
|
||||
if (!isValidNode(currentElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pathArray.push(nthChild(currentElement));
|
||||
pathArray.push(getIdentification(currentElement, specifity));
|
||||
|
||||
if (currentElement && currentElement.parentNode) {
|
||||
pathArray.push(" > ");
|
||||
pathArray.push(getIdentification(currentElement.parentNode, 0));
|
||||
const tempBuild = build(pathArray);
|
||||
|
||||
while (
|
||||
tempBuild &&
|
||||
document.querySelectorAll(tempBuild).length > 1 &&
|
||||
isValidNode(currentElement.parentNode)
|
||||
) {
|
||||
currentElement = currentElement.parentNode;
|
||||
pathArray.push(" > ");
|
||||
pathArray.push(nthChild(currentElement.parentNode));
|
||||
pathArray.push(getIdentification(currentElement.parentNode, 0));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const build = pathArray => pathArray.toReversed().join("");
|
||||
|
||||
const findBestExactSelector = (element, doc) => {
|
||||
let buildMap = new Map();
|
||||
|
||||
let pathExactElement = [];
|
||||
traverse(element, 0, pathExactElement);
|
||||
|
||||
const pathExactElementBuilt = build(pathExactElement);
|
||||
const pathExactElementLength = (
|
||||
pathExactElementBuilt ? doc.querySelectorAll(pathExactElementBuilt) : []
|
||||
).length;
|
||||
|
||||
buildMap.set(pathExactElementLength, pathExactElementBuilt);
|
||||
|
||||
let pathTypeElement = [];
|
||||
pathTypeElement.push(getIdentification(element, 2));
|
||||
if (isValidNode(element.parentNode)) {
|
||||
pathTypeElement.push(" > ");
|
||||
traverse(element.parentNode, 0, pathTypeElement);
|
||||
}
|
||||
|
||||
const pathTypeElementBuilt = build(pathTypeElement);
|
||||
const pathTypeElementLength = (
|
||||
pathTypeElementBuilt ? doc.querySelectorAll(pathTypeElementBuilt) : []
|
||||
).length;
|
||||
|
||||
if (!buildMap.has(pathTypeElementLength)) {
|
||||
buildMap.set(pathTypeElementLength, pathTypeElementBuilt);
|
||||
}
|
||||
|
||||
let parentExactElement = [];
|
||||
if (isValidNode(element.parentNode)) {
|
||||
traverse(element.parentNode, 0, parentExactElement);
|
||||
}
|
||||
|
||||
const pathParentElementBuilt = build(parentExactElement);
|
||||
const pathParentElementLength = (
|
||||
pathParentElementBuilt
|
||||
? doc.querySelectorAll(pathParentElementBuilt)
|
||||
: []
|
||||
).length;
|
||||
|
||||
if (!buildMap.has(pathParentElementLength)) {
|
||||
buildMap.set(pathParentElementLength, pathParentElementBuilt);
|
||||
}
|
||||
|
||||
let smallestLength = Number.MAX_VALUE;
|
||||
let smallestLengthPath = null;
|
||||
buildMap.forEach((buildPath, length) => {
|
||||
if (length < smallestLength) {
|
||||
smallestLength = length;
|
||||
smallestLengthPath = buildPath;
|
||||
}
|
||||
});
|
||||
|
||||
return smallestLengthPath;
|
||||
};
|
||||
|
||||
if (!isValidNode(selectedElement)) {
|
||||
return null;
|
||||
}
|
||||
switch (relatedValueIndex) {
|
||||
// Sometimes getting the exact element we want is not guaranteed by
|
||||
// one specific path builder. It is best to build multiple possible paths
|
||||
// and decide which one is best.
|
||||
default:
|
||||
case 0:
|
||||
return findBestExactSelector(selectedElement, document);
|
||||
// Getting the exact parent element of selected element
|
||||
case 1:
|
||||
if (!isValidNode(selectedElement.parentNode)) {
|
||||
return null;
|
||||
}
|
||||
traverse(selectedElement.parentNode, 0, path);
|
||||
break;
|
||||
// Getting the type of selected element with same exact parent
|
||||
case 2:
|
||||
path.push(getIdentification(selectedElement, 2));
|
||||
if (isValidNode(selectedElement.parentNode)) {
|
||||
path.push(" > ");
|
||||
path.push(getIdentification(selectedElement.parentNode, 0));
|
||||
}
|
||||
break;
|
||||
// Getting the same type of selected element with same parent type
|
||||
case 3:
|
||||
path.push(getIdentification(selectedElement, 2));
|
||||
if (isValidNode(selectedElement.parentNode)) {
|
||||
path.push(" > ");
|
||||
path.push(getIdentification(selectedElement.parentNode, 2));
|
||||
}
|
||||
break;
|
||||
// Getting the same type of selected element
|
||||
case 4:
|
||||
path.push(getIdentification(selectedElement, 2));
|
||||
break;
|
||||
// Getting the same type of the parent
|
||||
case 5:
|
||||
if (!isValidNode(selectedElement.parentNode)) {
|
||||
return null;
|
||||
}
|
||||
path.push(getIdentification(selectedElement.parentNode, 2));
|
||||
break;
|
||||
// Get any element with same parent
|
||||
case 6:
|
||||
if (!isValidNode(selectedElement.parentNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
path.push("*");
|
||||
path.push(" > ");
|
||||
traverse(selectedElement.parentNode, 0, path);
|
||||
break;
|
||||
// Get elements with similar classes
|
||||
case 7:
|
||||
path.push(getIdentification(selectedElement, 1));
|
||||
break;
|
||||
}
|
||||
|
||||
return build(path);
|
||||
}
|
||||
}
|
||||
636
src/zen/boosts/ZenZapDissolve.sys.mjs
Normal file
636
src/zen/boosts/ZenZapDissolve.sys.mjs
Normal file
@@ -0,0 +1,636 @@
|
||||
/* 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/. */
|
||||
|
||||
export class ZapDissolve {
|
||||
FRAG = `
|
||||
precision mediump float;
|
||||
uniform sampler2D u_DissolveTexture;
|
||||
varying vec2 v_PCoord;
|
||||
varying float v_PLifetime;
|
||||
varying float v_POpacity;
|
||||
|
||||
void main() {
|
||||
if(v_PLifetime >= 1.0) discard;
|
||||
|
||||
vec4 sampled = texture2D(u_DissolveTexture, v_PCoord);
|
||||
if(sampled.a == 0.0) discard;
|
||||
|
||||
float distToCenter = distance(vec2(0.5, 0.5), gl_PointCoord);
|
||||
if(distToCenter > 0.5) discard;
|
||||
|
||||
float alpha = sampled.a * v_POpacity;
|
||||
float brightnessFactor = 1.5;
|
||||
gl_FragColor = vec4(sampled.rgb * brightnessFactor, alpha);
|
||||
}
|
||||
`;
|
||||
|
||||
VERT = `
|
||||
precision highp float;
|
||||
|
||||
uniform float u_AnimationDuration;
|
||||
uniform float u_ParticleSize;
|
||||
uniform float u_ElapsedTime;
|
||||
uniform float u_ViewportWidth;
|
||||
uniform float u_ViewportHeight;
|
||||
uniform float u_TextureWidth;
|
||||
uniform float u_TextureHeight;
|
||||
uniform float u_TextureLeft;
|
||||
uniform float u_TextureTop;
|
||||
|
||||
attribute float a_ParticleIndex;
|
||||
|
||||
varying vec2 v_PCoord;
|
||||
varying float v_PLifetime;
|
||||
varying float v_POpacity;
|
||||
|
||||
float hash1(float n) { return fract(sin(n) * 100000.0); }
|
||||
float hash2(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453123); }
|
||||
float hash3(vec2 p, float offset) {
|
||||
return fract(sin(dot(p, vec2(12.9898 + offset, 78.233 - offset))) * 43758.5453123);
|
||||
}
|
||||
|
||||
/* coordinates */
|
||||
vec2 toNDC(vec2 pixel) {
|
||||
vec2 ndc;
|
||||
ndc.x = (pixel.x / u_ViewportWidth) * 2.0 - 1.0;
|
||||
ndc.y = 1.0 - (pixel.y / u_ViewportHeight) * 2.0;
|
||||
return ndc;
|
||||
}
|
||||
|
||||
float mix1(float a, float b, float t) { return a + (b - a) * t; }
|
||||
|
||||
/* particle layout */
|
||||
vec2 particlePixelPos(float index) {
|
||||
float cols = floor(u_TextureWidth / u_ParticleSize);
|
||||
if(cols < 1.0) cols = 1.0;
|
||||
float row = floor(index / cols);
|
||||
float col = index - row * cols;
|
||||
return vec2(
|
||||
(col + 0.5) * u_ParticleSize + u_TextureLeft,
|
||||
(row + 0.5) * u_ParticleSize + u_TextureTop
|
||||
);
|
||||
}
|
||||
|
||||
mat2 rotate2D(float angle) {
|
||||
float s = sin(angle);
|
||||
float c = cos(angle);
|
||||
return mat2(c, -s, s, c);
|
||||
}
|
||||
|
||||
/* motion */
|
||||
vec4 animatedPosition(vec2 pixelPos, float seed, float seed2, float seed3, float t) {
|
||||
vec2 base = toNDC(pixelPos);
|
||||
|
||||
// Random rotation
|
||||
float rotation1 = (seed - 0.5) * 0.52; // 15 * 2 * (π/180)
|
||||
float rotation2 = (seed2 - 0.5) * 0.52;
|
||||
|
||||
// Random translation
|
||||
float randomRadian = 6.28318 * (seed3 - 0.5);
|
||||
float translateX = 60.0 * cos(randomRadian) / u_ViewportWidth * 2.0;
|
||||
float translateY = 30.0 * sin(randomRadian) / u_ViewportHeight * 2.0;
|
||||
vec2 rotated1 = rotate2D(rotation1 * t) * base;
|
||||
|
||||
vec2 translated = vec2(
|
||||
mix1(rotated1.x, rotated1.x + translateX, t),
|
||||
mix1(rotated1.y, rotated1.y + translateY, t)
|
||||
);
|
||||
|
||||
vec2 finalPos = rotate2D(rotation2 * t) * (translated - base) + base;
|
||||
|
||||
return vec4(finalPos, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 pixelPos = particlePixelPos(a_ParticleIndex);
|
||||
|
||||
// Multiple seeds for different random values
|
||||
float seed = hash2(pixelPos);
|
||||
float seed2 = hash3(pixelPos, 1.0);
|
||||
float seed3 = hash3(pixelPos, 2.0);
|
||||
|
||||
float normalizedX = (pixelPos.x - u_TextureLeft) / u_TextureWidth;
|
||||
float frameAssignment = (seed + 2.0 * normalizedX) / 3.0;
|
||||
float maxDelay = u_AnimationDuration * 0.42;
|
||||
float delay = frameAssignment * maxDelay;
|
||||
float particleDuration = u_AnimationDuration - delay;
|
||||
float lifetime = clamp((u_ElapsedTime - delay) / particleDuration, 0.0, 1.0);
|
||||
float easedT = 1.0 - pow(1.0 - lifetime, 3.0);
|
||||
|
||||
gl_Position = animatedPosition(pixelPos, seed, seed2, seed3, easedT);
|
||||
gl_PointSize = u_ParticleSize;
|
||||
|
||||
v_PLifetime = lifetime;
|
||||
v_POpacity = 1.0 - lifetime;
|
||||
|
||||
// Texture coordinates
|
||||
float cols = floor(u_TextureWidth / u_ParticleSize);
|
||||
if(cols < 1.0) cols = 1.0;
|
||||
|
||||
float row = floor(a_ParticleIndex / cols);
|
||||
float col = a_ParticleIndex - row * cols;
|
||||
|
||||
v_PCoord = vec2(
|
||||
(col + 0.5) / cols,
|
||||
(row + 0.5) / floor(u_TextureHeight / u_ParticleSize)
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
window = null;
|
||||
document = null;
|
||||
#initialized = false;
|
||||
#content = null;
|
||||
|
||||
#webglContext = null;
|
||||
#program;
|
||||
#animationStartTime = -1;
|
||||
#particlesCount = 0;
|
||||
#duration = 1400;
|
||||
#texture = null;
|
||||
#buffer = null;
|
||||
|
||||
#hasTriggered = false;
|
||||
#rafId = null;
|
||||
|
||||
#onComplete = null;
|
||||
|
||||
/**
|
||||
* @param {Document} document Webpage document
|
||||
*/
|
||||
constructor(document) {
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
this.document = document;
|
||||
this.window = document.ownerGlobal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the zap mode and inserts anonymous content
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.#initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#content = this.document.insertAnonymousContent();
|
||||
this.#content.root.appendChild(this.fragment);
|
||||
await this.#initializeElements();
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the elements and contexts required for the rendering
|
||||
*/
|
||||
async #initializeElements() {
|
||||
const canvas = this.getElementById("zen-zap-dissolve-canvas");
|
||||
const gl = canvas.getContext("webgl", {
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
premultipliedAlpha: false,
|
||||
});
|
||||
this.#webglContext = gl;
|
||||
|
||||
if (!gl) {
|
||||
console.error("WebGL is not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.#resizeCanvasToClientSize(canvas);
|
||||
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
|
||||
// Ensure viewport is set correctly
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
|
||||
this.#program = await this.#createProgram(gl, this.VERT, this.FRAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the canvas to the correct size
|
||||
*
|
||||
* @param {Element} canvas
|
||||
*/
|
||||
#resizeCanvasToClientSize(canvas) {
|
||||
const width = this.window.innerWidth;
|
||||
const height = this.window.innerHeight;
|
||||
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
// Set the WebGL viewport to match the canvas size
|
||||
if (this.#webglContext) {
|
||||
this.#webglContext.viewport(0, 0, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the canvas program
|
||||
*
|
||||
* @param {object} gl WebGL context
|
||||
* @param {string} vertContent Content of the vertex shader
|
||||
* @param {string} fragContent Content of the fragment shader
|
||||
* @returns {*} The WebGL program
|
||||
*/
|
||||
async #createProgram(gl, vertContent, fragContent) {
|
||||
const vertexShader = this.#compileShader(gl, vertContent, gl.VERTEX_SHADER);
|
||||
const fragmentShader = this.#compileShader(
|
||||
gl,
|
||||
fragContent,
|
||||
gl.FRAGMENT_SHADER
|
||||
);
|
||||
|
||||
if (!vertexShader || !fragmentShader) {
|
||||
console.error(
|
||||
"Program creation aborted: One or more shaders failed to compile."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
|
||||
gl.detachShader(program, vertexShader);
|
||||
gl.detachShader(program, fragmentShader);
|
||||
gl.deleteShader(vertexShader);
|
||||
gl.deleteShader(fragmentShader);
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
console.error(
|
||||
"Shader program initializiation failure:",
|
||||
gl.getProgramInfoLog(program)
|
||||
);
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a shader
|
||||
*
|
||||
* @param {object} gl WebGL context
|
||||
* @param {string} source Shader source
|
||||
* @param {object} type Shader type
|
||||
* @returns {*} The compiled shader
|
||||
*/
|
||||
#compileShader(gl, source, type) {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
console.error("Shader compilation failure:", gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives image data from a given image
|
||||
*
|
||||
* @param {Image} image The image
|
||||
* @returns {ImageData} Image data
|
||||
*/
|
||||
#getImageData(image) {
|
||||
const canvas = this.getElementById(
|
||||
"zen-zap-dissolve-canvas"
|
||||
).ownerDocument.createElement("canvas");
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
context.drawImage(image, 0, 0);
|
||||
return context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and loads a WebGL texture from an Image
|
||||
*
|
||||
* @param {Image} image The target image
|
||||
*/
|
||||
#loadTexture(image) {
|
||||
if (this.#texture) {
|
||||
this.#webglContext.deleteTexture(this.#texture);
|
||||
}
|
||||
|
||||
const texture = this.#webglContext.createTexture();
|
||||
this.#webglContext.activeTexture(this.#webglContext.TEXTURE0);
|
||||
this.#webglContext.bindTexture(this.#webglContext.TEXTURE_2D, texture);
|
||||
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_WRAP_S,
|
||||
this.#webglContext.CLAMP_TO_EDGE
|
||||
);
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_WRAP_T,
|
||||
this.#webglContext.CLAMP_TO_EDGE
|
||||
);
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_MIN_FILTER,
|
||||
this.#webglContext.NEAREST
|
||||
);
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_MAG_FILTER,
|
||||
this.#webglContext.NEAREST
|
||||
);
|
||||
|
||||
if (
|
||||
image &&
|
||||
image instanceof Ci.nsIImageLoadingContent &&
|
||||
image.width &&
|
||||
image.height
|
||||
) {
|
||||
this.#webglContext.texImage2D(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
0,
|
||||
this.#webglContext.RGBA,
|
||||
this.#webglContext.RGBA,
|
||||
this.#webglContext.UNSIGNED_BYTE,
|
||||
image
|
||||
);
|
||||
} else {
|
||||
const imageData = this.#getImageData(image);
|
||||
this.#webglContext.texImage2D(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
0,
|
||||
this.#webglContext.RGBA,
|
||||
imageData.width,
|
||||
imageData.height,
|
||||
0,
|
||||
this.#webglContext.RGBA,
|
||||
this.#webglContext.UNSIGNED_BYTE,
|
||||
imageData.data
|
||||
);
|
||||
}
|
||||
|
||||
const textureLocation = this.#webglContext.getUniformLocation(
|
||||
this.#program,
|
||||
"u_DissolveTexture"
|
||||
);
|
||||
this.#webglContext.uniform1i(textureLocation, 0);
|
||||
this.#texture = texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the parameters to the program for a given Element
|
||||
*
|
||||
* @param {Element} element
|
||||
*/
|
||||
#bindParameters(element) {
|
||||
const gl = this.#webglContext;
|
||||
const rect = element.getBoundingClientRect();
|
||||
const particleSize = 1;
|
||||
|
||||
const textureWidth = rect.width;
|
||||
const textureHeight = rect.height;
|
||||
|
||||
const cols = Math.floor(textureWidth / particleSize);
|
||||
const rows = Math.floor(textureHeight / particleSize);
|
||||
this.#particlesCount = cols * rows;
|
||||
|
||||
gl.useProgram(this.#program);
|
||||
|
||||
this.#setUniform1f("u_AnimationDuration", this.#duration);
|
||||
this.#setUniform1f("u_ParticleSize", particleSize);
|
||||
this.#setUniform1f("u_ViewportWidth", this.window.innerWidth);
|
||||
this.#setUniform1f("u_ViewportHeight", this.window.innerHeight);
|
||||
this.#setUniform1f("u_TextureWidth", textureWidth);
|
||||
this.#setUniform1f("u_TextureHeight", textureHeight);
|
||||
|
||||
this.#setUniform1f("u_TextureLeft", rect.left);
|
||||
this.#setUniform1f("u_TextureTop", rect.top);
|
||||
|
||||
const indices = new Float32Array(this.#particlesCount);
|
||||
for (let i = 0; i < this.#particlesCount; i++) {
|
||||
indices[i] = i;
|
||||
}
|
||||
|
||||
if (this.#buffer) {
|
||||
gl.deleteBuffer(this.#buffer);
|
||||
}
|
||||
|
||||
const buffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, indices, gl.STATIC_DRAW);
|
||||
|
||||
const aLoc = gl.getAttribLocation(this.#program, "a_ParticleIndex");
|
||||
gl.enableVertexAttribArray(aLoc);
|
||||
gl.vertexAttribPointer(aLoc, 1, gl.FLOAT, false, 0, 0);
|
||||
|
||||
this.#buffer = buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an animation frame
|
||||
*/
|
||||
#requestDraw() {
|
||||
this.#rafId = this.window.requestAnimationFrame(t => this.#draw(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for setting a uniform in the WebGL context
|
||||
*
|
||||
* @param {string} name The property name
|
||||
* @param {*} value The property value
|
||||
*/
|
||||
#setUniform1f(name, value) {
|
||||
const loc = this.#webglContext.getUniformLocation(this.#program, name);
|
||||
this.#webglContext.uniform1f(loc, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the output to a canvas
|
||||
*
|
||||
* @param {number} time The frametime
|
||||
*/
|
||||
#draw(time) {
|
||||
const gl = this.#webglContext;
|
||||
gl.useProgram(this.#program);
|
||||
|
||||
if (this.#animationStartTime === -1) {
|
||||
this.#animationStartTime = time;
|
||||
}
|
||||
|
||||
const elapsed = time - this.#animationStartTime;
|
||||
if (elapsed > this.#duration) {
|
||||
this.#resetForNextRun();
|
||||
return;
|
||||
}
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
this.#setUniform1f("u_ElapsedTime", elapsed);
|
||||
|
||||
gl.drawArrays(gl.POINTS, 0, this.#particlesCount);
|
||||
this.#requestDraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dissolve effect for the element
|
||||
*
|
||||
* @param {Element} element The element to dissolve
|
||||
* @param {Function} onComplete Callback for when the animation is complete
|
||||
*/
|
||||
dissolve(element, onComplete) {
|
||||
if (!this.#initialized || this.#hasTriggered || !element) {
|
||||
return;
|
||||
}
|
||||
this.#hasTriggered = true;
|
||||
|
||||
this.#onComplete = onComplete;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) {
|
||||
console.warn("[ZapDissolve]: element has zero size. Skipping dissolve");
|
||||
return;
|
||||
}
|
||||
|
||||
const captureCanvas = this.document.createElement("canvas");
|
||||
captureCanvas.width = rect.width;
|
||||
captureCanvas.height = rect.height;
|
||||
const ctx = captureCanvas.getContext("2d");
|
||||
|
||||
const canvas = this.getElementById("zen-zap-dissolve-canvas");
|
||||
this.#resizeCanvasToClientSize(canvas);
|
||||
|
||||
ctx.drawWindow(
|
||||
this.window,
|
||||
rect.left + this.window.scrollX,
|
||||
rect.top + this.window.scrollY,
|
||||
rect.width,
|
||||
rect.height,
|
||||
"rgba(0,0,0,0)"
|
||||
);
|
||||
|
||||
this.#loadTexture(captureCanvas);
|
||||
this.#bindParameters(element);
|
||||
|
||||
this.#animationStartTime = -1;
|
||||
this.#requestDraw();
|
||||
}
|
||||
|
||||
get content() {
|
||||
if (!this.#content || Cu.isDeadWrapper(this.#content)) {
|
||||
return null;
|
||||
}
|
||||
return this.#content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an anonymous element by id
|
||||
*
|
||||
* @param {string} id The id of the element
|
||||
*/
|
||||
getElementById(id) {
|
||||
return this.content.root.getElementById(id);
|
||||
}
|
||||
|
||||
get markup() {
|
||||
return `
|
||||
<template>
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/content/zen-zap.css" />
|
||||
<canvas id="zen-zap-dissolve-canvas"></canvas>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
get fragment() {
|
||||
if (!this.template) {
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(this.markup, "text/html");
|
||||
this.template = this.document.importNode(
|
||||
doc.querySelector("template"),
|
||||
true
|
||||
);
|
||||
}
|
||||
let fragment = this.template.content.cloneNode(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the class ready for reusage
|
||||
*/
|
||||
#resetForNextRun() {
|
||||
const gl = this.#webglContext;
|
||||
if (gl) {
|
||||
if (this.#texture) {
|
||||
gl.deleteTexture(this.#texture);
|
||||
this.#texture = null;
|
||||
}
|
||||
if (this.#buffer) {
|
||||
gl.deleteBuffer(this.#buffer);
|
||||
this.#buffer = null;
|
||||
}
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
this.#hasTriggered = false;
|
||||
this.#animationStartTime = -1;
|
||||
|
||||
if (this.#rafId) {
|
||||
this.window.cancelAnimationFrame(this.#rafId);
|
||||
this.#rafId = null;
|
||||
}
|
||||
|
||||
if (this.#onComplete) {
|
||||
this.#onComplete();
|
||||
this.#onComplete = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners and removes the overlay from the Anonymous Content
|
||||
*/
|
||||
tearDown() {
|
||||
const gl = this.#webglContext;
|
||||
if (gl) {
|
||||
if (this.#texture) {
|
||||
gl.deleteTexture(this.#texture);
|
||||
this.#texture = null;
|
||||
}
|
||||
if (this.#buffer) {
|
||||
gl.deleteBuffer(this.#buffer);
|
||||
this.#buffer = null;
|
||||
}
|
||||
|
||||
if (this.#program) {
|
||||
gl.deleteProgram(this.#program);
|
||||
this.#program = null;
|
||||
}
|
||||
|
||||
const loseCtx = gl.getExtension("WEBGL_lose_context");
|
||||
loseCtx?.loseContext();
|
||||
}
|
||||
|
||||
if (this.window != null) {
|
||||
this.window.cancelAnimationFrame(this.#rafId);
|
||||
}
|
||||
this.#rafId = null;
|
||||
|
||||
if (this.#content) {
|
||||
try {
|
||||
this.document.removeAnonymousContent(this.#content);
|
||||
} catch {
|
||||
/* This might fail but that's not an issue */
|
||||
}
|
||||
}
|
||||
|
||||
this.#content = null;
|
||||
this.#initialized = false;
|
||||
}
|
||||
}
|
||||
424
src/zen/boosts/ZenZapOverlayChild.sys.mjs
Normal file
424
src/zen/boosts/ZenZapOverlayChild.sys.mjs
Normal file
@@ -0,0 +1,424 @@
|
||||
/* 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/. */
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ZapDissolve: "resource:///modules/zen/boosts/ZenZapDissolve.sys.mjs",
|
||||
SelectorComponent:
|
||||
"resource:///modules/zen/boosts/ZenSelectorComponent.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "overlayLocalization", () => {
|
||||
return new Localization(["browser/zen-boosts.ftl"], true);
|
||||
});
|
||||
|
||||
export class ZapOverlay {
|
||||
document = null;
|
||||
window = null;
|
||||
#initialized = false;
|
||||
#content = null;
|
||||
|
||||
#zapContentIDs = ["zap-list", "zap-controls-container"];
|
||||
#selectorComponent = null;
|
||||
|
||||
#dissolvePoolSize = 5;
|
||||
#dissolveEffectPool = [];
|
||||
#currentDissolveIndex = 0;
|
||||
|
||||
/**
|
||||
* @param {*} document Webpage document
|
||||
* @param {*} zenBoostsChild Boost JSActor child
|
||||
*/
|
||||
constructor(document, zenBoostsChild) {
|
||||
this.document = document;
|
||||
this.window = document.ownerGlobal;
|
||||
this.zenBoostsChild = zenBoostsChild;
|
||||
|
||||
this.#selectorComponent = new lazy.SelectorComponent(
|
||||
document,
|
||||
zenBoostsChild,
|
||||
this.#zapContentIDs,
|
||||
this.handleSelectComponentSelect.bind(this),
|
||||
[
|
||||
{ id: "zen-zap-this" },
|
||||
{ id: "zen-zap-related" },
|
||||
{ id: "zen-zap-cancel" },
|
||||
]
|
||||
);
|
||||
|
||||
// Remove the bottom unzap bar to the safe area
|
||||
this.#selectorComponent.safeAreaPadding.bottom = 65;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the zap mode and inserts anonymous content
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.#initialized) {
|
||||
console.warn(
|
||||
"[ZenZapOverlayChild]: Skipping initialize because initialized."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.#selectorComponent.initialize();
|
||||
|
||||
this.#content = this.document.insertAnonymousContent();
|
||||
this.#content.root.appendChild(this.fragment);
|
||||
this.#initializeElements();
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all anonymous content and events
|
||||
*/
|
||||
#initializeElements() {
|
||||
this.zapDoneButton = this.getElementById("zap-done");
|
||||
this.zapDoneButton.addEventListener(
|
||||
"click",
|
||||
this.#disableZapMode.bind(this)
|
||||
);
|
||||
|
||||
this.#updateZappedList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily loads the next available dissolve effect.
|
||||
* The returned effect might not currently be ready to trigger again.
|
||||
*
|
||||
* @returns {Promise<ZapDissolve>} Dissolve effect
|
||||
*/
|
||||
async #getNextDissolveEffect() {
|
||||
// Effect does not exist yet, create and initialize
|
||||
if (this.#currentDissolveIndex >= this.#dissolveEffectPool.length) {
|
||||
const dissolveEffect = new lazy.ZapDissolve(this.document);
|
||||
await dissolveEffect.initialize();
|
||||
this.#dissolveEffectPool.push(dissolveEffect);
|
||||
}
|
||||
|
||||
// Capture current index and increment for next call
|
||||
const returnIndex = this.#currentDissolveIndex;
|
||||
this.#currentDissolveIndex =
|
||||
(this.#currentDissolveIndex + 1) % this.#dissolvePoolSize;
|
||||
|
||||
return this.#dissolveEffectPool[returnIndex];
|
||||
}
|
||||
|
||||
get content() {
|
||||
if (!this.#content || Cu.isDeadWrapper(this.#content)) {
|
||||
return null;
|
||||
}
|
||||
return this.#content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an anonymous element by id
|
||||
*
|
||||
* @param {string} id The id of the element
|
||||
*/
|
||||
getElementById(id) {
|
||||
return this.content.root.getElementById(id);
|
||||
}
|
||||
|
||||
get markup() {
|
||||
// Fetch localizations
|
||||
let [done] = lazy.overlayLocalization.formatMessagesSync([
|
||||
{ id: "zen-zap-done" },
|
||||
]);
|
||||
|
||||
return `
|
||||
<template>
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/content/zen-zap.css" />
|
||||
<div id="zap-controls-container">
|
||||
<div id="zap-list">
|
||||
</div>
|
||||
<input type="button" id="zap-done" value="${done.value}"/>
|
||||
</div>
|
||||
<div id="zap-border"></div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
get fragment() {
|
||||
if (!this.template) {
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(this.markup, "text/html");
|
||||
this.template = this.document.importNode(
|
||||
doc.querySelector("template"),
|
||||
true
|
||||
);
|
||||
}
|
||||
let fragment = this.template.content.cloneNode(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the onSelect callback from the SelectComponent
|
||||
*
|
||||
* @param {string} cssSelector The CSS selector of the selected element
|
||||
*/
|
||||
handleSelectComponentSelect(cssSelector) {
|
||||
this.#handleZap(cssSelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listeners for an update in the zap list
|
||||
*/
|
||||
onZapUpdate() {
|
||||
this.#updateZappedList();
|
||||
this.zenBoostsChild.sendNotify("zap-list-update");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the addition of the given zap selector
|
||||
*
|
||||
* @param {string} cssPath The css selector of the zap
|
||||
*/
|
||||
#handleZap(cssPath) {
|
||||
const useDissolve = Services.prefs.getBoolPref(
|
||||
"zen.boosts.dissolve-on-zap"
|
||||
);
|
||||
if (!this.window.gReduceMotion && useDissolve) {
|
||||
const elements = this.document.querySelectorAll(cssPath);
|
||||
|
||||
let counter = 0;
|
||||
elements.forEach(async element => {
|
||||
if (counter >= this.#dissolvePoolSize) {
|
||||
return;
|
||||
}
|
||||
counter++;
|
||||
|
||||
this.#getNextDissolveEffect().then(dissolve => {
|
||||
dissolve.dissolve(element, () => {
|
||||
this.zenBoostsChild.addZapSelector(cssPath);
|
||||
this.onZapUpdate();
|
||||
|
||||
this.window.requestAnimationFrame(() => {
|
||||
element.style.removeProperty("opacity");
|
||||
});
|
||||
});
|
||||
element.style.setProperty("opacity", "0", "important");
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.zenBoostsChild.addZapSelector(cssPath);
|
||||
this.onZapUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the removal of a zap selector
|
||||
*
|
||||
* @param {string} cssPath The css selector of the zap
|
||||
*/
|
||||
#handleUnzap(cssPath) {
|
||||
this.zenBoostsChild.removeZapSelector(cssPath);
|
||||
this.onZapUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancles the current zap operation
|
||||
*/
|
||||
#cancelZap() {
|
||||
this.#selectorComponent.setState(lazy.SelectorComponent.STATES.SELECTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for leaving the zap mode
|
||||
*/
|
||||
#disableZapMode() {
|
||||
this.zenBoostsChild.disableZapMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the unzap button list at the bottom of the website
|
||||
*/
|
||||
async #updateZappedList() {
|
||||
const zapList = this.getElementById("zap-list");
|
||||
zapList.innerHTML = "";
|
||||
|
||||
const boost = await this.zenBoostsChild.getWebsiteBoost();
|
||||
const { boostData } = boost.boostEntry;
|
||||
|
||||
boostData.zapSelectors.forEach(selector => {
|
||||
const unzapButton = zapList.ownerDocument.createElement("input");
|
||||
unzapButton.type = "button";
|
||||
unzapButton.id = "zen-zap-unzap";
|
||||
|
||||
const index = boostData.zapSelectors.indexOf(selector) + 1;
|
||||
const zappedElementsCount =
|
||||
selector == "" ? 0 : this.document.querySelectorAll(selector).length;
|
||||
|
||||
const [tooltip] = lazy.overlayLocalization.formatMessagesSync([
|
||||
{
|
||||
id: "zen-unzap-tooltip",
|
||||
args: { elementCount: zappedElementsCount },
|
||||
},
|
||||
]);
|
||||
|
||||
unzapButton.value = index;
|
||||
unzapButton.title = tooltip.value;
|
||||
|
||||
unzapButton.setAttribute("index", index);
|
||||
unzapButton.setAttribute("selector", selector);
|
||||
zapList.appendChild(unzapButton);
|
||||
});
|
||||
|
||||
// Fetch localizations
|
||||
let [addZapHelper, removeZapHelper] =
|
||||
lazy.overlayLocalization.formatMessagesSync([
|
||||
{ id: "zen-add-zap-helper" },
|
||||
{ id: "zen-remove-zap-helper" },
|
||||
]);
|
||||
|
||||
if (!boostData.zapSelectors.length) {
|
||||
const addZapHelperText = zapList.ownerDocument.createElement("p");
|
||||
addZapHelperText.setHTML(addZapHelper.value);
|
||||
addZapHelperText.classList.add("pcenter");
|
||||
zapList.appendChild(addZapHelperText);
|
||||
} else {
|
||||
const removeZapHelperText = zapList.ownerDocument.createElement("p");
|
||||
removeZapHelperText.setHTML(removeZapHelper.value);
|
||||
zapList.appendChild(removeZapHelperText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse enter event for the unzap buttons
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
#unzapButtonHover(event) {
|
||||
const button = event.originalTarget;
|
||||
const selector = button.getAttribute("selector");
|
||||
this.zenBoostsChild.tempShowZappedElement(selector);
|
||||
|
||||
button.value = "×";
|
||||
|
||||
// This has to run with later, as the elements we are trying to highlight do not exist yet.
|
||||
// The css has to load first and calculate the bounding boxes for the elements before we can highlight.
|
||||
this.window.requestAnimationFrame(() => {
|
||||
const selection = this.document.querySelectorAll(selector);
|
||||
if (selection.length) {
|
||||
this.#selectorComponent.showHighlight(selection);
|
||||
}
|
||||
});
|
||||
|
||||
// Cancle an ongoing select action
|
||||
this.#cancelZap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse exit event for the unzap buttons
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
#unzapButtonUnhover(event) {
|
||||
const button = event.originalTarget;
|
||||
button.value = button.getAttribute("index");
|
||||
|
||||
this.zenBoostsChild.tempHideZappedElement();
|
||||
this.#selectorComponent.removeHighlight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles button clicks from the unzap list
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
#unzapButtonClick(event) {
|
||||
const button = event.originalTarget;
|
||||
const selector = button.getAttribute("selector");
|
||||
|
||||
this.zenBoostsChild.tempHideZappedElement();
|
||||
this.#selectorComponent.removeHighlight();
|
||||
this.#cancelZap();
|
||||
|
||||
// In order to avoid the clicked element being null when the
|
||||
// SelectorComponent receives it, push the list re-creation to the next frame
|
||||
this.window.requestAnimationFrame(() => {
|
||||
this.#handleUnzap(selector);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners and removes the overlay from the Anonymous Content
|
||||
*/
|
||||
tearDown() {
|
||||
this.#selectorComponent.tearDown();
|
||||
this.#selectorComponent = null;
|
||||
|
||||
this.#dissolveEffectPool.forEach(dissolve => {
|
||||
dissolve.tearDown();
|
||||
});
|
||||
|
||||
if (this.#content) {
|
||||
try {
|
||||
this.document.removeAnonymousContent(this.#content);
|
||||
} catch {
|
||||
/* This might fail but that's not an issue */
|
||||
}
|
||||
}
|
||||
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
this.#initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles page events while the overlay is active
|
||||
*
|
||||
* @param {Event} event The event which will be handled by the overlay
|
||||
* @param {boolean} prevent True if the event should be prevented
|
||||
*/
|
||||
handleEvent(event, prevent) {
|
||||
switch (event.type) {
|
||||
case "click":
|
||||
this.#handleClick(event);
|
||||
break;
|
||||
case "mouseover":
|
||||
this.#handleHoverDelegation(event);
|
||||
break;
|
||||
case "mouseout":
|
||||
this.#handleUnhoverDelegation(event);
|
||||
}
|
||||
|
||||
this.#selectorComponent.handleEvent(event, prevent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse click event
|
||||
*
|
||||
* @param {Event} event Mouse move event params
|
||||
*/
|
||||
#handleClick(event) {
|
||||
if (event.originalTarget.id == "zen-zap-unzap") {
|
||||
this.#unzapButtonClick(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse enter event
|
||||
*
|
||||
* @param {Event} event Mouse enter event params
|
||||
*/
|
||||
#handleHoverDelegation(event) {
|
||||
if (event.originalTarget.id == "zen-zap-unzap") {
|
||||
this.#unzapButtonHover(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse leave event
|
||||
*
|
||||
* @param {Event} event Mouse leave event params
|
||||
*/
|
||||
#handleUnhoverDelegation(event) {
|
||||
if (event.originalTarget.id == "zen-zap-unzap") {
|
||||
this.#unzapButtonUnhover(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
539
src/zen/boosts/actors/ZenBoostsChild.sys.mjs
Normal file
539
src/zen/boosts/actors/ZenBoostsChild.sys.mjs
Normal file
@@ -0,0 +1,539 @@
|
||||
// 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/.
|
||||
|
||||
const AGENT_SHEET = Ci.nsIStyleSheetService.AGENT_SHEET;
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ZapOverlay: "resource:///modules/zen/boosts/ZenZapOverlayChild.sys.mjs",
|
||||
SelectorComponent:
|
||||
"resource:///modules/zen/boosts/ZenSelectorComponent.sys.mjs",
|
||||
});
|
||||
|
||||
export class ZenBoostsChild extends JSWindowActorChild {
|
||||
#currentSheet = null;
|
||||
#currentState = ZenBoostsChild.STATES.NONE;
|
||||
#preventableEventsAdded = false;
|
||||
#zappedElementsTempShown = [];
|
||||
|
||||
#overlay = null;
|
||||
|
||||
static STATES = {
|
||||
NONE: "none",
|
||||
ZAP: "zap",
|
||||
PICKER: "picker",
|
||||
};
|
||||
|
||||
static OVERLAY_EVENTS = [
|
||||
"click",
|
||||
"pointerdown",
|
||||
"pointermove",
|
||||
"pointerup",
|
||||
"scroll",
|
||||
"resize",
|
||||
];
|
||||
|
||||
// A list of events that will be prevented from
|
||||
// reaching the document
|
||||
static PREVENTABLE_EVENTS = [
|
||||
"click",
|
||||
"pointerdown",
|
||||
"pointermove",
|
||||
"pointerup",
|
||||
"mousemove",
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"mouseenter",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"mouseleave",
|
||||
"touchstart",
|
||||
"touchmove",
|
||||
"touchend",
|
||||
"dblclick",
|
||||
"auxclick",
|
||||
"keypress",
|
||||
"contextmenu",
|
||||
"pointerenter",
|
||||
"pointerover",
|
||||
"pointerout",
|
||||
"pointerleave",
|
||||
];
|
||||
|
||||
// Caching the events in sets for performance
|
||||
static ALL_EVENTS_SET = new Set([
|
||||
...ZenBoostsChild.OVERLAY_EVENTS,
|
||||
...ZenBoostsChild.PREVENTABLE_EVENTS,
|
||||
]);
|
||||
|
||||
static PREVENTABLE_SET = new Set(ZenBoostsChild.PREVENTABLE_EVENTS);
|
||||
|
||||
/**
|
||||
* Inverse of https://searchfox.org/firefox-main/rev/1a8c62b86277005f907151bc5389cf5c5091e76f/gfx/src/nsColor.h#23-27
|
||||
*
|
||||
* > #define NS_RGBA(_r, _g, _b, _a) \
|
||||
* > ((nscolor)(((_a) << 24) | ((_b) << 16) | ((_g) << 8) | (_r)))
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param {Array} rgb - Array of red, green, blue values [0, 255]
|
||||
* @param {number} rgb.0 - Red color value [0, 255]
|
||||
* @param {number} rgb.1 - Green color value [0, 255]
|
||||
* @param {number} rgb.2 - Blue color value [0, 255]
|
||||
* @param {number} contrast - Contrast value (default 255)
|
||||
* @returns {number} NSColor integer representation
|
||||
*/
|
||||
#rgbToNSColor([r, g, b], contrast = 255) {
|
||||
// Note will be using the alpha channel for contrast, since the colors will always
|
||||
// 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.
|
||||
// Take into account that its an unsigned int
|
||||
return ((contrast << 24) | (b << 16) | (g << 8) | r) >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* From ZenGradientGenerator.mjs
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from https://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*
|
||||
* @param {number} h The hue
|
||||
* @param {number} s The saturation
|
||||
* @param {number} l The lightness
|
||||
* @returns {Array} The RGB representation
|
||||
*/
|
||||
#hslToRgb(h, s, l) {
|
||||
const { round } = Math;
|
||||
let r, g, b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = this.#hueToRgb(p, q, h + 1 / 3);
|
||||
g = this.#hueToRgb(p, q, h);
|
||||
b = this.#hueToRgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return [round(r * 255), round(g * 255), round(b * 255)];
|
||||
}
|
||||
|
||||
/**
|
||||
* From ZenGradientGenerator.mjs
|
||||
* Inverse of hslToRgb
|
||||
* Converts an RGB color value to HSL. Conversion formula
|
||||
* adapted from https://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h, s, and l in the set [0, 1].
|
||||
*
|
||||
* @param {number} r The red value
|
||||
* @param {number} g The green value
|
||||
* @param {number} b The blue value
|
||||
* @returns {Array} The HSL representation
|
||||
*/
|
||||
#rgbToHsl(r, g, b) {
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
let max = Math.max(r, g, b);
|
||||
let min = Math.min(r, g, b);
|
||||
let d = max - min;
|
||||
let h;
|
||||
if (d === 0) {
|
||||
h = 0;
|
||||
} else if (max === r) {
|
||||
h = ((g - b) / d) % 6;
|
||||
} else if (max === g) {
|
||||
h = (b - r) / d + 2;
|
||||
} else if (max === b) {
|
||||
h = (r - g) / d + 4;
|
||||
}
|
||||
let l = (min + max) / 2;
|
||||
let s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
||||
return [h * 60, s, l];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles DOM events for the actor. Applies boost settings when a document
|
||||
* element is inserted.
|
||||
*
|
||||
* @param {Event} event - The DOM event to handle.
|
||||
*/
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "unload":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
this.disableZapMode();
|
||||
}
|
||||
this.#removeEventListeners();
|
||||
break;
|
||||
case "DOMWindowCreated":
|
||||
this.#applyBoostForPageIfAvailable();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleZapEvent(event) {
|
||||
if (ZenBoostsChild.ALL_EVENTS_SET.has(event.type)) {
|
||||
this.#overlay.handleEvent(
|
||||
event,
|
||||
ZenBoostsChild.PREVENTABLE_SET.has(event.type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary event listeners to the document
|
||||
* to prevent content interactions
|
||||
*/
|
||||
#addEventListeners() {
|
||||
this._handleZapEvent = this.handleZapEvent.bind(this);
|
||||
this._disableZapMode = this.disableZapMode.bind(this);
|
||||
|
||||
for (let event of ZenBoostsChild.OVERLAY_EVENTS) {
|
||||
this.document.addEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
|
||||
for (let event of ZenBoostsChild.PREVENTABLE_EVENTS) {
|
||||
this.document.addEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
this.#preventableEventsAdded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the event listeners from the document
|
||||
*/
|
||||
#removeEventListeners() {
|
||||
for (let event of ZenBoostsChild.OVERLAY_EVENTS) {
|
||||
this.document.removeEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
|
||||
if (this.#preventableEventsAdded) {
|
||||
for (let event of ZenBoostsChild.PREVENTABLE_EVENTS) {
|
||||
this.document.removeEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
}
|
||||
this.#preventableEventsAdded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles messages received from the parent actor.
|
||||
*
|
||||
* @param {object} message - The message object containing name and data.
|
||||
*/
|
||||
async receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "ZenBoost:BoostDataUpdated": {
|
||||
const { unloadStyles = false } = message.data || {};
|
||||
this.#applyBoostForPageIfAvailable(unloadStyles);
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:DisableZapMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
this.disableZapMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:DisablePickerMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.PICKER) {
|
||||
this.disablePickerMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:ToggleZapMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
this.#startZappingOverlay();
|
||||
} else if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
this.disableZapMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:TogglePickerMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
this.#startPickingOverlay();
|
||||
} else if (this.#currentState === ZenBoostsChild.STATES.PICKER) {
|
||||
this.disablePickerMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:ZapModeEnabled":
|
||||
return this.#currentState === ZenBoostsChild.STATES.ZAP;
|
||||
case "ZenBoost:ZapModeAny": {
|
||||
const { boostData } = (await this.getWebsiteBoost())?.boostEntry || {};
|
||||
return !!boostData?.zapSelectors?.length;
|
||||
}
|
||||
case "ZenBoost:SelectorPickerModeEnabled":
|
||||
return this.#currentState === ZenBoostsChild.STATES.PICKER;
|
||||
case "ZenBoost:OpenInspector":
|
||||
this.sendAsyncMessage("ZenBoost:OpenInspector");
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* From ZenGradientGenerator.mjs
|
||||
* Helper function for hslToRgb conversion
|
||||
*
|
||||
* @param {number} p
|
||||
* @param {number} q
|
||||
* @param {number} t
|
||||
*/
|
||||
#hueToRgb(p, q, t) {
|
||||
if (t < 0) {
|
||||
t += 1;
|
||||
}
|
||||
if (t > 1) {
|
||||
t -= 1;
|
||||
}
|
||||
if (t < 1 / 6) {
|
||||
return p + (q - p) * 6 * t;
|
||||
}
|
||||
if (t < 1 / 2) {
|
||||
return q;
|
||||
}
|
||||
if (t < 2 / 3) {
|
||||
return p + (q - p) * (2 / 3 - t) * 6;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aquires the boost data for this website
|
||||
*
|
||||
* @returns {object} Boost data for the current website
|
||||
*/
|
||||
getWebsiteBoost() {
|
||||
const domain = this.browsingContext.topWindow?.location?.host;
|
||||
if (!domain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.sendQuery("ZenBoost:GetBoostForDomain", domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the boost settings for the current page if available.
|
||||
*
|
||||
* @param {boolean} unloadStyles - Indicates whether to unload styles.
|
||||
*/
|
||||
async #applyBoostForPageIfAvailable(unloadStyles = false) {
|
||||
const browsingContext = this.browsingContext;
|
||||
|
||||
// Prevent applying boosts to iframes or non-top-level browsing contexts.
|
||||
// It makes the tab crash if we try to load stylesheets into an iframe's
|
||||
if (!browsingContext || browsingContext.parent !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const boost = await this.getWebsiteBoost();
|
||||
|
||||
if (unloadStyles) {
|
||||
this.#unloadCurrentStyleSheet();
|
||||
}
|
||||
|
||||
if (boost) {
|
||||
const { boostData } = boost.boostEntry;
|
||||
if (boost.styleSheet) {
|
||||
this.#loadStyleSheet(boost.styleSheet);
|
||||
}
|
||||
|
||||
browsingContext.isZenBoostsInverted = boostData.smartInvert;
|
||||
if (boostData.enableColorBoost) {
|
||||
if (boostData.autoTheme) {
|
||||
// Workspace color is converted to the HSL color space
|
||||
let primaryGradientColor = boost.workspaceGradient[0]?.c ?? [
|
||||
0, 0, 0.6,
|
||||
];
|
||||
boost.workspaceGradient.forEach(color => {
|
||||
if (color.isPrimary) {
|
||||
primaryGradientColor = this.#rgbToHsl(
|
||||
color.c[0],
|
||||
color.c[1],
|
||||
color.c[2]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Workspace color is converted back to rgb
|
||||
// using the same modifiers as the color above
|
||||
primaryGradientColor = this.#hslToRgb(
|
||||
primaryGradientColor[0] / 360,
|
||||
primaryGradientColor[1] * (1 - boostData.saturation),
|
||||
0.1 + primaryGradientColor[2] * 0.9 * boostData.brightness
|
||||
);
|
||||
|
||||
const rgbColor = primaryGradientColor;
|
||||
const nsColor = this.#rgbToNSColor(
|
||||
rgbColor,
|
||||
(1 - boostData.contrast) * 255
|
||||
);
|
||||
browsingContext.zenBoostsData = nsColor;
|
||||
} else {
|
||||
let colorWheelColor = this.#hslToRgb(
|
||||
boostData.dotAngleDeg / 360,
|
||||
/* already is [0, 1] */
|
||||
boostData.dotDistance * (1 - boostData.saturation),
|
||||
/* lightness range from [0.1, 0.9] */
|
||||
0.1 + boostData.dotDistance * 0.8 * boostData.brightness
|
||||
);
|
||||
|
||||
const rgbColor = colorWheelColor;
|
||||
const nsColor = this.#rgbToNSColor(
|
||||
rgbColor,
|
||||
(1 - boostData.contrast) * 255
|
||||
);
|
||||
browsingContext.zenBoostsData = nsColor;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
browsingContext.isZenBoostsInverted = false;
|
||||
}
|
||||
browsingContext.zenBoostsData = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given stylesheet into the website
|
||||
*
|
||||
* @param {object} styleSheet The stylesheet
|
||||
*/
|
||||
#loadStyleSheet(styleSheet) {
|
||||
const browsingContext = this.browsingContext;
|
||||
styleSheet.uri = Services.io.newURI(styleSheet.uri);
|
||||
|
||||
if (this.#currentSheet?.uuid !== styleSheet.uuid) {
|
||||
if (this.#currentSheet) {
|
||||
this.#unloadCurrentStyleSheet();
|
||||
}
|
||||
browsingContext.window.windowGlobalChild.browsingContext.window.windowUtils.loadSheet(
|
||||
styleSheet.uri,
|
||||
AGENT_SHEET
|
||||
);
|
||||
this.#currentSheet = styleSheet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the currently loaded stylesheet
|
||||
*/
|
||||
#unloadCurrentStyleSheet() {
|
||||
const browsingContext = this.browsingContext;
|
||||
if (this.#currentSheet && browsingContext) {
|
||||
browsingContext.window.windowGlobalChild.browsingContext.window.windowUtils.removeSheet(
|
||||
this.#currentSheet.uri,
|
||||
AGENT_SHEET
|
||||
);
|
||||
this.#currentSheet = null;
|
||||
}
|
||||
}
|
||||
|
||||
async #startZappingOverlay() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.ZAP;
|
||||
|
||||
this.#overlay = new lazy.ZapOverlay(this.document, this);
|
||||
this.#overlay.initialize();
|
||||
|
||||
this.#addEventListeners();
|
||||
this.sendNotify("zap-state-update");
|
||||
}
|
||||
|
||||
async #startPickingOverlay() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.PICKER) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.PICKER;
|
||||
|
||||
this.#overlay = new lazy.SelectorComponent(
|
||||
this.document,
|
||||
this,
|
||||
[], // No additional IDs needed
|
||||
this.onPickerSelection.bind(this)
|
||||
);
|
||||
|
||||
this.#overlay.initialize();
|
||||
|
||||
this.#addEventListeners();
|
||||
this.sendNotify("selector-picker-state-update", "onenable");
|
||||
}
|
||||
|
||||
onPickerSelection(cssSelector) {
|
||||
this.sendNotify("selector-picker-picked", cssSelector);
|
||||
}
|
||||
|
||||
addZapSelector(selector) {
|
||||
const domain = this.browsingContext.topWindow?.location?.host;
|
||||
this.sendQuery("ZenBoost:ZapSelector", {
|
||||
action: "add",
|
||||
selector,
|
||||
domain,
|
||||
});
|
||||
}
|
||||
|
||||
removeZapSelector(selector) {
|
||||
const domain = this.browsingContext.topWindow?.location?.host;
|
||||
this.sendQuery("ZenBoost:ZapSelector", {
|
||||
action: "remove",
|
||||
selector,
|
||||
domain,
|
||||
});
|
||||
}
|
||||
|
||||
async tempShowZappedElement(selector) {
|
||||
this.document.querySelectorAll(selector).forEach(element => {
|
||||
element.setAttribute("zen-zap-unhide", "true");
|
||||
});
|
||||
|
||||
if (!this.#zappedElementsTempShown.includes(selector)) {
|
||||
this.#zappedElementsTempShown.push(selector);
|
||||
}
|
||||
}
|
||||
|
||||
async tempHideZappedElement() {
|
||||
this.#zappedElementsTempShown.forEach(selector => {
|
||||
this.document.querySelectorAll(selector).forEach(element => {
|
||||
element.removeAttribute("zen-zap-unhide");
|
||||
});
|
||||
});
|
||||
|
||||
this.#zappedElementsTempShown = [];
|
||||
}
|
||||
|
||||
disableZapMode() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.NONE;
|
||||
|
||||
this.#overlay?.tearDown();
|
||||
this.#overlay = null;
|
||||
|
||||
this.#removeEventListeners();
|
||||
this.sendNotify("zap-state-update");
|
||||
}
|
||||
|
||||
disablePickerMode() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.NONE;
|
||||
|
||||
this.#overlay?.tearDown();
|
||||
this.#overlay = null;
|
||||
|
||||
this.#removeEventListeners();
|
||||
this.sendNotify("selector-picker-state-update", "ondisable");
|
||||
}
|
||||
|
||||
sendNotify(topic, msg = null) {
|
||||
this.sendAsyncMessage("ZenBoost:Notify", { topic, msg });
|
||||
}
|
||||
}
|
||||
185
src/zen/boosts/actors/ZenBoostsParent.sys.mjs
Normal file
185
src/zen/boosts/actors/ZenBoostsParent.sys.mjs
Normal file
@@ -0,0 +1,185 @@
|
||||
// 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/.
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
gZenBoostsManager: "resource:///modules/zen/boosts/ZenBoostsManager.sys.mjs",
|
||||
});
|
||||
|
||||
export class ZenBoostsParent extends JSWindowActorParent {
|
||||
static OBSERVERS = [
|
||||
"zen-boosts-update",
|
||||
"zen-space-gradient-update",
|
||||
"zen-boosts-disable-zap",
|
||||
"zen-boosts-disable-picker",
|
||||
];
|
||||
|
||||
// Topics the content child is allowed to forward to the observer service.
|
||||
// Anything outside this set is rejected to prevent content-triggered
|
||||
// notifications on unrelated chrome observers.
|
||||
static ALLOWED_NOTIFY_TOPICS = new Set([
|
||||
"zap-state-update",
|
||||
"zap-list-update",
|
||||
"selector-picker-state-update",
|
||||
"selector-picker-picked",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Creates a new ZenBoostsParent actor instance and sets up an observer
|
||||
* for boost update notifications.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._observe = this.observe.bind(this);
|
||||
ZenBoostsParent.OBSERVERS.forEach(observe => {
|
||||
Services.obs.addObserver(this._observe, observe);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the actor is destroyed. Cleans up the observer.
|
||||
*/
|
||||
didDestroy() {
|
||||
ZenBoostsParent.OBSERVERS.forEach(observe => {
|
||||
Services.obs.removeObserver(this._observe, observe);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer callback that handles boost update notifications.
|
||||
* Sends a message to child actors when boosts are updated.
|
||||
*
|
||||
* @param {object} subject - The subject of the notification.
|
||||
* @param {string} topic - The topic of the notification.
|
||||
*/
|
||||
observe(subject, topic) {
|
||||
switch (topic) {
|
||||
case "zen-boosts-update":
|
||||
case "zen-space-gradient-update":
|
||||
this.sendAsyncMessage("ZenBoost:BoostDataUpdated", {
|
||||
unloadStyles: true,
|
||||
});
|
||||
break;
|
||||
case "zen-boosts-disable-zap":
|
||||
this.sendAsyncMessage("ZenBoost:DisableZapMode");
|
||||
break;
|
||||
case "zen-boosts-disable-picker":
|
||||
this.sendAsyncMessage("ZenBoost:DisablePickerMode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles messages received from child actors.
|
||||
* Retrieves boost data for a domain when requested.
|
||||
*
|
||||
* @param {object} message - The message object containing name and data.
|
||||
* @returns {Promise<object | null>} A promise that resolves to the boost data or null.
|
||||
*/
|
||||
async receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "ZenBoost:OpenInspector": {
|
||||
const { require } = ChromeUtils.importESModule(
|
||||
"resource://devtools/shared/loader/Loader.sys.mjs"
|
||||
);
|
||||
|
||||
const { gDevTools } = require("devtools/client/framework/devtools");
|
||||
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let tab = win.gBrowser.selectedTab;
|
||||
|
||||
let toolbox = gDevTools.getToolboxForTab(tab);
|
||||
|
||||
if (toolbox) {
|
||||
await gDevTools.closeToolboxForTab(tab);
|
||||
} else {
|
||||
await gDevTools.showToolboxForTab(tab, "inspector");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:Notify": {
|
||||
const { topic, msg } = message.data ?? {};
|
||||
if (!ZenBoostsParent.ALLOWED_NOTIFY_TOPICS.has(topic)) {
|
||||
console.warn(
|
||||
`[ZenBoostsParent]: Rejected notify for disallowed topic: ${topic}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
Services.obs.notifyObservers(null, topic, msg);
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:ZapSelector": {
|
||||
const data = message.data;
|
||||
|
||||
if (!data.action) {
|
||||
break;
|
||||
}
|
||||
if (!data.selector) {
|
||||
break;
|
||||
}
|
||||
if (!data.domain) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.action == "add") {
|
||||
lazy.gZenBoostsManager.addZapSelectorToActive(
|
||||
data.selector,
|
||||
data.domain
|
||||
);
|
||||
} else if (data.action == "remove") {
|
||||
lazy.gZenBoostsManager.removeZapSelectorToActive(
|
||||
data.selector,
|
||||
data.domain
|
||||
);
|
||||
} else if (data.action == "clear") {
|
||||
lazy.gZenBoostsManager.clearZapSelectorsForActive(data.domain);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:GetBoostForDomain": {
|
||||
const domain = message.data;
|
||||
const embedder = this.browsingContext.top.embedderElement;
|
||||
|
||||
if (!embedder || !domain) {
|
||||
break;
|
||||
}
|
||||
|
||||
const exists = lazy.gZenBoostsManager.registeredBoostForDomain(domain);
|
||||
if (!exists) {
|
||||
break;
|
||||
}
|
||||
|
||||
const boost = lazy.gZenBoostsManager.loadActiveBoostFromStore(domain);
|
||||
let workspaceGradient = [];
|
||||
if (boost.boostEntry.boostData.autoTheme) {
|
||||
const currentWorkspace =
|
||||
await this.browsingContext.topChromeWindow.gZenWorkspaces.getActiveWorkspace();
|
||||
workspaceGradient = currentWorkspace.theme.gradientColors;
|
||||
}
|
||||
|
||||
const styleData =
|
||||
await lazy.gZenBoostsManager.getStyleSheetForBoost(domain);
|
||||
|
||||
return {
|
||||
...boost,
|
||||
workspaceGradient,
|
||||
styleSheet: styleData
|
||||
? {
|
||||
uuid: styleData.uuid,
|
||||
uri: styleData.uri.spec,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
console.warn(`[ZenBoostsParent]: Unknown message: ${message.name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
14
src/zen/boosts/jar.inc.mn
Normal file
14
src/zen/boosts/jar.inc.mn
Normal file
@@ -0,0 +1,14 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
content/browser/zen-styles/zen-boosts.css (../../zen/boosts/zen-boosts.css)
|
||||
content/browser/zen-styles/content/zen-zap.css (../../zen/boosts/zen-zap.css)
|
||||
content/browser/zen-styles/content/zen-selector.css (../../zen/boosts/zen-selector.css)
|
||||
content/browser/zen-styles/zen-advanced-color-options.css (../../zen/boosts/zen-advanced-color-options.css)
|
||||
|
||||
# Windows
|
||||
* content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.inc.xhtml)
|
||||
|
||||
# Images
|
||||
content/browser/zen-images/boost-indicator.svg (../../zen/images/boost-indicator.svg)
|
||||
28
src/zen/boosts/moz.build
Normal file
28
src/zen/boosts/moz.build
Normal file
@@ -0,0 +1,28 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_JS_MODULES.zen.boosts += [
|
||||
"ZenBoostsEditor.mjs",
|
||||
"ZenBoostsManager.sys.mjs",
|
||||
"ZenBoostStyles.sys.mjs",
|
||||
"ZenSelectorComponent.sys.mjs",
|
||||
"ZenZapDissolve.sys.mjs",
|
||||
"ZenZapOverlayChild.sys.mjs",
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.actors += [
|
||||
"actors/ZenBoostsChild.sys.mjs",
|
||||
"actors/ZenBoostsParent.sys.mjs",
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
"nsZenBoostsBackend.h",
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
"nsZenBCOverrides.cpp",
|
||||
"nsZenBoostsBackend.cpp",
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
||||
83
src/zen/boosts/nsZenBCOverrides.cpp
Normal file
83
src/zen/boosts/nsZenBCOverrides.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsZenBoostsBackend.h"
|
||||
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
#include "mozilla/MediaFeatureChange.h"
|
||||
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
|
||||
#define MARK_MEDIA_FEATURE_CHANGED(_pc) \
|
||||
(_pc)->MediaFeatureValuesChanged( \
|
||||
{mozilla::RestyleHint::RecascadeSubtree(), NS_STYLE_HINT_VISUAL, \
|
||||
mozilla::MediaFeatureChangeReason::PreferenceChange}, \
|
||||
mozilla::MediaFeatureChangePropagation::All);
|
||||
|
||||
#define TRIGGER_PRES_CONTEXT_RESTYLE() \
|
||||
WalkPresContexts( \
|
||||
[&](nsPresContext* aPc) { MARK_MEDIA_FEATURE_CHANGED(aPc); });
|
||||
|
||||
using BrowsingContext = mozilla::dom::BrowsingContext;
|
||||
|
||||
template <typename Callback>
|
||||
void BrowsingContext::WalkPresContexts(Callback&& aCallback) {
|
||||
PreOrderWalk([&](BrowsingContext* aContext) {
|
||||
if (nsIDocShell* shell = aContext->GetDocShell()) {
|
||||
if (RefPtr pc = shell->GetPresContext()) {
|
||||
aCallback(pc.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
* @param aOldValue The previous value of the boost data.
|
||||
*/
|
||||
void BrowsingContext::DidSet(FieldIndex<IDX_ZenBoostsData>,
|
||||
ZenBoostData aOldValue) {
|
||||
MOZ_ASSERT(IsTop());
|
||||
if (ZenBoostsData() == aOldValue) {
|
||||
return;
|
||||
}
|
||||
RefreshBoostCacheIfMatchesCurrent(this);
|
||||
PresContextAffectingFieldChanged();
|
||||
TRIGGER_PRES_CONTEXT_RESTYLE();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called when the IsZenBoostsInverted field is set on a browsing
|
||||
* context. Triggers a restyle if the value has changed.
|
||||
* @param aOldValue The previous value of the IsZenBoostsInverted flag.
|
||||
*/
|
||||
void BrowsingContext::DidSet(FieldIndex<IDX_IsZenBoostsInverted>,
|
||||
bool aOldValue) {
|
||||
MOZ_ASSERT(IsTop());
|
||||
if (IsZenBoostsInverted() == aOldValue) {
|
||||
return;
|
||||
}
|
||||
RefreshBoostCacheIfMatchesCurrent(this);
|
||||
PresContextAffectingFieldChanged();
|
||||
TRIGGER_PRES_CONTEXT_RESTYLE();
|
||||
}
|
||||
382
src/zen/boosts/nsZenBoostsBackend.cpp
Normal file
382
src/zen/boosts/nsZenBoostsBackend.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "nsZenBoostsBackend.h"
|
||||
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
#include "mozilla/ServoStyleConsts.h"
|
||||
#include "mozilla/ServoStyleConstsInlines.h"
|
||||
#include "mozilla/MediaFeatureChange.h"
|
||||
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
|
||||
#include "mozilla/StaticPrefs_zen.h"
|
||||
|
||||
// Lower bound applied to inverted channels so that pure white doesn't invert
|
||||
// all the way to pure black, which makes inverted pages feel too dark.
|
||||
#define INVERT_CHANNEL_FLOOR() \
|
||||
(mozilla::StaticPrefs::zen_boosts_invert_channel_floor_AtStartup())
|
||||
|
||||
#define SHOULD_APPLY_BOOSTS_TO_ANONYMOUS_CONTENT() \
|
||||
(!mozilla::StaticPrefs::zen_boosts_disable_on_anonymous_content_AtStartup())
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
# define ZEN_HOT_FUNCTION __attribute__((hot))
|
||||
#else
|
||||
# define ZEN_HOT_FUNCTION
|
||||
#endif
|
||||
|
||||
// It's a bit of a hacky solution, but instead of using alpha as what it is
|
||||
// (opacity), we use it to store contrast information for now.
|
||||
// We do this primarily to avoid having to deal with WebIDL structs and
|
||||
// serialization/deserialization between parent and content processes.
|
||||
#define NS_GET_CONTRAST(_c) NS_GET_A(_c)
|
||||
|
||||
namespace zen {
|
||||
|
||||
nsZenAccentOklab nsZenBoostsBackend::mCachedAccent{0};
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief Converts an sRGB color component to linear space.
|
||||
* @param c The sRGB color component value (0.0 to 1.0).
|
||||
* @return The linear color component value.
|
||||
*/
|
||||
static inline float srgbToLinear(float c) {
|
||||
return c <= 0.04045f ? c * (1.0f / 12.92f)
|
||||
: std::pow((c + 0.055f) * (1.0f / 1.055f), 2.4f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a linear color component to sRGB space.
|
||||
* @param c The linear color component value.
|
||||
* @return The sRGB color component value (0.0 to 1.0).
|
||||
*/
|
||||
static inline float linearToSrgb(float c) {
|
||||
c = std::max(0.0f, c);
|
||||
return c <= 0.0031308f ? 12.92f * c
|
||||
: 1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Fast approximation of the cube root of a number.
|
||||
* @param x The input value.
|
||||
* @return The approximate cube root of the input value.
|
||||
*/
|
||||
static inline float fastCbrt(float x) {
|
||||
if (x == 0.0f) return 0.0f;
|
||||
float a = std::abs(x);
|
||||
union {
|
||||
float f;
|
||||
uint32_t i;
|
||||
} u = {a};
|
||||
u.i = u.i / 3 + 0x2a504a2e;
|
||||
float y = u.f;
|
||||
y = (2.0f * y + a / (y * y)) * (1.0f / 3.0f);
|
||||
y = (2.0f * y + a / (y * y)) * (1.0f / 3.0f);
|
||||
return x < 0.0f ? -y : y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Precomputes the Oklab values for a given accent color. This allows us
|
||||
* to efficiently apply the accent color as a filter to other colors without
|
||||
* having to convert the accent color from sRGB to Oklab space on every filter
|
||||
* operation.
|
||||
* @param aAccentColor The accent color in nscolor format.
|
||||
* @return A struct containing the precomputed Oklab values and contrast factor
|
||||
* for the accent color.
|
||||
*/
|
||||
ZEN_HOT_FUNCTION
|
||||
inline static auto zenPrecomputeAccent(nscolor aAccentColor) {
|
||||
constexpr float inv255 = 1.0f / 255.0f;
|
||||
|
||||
const float r = NS_GET_R(aAccentColor) * inv255;
|
||||
const float g = NS_GET_G(aAccentColor) * inv255;
|
||||
const float b = NS_GET_B(aAccentColor) * inv255;
|
||||
|
||||
const float lr = srgbToLinear(r);
|
||||
const float lg = srgbToLinear(g);
|
||||
const float lb = srgbToLinear(b);
|
||||
|
||||
const float l_ =
|
||||
fastCbrt(0.4122214708f * lr + 0.5363325363f * lg + 0.0514459929f * lb);
|
||||
const float m_ =
|
||||
fastCbrt(0.2119034982f * lr + 0.6806995451f * lg + 0.1073969566f * lb);
|
||||
const float s_ =
|
||||
fastCbrt(0.0883024619f * lr + 0.2817188376f * lg + 0.6299787005f * lb);
|
||||
|
||||
return nsZenAccentOklab{
|
||||
.accentNS = aAccentColor,
|
||||
.accL = 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
|
||||
.accA = 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
|
||||
.accB = 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
|
||||
.contrastFactor = NS_GET_CONTRAST(aAccentColor) * inv255,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a color filter to transform an original color toward an accent
|
||||
* color. Preserves the original color's perceived luminance while shifting
|
||||
* hue/chroma toward the accent. Uses the alpha channel of the accent color to
|
||||
* store contrast information.
|
||||
* @param aOriginalColor The original color to filter.
|
||||
* @param aAccentColor The accent color to filter toward (alpha channel contains
|
||||
* contrast value).
|
||||
* @return The filtered color with transformations applied.
|
||||
*/
|
||||
[[nodiscard]] ZEN_HOT_FUNCTION static inline nscolor zenFilterColorChannel(
|
||||
nscolor aOriginalColor, const nsZenAccentOklab& aAccent) {
|
||||
const uint8_t oL = NS_GET_A(aOriginalColor);
|
||||
const uint8_t contrast = NS_GET_CONTRAST(aAccent.accentNS);
|
||||
if (oL == 0) {
|
||||
return aOriginalColor;
|
||||
}
|
||||
|
||||
constexpr float inv255 = 1.0f / 255.0f;
|
||||
const float blendFactor = contrast * inv255;
|
||||
|
||||
// sRGB -> linear
|
||||
const float lr = srgbToLinear(NS_GET_R(aOriginalColor) * inv255);
|
||||
const float lg = srgbToLinear(NS_GET_G(aOriginalColor) * inv255);
|
||||
const float lb = srgbToLinear(NS_GET_B(aOriginalColor) * inv255);
|
||||
|
||||
// Linear RGB -> LMS -> cube root -> Oklab (fused)
|
||||
const float l_ =
|
||||
fastCbrt(0.4122214708f * lr + 0.5363325363f * lg + 0.0514459929f * lb);
|
||||
const float m_ =
|
||||
fastCbrt(0.2119034982f * lr + 0.6806995451f * lg + 0.1073969566f * lb);
|
||||
const float s_ =
|
||||
fastCbrt(0.0883024619f * lr + 0.2817188376f * lg + 0.6299787005f * lb);
|
||||
|
||||
const float origL =
|
||||
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_;
|
||||
const float origA =
|
||||
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_;
|
||||
const float origB =
|
||||
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_;
|
||||
|
||||
// Blend chroma toward accent
|
||||
const float bA = origA + (aAccent.accA - origA) * blendFactor;
|
||||
const float bB = origB + (aAccent.accB - origB) * blendFactor;
|
||||
|
||||
// Luminance: at low contrast stay near the original, the higher the contrast,
|
||||
// the more we shift toward the accent luminance, but we never go fully to
|
||||
// the accent luminance to preserve some of the original color's character.
|
||||
const float lumDelta = aAccent.accL - origL;
|
||||
const float fL =
|
||||
origL + lumDelta * (blendFactor * aAccent.contrastFactor * 0.5f);
|
||||
|
||||
// Rotate hue in the Oklab a/b plane. Direction follows the luminance shift:
|
||||
// pushing darker rotates clockwise ("right"), pushing lighter rotates the
|
||||
// other way. Magnitude scales with blend strength so subtle accents stay
|
||||
// subtle.
|
||||
const float rotAngle = (lumDelta > 0.0f ? -1.0f : 1.0f) * blendFactor *
|
||||
aAccent.contrastFactor * 0.25f;
|
||||
const float cosR = std::cos(rotAngle);
|
||||
const float sinR = std::sin(rotAngle);
|
||||
const float fA = bA * cosR - bB * sinR;
|
||||
const float fB = bA * sinR + bB * cosR;
|
||||
|
||||
// Oklab -> LMS
|
||||
const float fl_ = fL + 0.3963377774f * fA + 0.2158037573f * fB;
|
||||
const float fm_ = fL - 0.1055613458f * fA - 0.0638541728f * fB;
|
||||
const float fs_ = fL - 0.0894841775f * fA - 1.2914855480f * fB;
|
||||
|
||||
// Cube
|
||||
const float fl = fl_ * fl_ * fl_;
|
||||
const float fm = fm_ * fm_ * fm_;
|
||||
const float fs = fs_ * fs_ * fs_;
|
||||
|
||||
// LMS -> linear RGB
|
||||
const float rF = 4.0767416621f * fl - 3.3077115913f * fm + 0.2309699292f * fs;
|
||||
const float gF =
|
||||
-1.2684380046f * fl + 2.6097574011f * fm - 0.3413193965f * fs;
|
||||
const float bF =
|
||||
-0.0041960863f * fl - 0.7034186147f * fm + 1.7076147010f * fs;
|
||||
|
||||
// Linear -> sRGB -> uint8
|
||||
return NS_RGBA(static_cast<uint8_t>(std::clamp(
|
||||
linearToSrgb(rF) * 255.0f + 0.5f, 0.0f, 255.0f)),
|
||||
static_cast<uint8_t>(std::clamp(
|
||||
linearToSrgb(gF) * 255.0f + 0.5f, 0.0f, 255.0f)),
|
||||
static_cast<uint8_t>(std::clamp(
|
||||
linearToSrgb(bF) * 255.0f + 0.5f, 0.0f, 255.0f)),
|
||||
oL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inverts a color by inverting each RGB channel while preserving
|
||||
* perceived luminance. This is done by inverting the color and then shifting it
|
||||
* based on the sum of the inverted channels.
|
||||
* @param aColor The color to invert.
|
||||
* @return The inverted color with luminance preservation.
|
||||
*/
|
||||
ZEN_HOT_FUNCTION
|
||||
inline static nscolor zenInvertColorChannel(nscolor aColor) {
|
||||
const auto r = NS_GET_R(aColor);
|
||||
const auto g = NS_GET_G(aColor);
|
||||
const auto b = NS_GET_B(aColor);
|
||||
const auto a = NS_GET_A(aColor);
|
||||
if (a == 0) {
|
||||
// Skip processing fully transparent colors since they won't be visible and
|
||||
// we want to avoid unnecessary computations.
|
||||
return aColor;
|
||||
}
|
||||
|
||||
const auto rInv = 255 - r;
|
||||
const auto gInv = 255 - g;
|
||||
const auto bInv = 255 - b;
|
||||
|
||||
const auto max = std::max({rInv, gInv, bInv});
|
||||
const auto min = std::min({rInv, gInv, bInv});
|
||||
const auto sum = max + min;
|
||||
|
||||
const auto rShifted = sum - rInv;
|
||||
const auto gShifted = sum - gInv;
|
||||
const auto bShifted = sum - bInv;
|
||||
|
||||
// Compress the channel range into [FLOOR, 255] so dark inversions are
|
||||
// lifted while light inversions are left untouched. This preserves hue
|
||||
// since all three channels are scaled by the same factor.
|
||||
const auto channelFloor = INVERT_CHANNEL_FLOOR();
|
||||
const uint32_t range = 255 - channelFloor;
|
||||
const auto lift = [channelFloor, range](uint8_t c) -> uint8_t {
|
||||
return static_cast<uint8_t>(channelFloor + (c * range) / 255);
|
||||
};
|
||||
|
||||
return NS_RGBA(lift(rShifted), lift(gShifted), lift(bShifted), a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(
|
||||
ZenBoostData* aData, bool* aIsInverted,
|
||||
nsPresContext* aPresContext = nullptr) {
|
||||
auto zenBoosts = nsZenBoostsBackend::GetInstance();
|
||||
if (!zenBoosts || (zenBoosts->mCurrentFrameIsAnonymousContent &&
|
||||
!SHOULD_APPLY_BOOSTS_TO_ANONYMOUS_CONTENT())) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
browsingContext = browsingContext->Top();
|
||||
*aData = browsingContext->ZenBoostsData();
|
||||
*aIsInverted = browsingContext->IsZenBoostsInverted();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static mozilla::StaticAutoPtr<nsZenBoostsBackend> sZenBoostsBackend;
|
||||
|
||||
auto nsZenBoostsBackend::GetInstance() -> nsZenBoostsBackend* {
|
||||
if (!XRE_IsContentProcess()) {
|
||||
// Zen boosts are only supported in content, so if we're in the parent
|
||||
// process, just return null.
|
||||
return nullptr;
|
||||
}
|
||||
if (!sZenBoostsBackend) {
|
||||
sZenBoostsBackend = new nsZenBoostsBackend();
|
||||
mozilla::ClearOnShutdown(&sZenBoostsBackend);
|
||||
}
|
||||
return sZenBoostsBackend.get();
|
||||
}
|
||||
|
||||
auto nsZenBoostsBackend::onPresShellEntered(mozilla::dom::Document* aDocument)
|
||||
-> void {
|
||||
if (auto displayDoc = aDocument->GetDisplayDocument()) {
|
||||
onPresShellEntered(displayDoc);
|
||||
return;
|
||||
}
|
||||
// Note that aDocument can be null when entering anonymous content frames.
|
||||
// We explicitly do this to prevent applying boosts to anonymous content, such
|
||||
// as devtools or screenshots.
|
||||
mozilla::dom::BrowsingContext* browsingContext =
|
||||
aDocument ? aDocument->GetBrowsingContext() : nullptr;
|
||||
if (!browsingContext) {
|
||||
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
|
||||
nsZenBoostsBackend::FilterColorFromPresContext(nscolor aColor,
|
||||
nsPresContext* aPresContext)
|
||||
-> nscolor {
|
||||
ZenBoostData accentNS = 0;
|
||||
bool invertColors = false;
|
||||
GetZenBoostsDataFromBrowsingContext(&accentNS, &invertColors, aPresContext);
|
||||
if (accentNS) {
|
||||
if (mCachedAccent.accentNS != accentNS) {
|
||||
mCachedAccent = zenPrecomputeAccent(accentNS);
|
||||
}
|
||||
// Apply a filter-like tint:
|
||||
// - Preserve the original color's perceived luminance
|
||||
// - Map hue/chroma toward the accent by scaling the accent's RGB
|
||||
// to match the original luminance
|
||||
// - Keep the original alpha
|
||||
// Convert both colors to nscolor to access channels
|
||||
aColor = zenFilterColorChannel(aColor, mCachedAccent);
|
||||
}
|
||||
if (invertColors) {
|
||||
aColor = zenInvertColorChannel(aColor);
|
||||
}
|
||||
return aColor;
|
||||
}
|
||||
|
||||
[[nodiscard]] ZEN_HOT_FUNCTION auto nsZenBoostsBackend::ResolveStyleColor(
|
||||
mozilla::StyleAbsoluteColor aColor) -> mozilla::StyleAbsoluteColor {
|
||||
const auto resultColor = FilterColorFromPresContext(aColor.ToColor());
|
||||
return mozilla::StyleAbsoluteColor::FromColor(resultColor);
|
||||
}
|
||||
|
||||
[[nodiscard]] ZEN_HOT_FUNCTION auto nsZenBoostsBackend::ResolveStyleColor(
|
||||
nscolor aColor) -> nscolor {
|
||||
if (NS_GET_A(aColor) == 0) {
|
||||
// Skip processing fully transparent colors since they won't be visible and
|
||||
// we want to avoid unnecessary computations. This also prevents issues with
|
||||
// using the alpha channel for contrast information in the accent color.
|
||||
return aColor;
|
||||
}
|
||||
return FilterColorFromPresContext(aColor);
|
||||
}
|
||||
|
||||
} // namespace zen
|
||||
106
src/zen/boosts/nsZenBoostsBackend.h
Normal file
106
src/zen/boosts/nsZenBoostsBackend.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_ZenBoostsBackend_h_
|
||||
#define mozilla_ZenBoostsBackend_h_
|
||||
|
||||
#include "nsColor.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#define ZEN_BOOSTS_BACKEND_CONTRACTID "@mozilla.org/zen/boosts-backend;1"
|
||||
|
||||
using ZenBoostData = nscolor; // For now, Zen boosts data is just a color.
|
||||
|
||||
namespace zen {
|
||||
|
||||
struct nsZenAccentOklab {
|
||||
nscolor accentNS;
|
||||
float accL, accA, accB;
|
||||
float contrastFactor;
|
||||
};
|
||||
|
||||
class nsZenBoostsBackend final {
|
||||
public:
|
||||
explicit nsZenBoostsBackend() = default;
|
||||
~nsZenBoostsBackend() = default;
|
||||
|
||||
/**
|
||||
* Indicates whether the current frame being rendered is for anonymous
|
||||
* content.
|
||||
*/
|
||||
bool mCurrentFrameIsAnonymousContent = false;
|
||||
|
||||
/**
|
||||
* @brief Resolve a StyleAbsoluteColor to take into account Zen boosts.
|
||||
* @param aColor The color to resolve.
|
||||
* @return The resolved color with Zen boost filters applied, or the original
|
||||
* color if no boost is active.
|
||||
* @see StyleColor::ResolveColor for reference.
|
||||
*/
|
||||
static auto ResolveStyleColor(mozilla::StyleAbsoluteColor aColor)
|
||||
-> mozilla::StyleAbsoluteColor;
|
||||
|
||||
/**
|
||||
* @see ResolveStyleColor for reference.
|
||||
*/
|
||||
static auto ResolveStyleColor(nscolor aColor) -> nscolor;
|
||||
|
||||
/**
|
||||
* @brief Filter a color based on the current Zen boost settings.
|
||||
* @param aColor The color to filter.
|
||||
* @param aPresContext The presentation context to use for filtering.
|
||||
* @return The filtered color.
|
||||
*/
|
||||
static auto FilterColorFromPresContext(nscolor aColor,
|
||||
nsPresContext* aPresContext = nullptr)
|
||||
-> nscolor;
|
||||
|
||||
/**
|
||||
* @brief Called when a presshell is entered during rendering.
|
||||
* @param aDocument The document associated with the presshell being entered.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
RefPtr<mozilla::dom::BrowsingContext> mCurrentBrowsingContext;
|
||||
|
||||
static nsZenAccentOklab mCachedAccent;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the singleton instance of the ZenBoostsBackend.
|
||||
* @return The singleton instance.
|
||||
*/
|
||||
static auto GetInstance() -> nsZenBoostsBackend*;
|
||||
};
|
||||
|
||||
} // namespace zen
|
||||
|
||||
#endif
|
||||
43
src/zen/boosts/zen-advanced-color-options.css
Normal file
43
src/zen/boosts/zen-advanced-color-options.css
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#zen-boost-advanced-color-options-panel {
|
||||
color-scheme: light;
|
||||
--panel-padding: 15px;
|
||||
|
||||
& p {
|
||||
color: #3a3a3b;
|
||||
}
|
||||
|
||||
& input {
|
||||
width: 185px;
|
||||
appearance: none !important;
|
||||
height: 4px;
|
||||
|
||||
background-color: #b5b5b9;
|
||||
border-radius: 2px;
|
||||
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&::part(content) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-advanced-color-options-panel input::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #e6e5ea;
|
||||
border: solid 1px #d1d0d5;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
#zen-boost-advanced-color-options-panel input::-moz-range-thumb:active {
|
||||
background-color: #cbcad0;
|
||||
}
|
||||
153
src/zen/boosts/zen-boost-editor.inc.xhtml
Normal file
153
src/zen/boosts/zen-boost-editor.inc.xhtml
Normal file
@@ -0,0 +1,153 @@
|
||||
#filter substitution
|
||||
<?xml version="1.0"?>
|
||||
# -*- Mode: HTML -*-
|
||||
#
|
||||
# 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/.
|
||||
|
||||
<!DOCTYPE window>
|
||||
|
||||
<html
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="zenBoostWindow"
|
||||
windowtype="Zen:BoostEditor"
|
||||
alwaysontop="true"
|
||||
customtitlebar="true"
|
||||
sizemode="normal"
|
||||
scrolling="false"
|
||||
macanimationtype="document"
|
||||
windowsmica="true"
|
||||
data-l10n-sync="true">
|
||||
<head>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/" />
|
||||
<link rel="stylesheet" href="chrome://global/skin/global.css" />
|
||||
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-boosts.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-buttons.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-theme.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-animations.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-panel-ui.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/zen-icons/icons.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-advanced-color-options.css" />
|
||||
|
||||
<link rel="localization" href="browser/zen-boosts.ftl"/>
|
||||
|
||||
<!-- Loading in the window module -->
|
||||
<script>
|
||||
const { nsZenBoostEditor } = ChromeUtils.importESModule( "resource:///modules/zen/boosts/ZenBoostsEditor.mjs" );
|
||||
window.addEventListener("load", () => {
|
||||
window.boostEditor = new nsZenBoostEditor(document, window.domain, window, window.openerWindow);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<vbox flex="1" id="zen-boost-editor-root">
|
||||
<hbox id="zen-boost-head-wrapper">
|
||||
<button id="zen-boost-close" class="subviewbutton mod-button title-button"></button>
|
||||
<hbox id="zen-boost-name" flex="1">
|
||||
<hbox id="zen-boost-name-container">
|
||||
<html:p id="zen-boost-name-text"></html:p>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<button data-l10n-id="zen-boost-shuffle" id="zen-boost-shuffle" class="subviewbutton mod-button title-button"></button>
|
||||
</hbox>
|
||||
<vbox flex="1" id="zen-boost-filter-wrapper">
|
||||
<hbox class="zen-boost-color-picker-gradient zen-boost-panel-disabled">
|
||||
<button data-l10n-id="zen-boost-magic-theme" id="zen-boost-magic-theme" class="subviewbutton mod-button"></button>
|
||||
<html:div class="zen-boost-color-picker-dot"></html:div>
|
||||
<html:div class="zen-boost-color-picker-circle"></html:div>
|
||||
</hbox>
|
||||
|
||||
<hbox flex="1" id="zen-boost-toolbar-wrapper-colors">
|
||||
<button data-l10n-id="zen-boost-invert" id="zen-boost-invert" class="subviewbutton mod-button small"></button>
|
||||
<button data-l10n-id="zen-boost-controls" id="zen-boost-controls" class="subviewbutton mod-button small"></button>
|
||||
<button data-l10n-id="zen-boost-disable" id="zen-boost-disable" class="subviewbutton mod-button small"></button>
|
||||
</hbox>
|
||||
|
||||
<html:div id="zen-boost-font-wrapper">
|
||||
<vbox id="zen-boost-font-grid">
|
||||
<!-- Font buttons will be injected here -->
|
||||
</vbox>
|
||||
<html:div class="visible-separator"></html:div>
|
||||
<hbox flex="1" id="zen-boost-font-toolbar">
|
||||
<html:select name="font" id="zen-boost-font-select" class="mod-button">
|
||||
<!-- Additional font options will be injected here -->
|
||||
</html:select>
|
||||
<button data-l10n-id="zen-boost-text-case-toggle" id="zen-boost-text-case-toggle" class="subviewbutton mod-button"></button>
|
||||
</hbox>
|
||||
</html:div>
|
||||
|
||||
<button id="zen-boost-zap" class="subviewbutton mod-button big-button toggleable-button">
|
||||
<html:p data-l10n-id="zen-boost-zap" id="zen-boost-zap-text"></html:p>
|
||||
<html:p id="zen-boost-zap-value"></html:p>
|
||||
</button>
|
||||
|
||||
<button id="zen-boost-code" class="subviewbutton mod-button big-button">
|
||||
<html:p data-l10n-id="zen-boost-code" id="zen-boost-code-text"></html:p>
|
||||
</button>
|
||||
|
||||
<hbox flex="1" id="zen-boost-toolbar-wrapper">
|
||||
<button data-l10n-id="zen-boost-save" id="zen-boost-save" class="subviewbutton mod-button med"></button>
|
||||
<button data-l10n-id="zen-boost-load" id="zen-boost-load" class="subviewbutton mod-button med"></button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox flex="1" id="zen-boost-code-editor-root">
|
||||
<hbox id="zen-boost-code-top-bar">
|
||||
<button id="zen-boost-back" class="subviewbutton mod-button big-button">
|
||||
<html:p data-l10n-id="zen-boost-back" id="zen-boost-back-text"></html:p>
|
||||
</button>
|
||||
</hbox>
|
||||
|
||||
<vbox flex="1" id="zen-boost-code-editor">
|
||||
</vbox>
|
||||
|
||||
<hbox id="zen-boost-code-bottom-bar">
|
||||
<button data-l10n-id="zen-boost-css-picker" id="zen-boost-css-picker" class="subviewbutton mod-button big-button toggleable-button"></button>
|
||||
<button data-l10n-id="zen-boost-css-inspector" id="zen-boost-css-inspector" class="subviewbutton mod-button big-button"></button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<popupset id="mainPopupSet">
|
||||
<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.05" max="0.9" value="0.75" step="0.01"/>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<p data-l10n-id="zen-bootst-color-brightness"></p>
|
||||
<html:input id="zen-boost-color-brightness" type="range" min="0" max="1" value="0.5" step="0.01"/>
|
||||
</vbox>
|
||||
<vbox>
|
||||
<p data-l10n-id="zen-bootst-color-original-saturation"></p>
|
||||
<html:input id="zen-boost-color-saturation" type="range" min="0" max="1" value="0.5" step="0.01"/>
|
||||
</vbox>
|
||||
</panel>
|
||||
</popupset>
|
||||
|
||||
<menupopup id="zenBoostContextMenu">
|
||||
<menuitem data-l10n-id="zen-boost-edit-rename" id="zen-boost-edit-rename" command="cmd_zenBoostEditName" />
|
||||
<menuitem data-l10n-id="zen-boost-edit-shuffle" id="zen-boost-edit-shuffle" command="cmd_zenBoostShuffle" />
|
||||
<menuitem data-l10n-id="zen-boost-edit-reset" id="zen-boost-edit-reset" command="cmd_zenBoostReset" />
|
||||
<menuseparator/>
|
||||
<menuitem data-l10n-id="zen-boost-edit-delete" id="zen-boost-edit-delete" command="cmd_zenBoostDelete" />
|
||||
</menupopup>
|
||||
|
||||
<commandset id="zenBoostCommandSet">
|
||||
<command id="cmd_zenBoostEditName"
|
||||
oncommand="window.boostEditor.editBoostName();" />
|
||||
|
||||
<command id="cmd_zenBoostShuffle"
|
||||
oncommand="window.boostEditor.shuffleBoost();" />
|
||||
|
||||
<command id="cmd_zenBoostReset"
|
||||
oncommand="window.boostEditor.resetBoost();" />
|
||||
|
||||
<command id="cmd_zenBoostDelete"
|
||||
oncommand="window.boostEditor.deleteBoost();" />
|
||||
</commandset>
|
||||
</html:body>
|
||||
</html>
|
||||
805
src/zen/boosts/zen-boosts.css
Normal file
805
src/zen/boosts/zen-boosts.css
Normal file
@@ -0,0 +1,805 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#zenBoostWindow {
|
||||
/* For the mica effect we want a white tint */
|
||||
@media not (-moz-platform: linux) {
|
||||
color-scheme: light;
|
||||
}
|
||||
appearance: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.source-editor-frame {
|
||||
max-width: 1000px;
|
||||
max-height: 1000px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#zen-boost-code-top-bar {
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: #f6f6f8;
|
||||
@media (-moz-windows-mica) {
|
||||
background-color: #f6f6f8c0;
|
||||
}
|
||||
|
||||
border: solid 0 #ededef;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
#zen-boost-name-container {
|
||||
background-image: url("chrome://global/skin/icons/arrow-down.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: calc(100% - 2px) calc(50% + 2px);
|
||||
background-size: 12px 12px;
|
||||
padding: 4px 18px 4px 2px;
|
||||
margin-left: 4px;
|
||||
border-radius: 8px;
|
||||
|
||||
opacity: 0.75;
|
||||
background-color: transparent;
|
||||
|
||||
transition:
|
||||
0.25s opacity cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.25s background-color cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
|
||||
-moz-window-dragging: no-drag;
|
||||
}
|
||||
|
||||
#zen-boost-name-container:hover {
|
||||
opacity: 0.8;
|
||||
background-color: #aaaaaa22;
|
||||
}
|
||||
|
||||
#zen-boost-code-bottom-bar {
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: #f6f6f8;
|
||||
@media (-moz-windows-mica) {
|
||||
background-color: #f6f6f8c0;
|
||||
}
|
||||
|
||||
border: solid 0 #ededef;
|
||||
border-top-width: 1px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#zen-boost-code-editor {
|
||||
width: 100%;
|
||||
height: calc(100% - 100px);
|
||||
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: clip;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#zen-boost-editor-root,
|
||||
#zen-boost-code-editor-root {
|
||||
user-select: none;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
& button {
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
padding: 0 !important;
|
||||
min-width: fit-content !important;
|
||||
|
||||
border: solid 6px transparent;
|
||||
|
||||
transition: background 0.1s cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
appearance: none;
|
||||
|
||||
&:active {
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
& .button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.subviewbutton {
|
||||
color: #3a3a3b;
|
||||
}
|
||||
|
||||
#zen-boost-editor-view {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#zen-boost-code-top-bar {
|
||||
-moz-window-dragging: drag; /* Allow dragging the window with the custom titlebar for the code view */
|
||||
}
|
||||
|
||||
#zen-boost-head-wrapper {
|
||||
-moz-window-dragging: drag; /* Allow dragging the window with the custom titlebar */
|
||||
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
max-height: 40px;
|
||||
align-items: center;
|
||||
background-color: #f6f6f8;
|
||||
border: solid 1px #e7e7e7ab;
|
||||
|
||||
@media (-moz-windows-mica) {
|
||||
background-color: #f6f6f8c0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: row-reverse;
|
||||
@media (-moz-platform: macos) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
& button {
|
||||
height: 24px !important;
|
||||
width: 24px !important;
|
||||
aspect-ratio: 1/1 !important;
|
||||
background-color: transparent;
|
||||
opacity: 0.45;
|
||||
|
||||
transition: 0.25s opacity cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
& #zen-boost-name {
|
||||
transition: color 0.2s ease-in-out;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-name-text {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
font-size: 100%;
|
||||
font-weight: 600;
|
||||
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#zen-boost-name-text {
|
||||
text-align: center;
|
||||
margin: auto !important;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
#zen-boost-font-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 0;
|
||||
width: 100%;
|
||||
|
||||
padding: 2px;
|
||||
|
||||
& button {
|
||||
padding: auto;
|
||||
margin: auto;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-filter-wrapper {
|
||||
padding: 20px;
|
||||
gap: 14px;
|
||||
padding-top: 10px;
|
||||
|
||||
background-color: #fcfcfe;
|
||||
|
||||
-moz-window-dragging: drag;
|
||||
& > * {
|
||||
-moz-window-dragging: no-drag;
|
||||
}
|
||||
}
|
||||
|
||||
.big-button {
|
||||
width: 100% !important;
|
||||
margin: auto;
|
||||
text-indent: 6px;
|
||||
vertical-align: middle;
|
||||
font-size: 10pt;
|
||||
|
||||
list-style-position: right;
|
||||
|
||||
& p {
|
||||
width: 75%;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-back {
|
||||
width: 60px !important;
|
||||
margin: 5px;
|
||||
left: 5px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
& hbox {
|
||||
scale: 0.75;
|
||||
margin-right: -7px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-code-bottom-bar button {
|
||||
content: "";
|
||||
width: auto !important;
|
||||
aspect-ratio: 1/1 !important;
|
||||
|
||||
margin: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
#zen-boost-zap {
|
||||
& #zen-boost-zap-value {
|
||||
text-align: right;
|
||||
right: 8px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& hbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-zap[hideicon="true"] {
|
||||
list-style: none;
|
||||
list-style-type: none;
|
||||
|
||||
& hbox {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-size {
|
||||
list-style-type: none;
|
||||
|
||||
& #zen-boost-size-value {
|
||||
text-align: right;
|
||||
right: 8px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-panel-disabled {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.title-button {
|
||||
scale: 0.9;
|
||||
}
|
||||
|
||||
.mod-button {
|
||||
height: 38px !important;
|
||||
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
background-color: #ebebed;
|
||||
|
||||
transition:
|
||||
0.4s background-color cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s opacity cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s filter cubic-bezier(0.075, 0.82, 0.165, 1) !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: none !important;
|
||||
|
||||
opacity: 0.9;
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="none"] {
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
opacity: 0.6 !important;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle:not([case-mode="none"]) {
|
||||
background-color: #ebebed;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
#zen-boost-code-top-bar .mod-button {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
#zen-boost-code-bottom-bar button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#zen-boost-name {
|
||||
height: 26px;
|
||||
font-size: 8pt;
|
||||
text-indent: 2px;
|
||||
vertical-align: middle;
|
||||
color: #3a3a3b;
|
||||
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#zen-boost-close {
|
||||
margin-left: 0;
|
||||
margin-right: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
margin-left: 6px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-shuffle {
|
||||
margin-right: 0;
|
||||
margin-left: 4px;
|
||||
@media (-moz-platform: macos) {
|
||||
margin-right: 10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-button-active {
|
||||
background-color: #3a3a3a;
|
||||
color: #fcfcfe;
|
||||
}
|
||||
|
||||
.zen-boost-button-active:hover {
|
||||
background-color: #5b5b5c;
|
||||
}
|
||||
|
||||
.zen-boost-button-active-transparent {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.zen-boost-button-active-transparent:hover {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
#zen-boost-magic-theme {
|
||||
width: 18px;
|
||||
height: 24px !important;
|
||||
margin-top: 12px;
|
||||
|
||||
z-index: 4;
|
||||
box-shadow: 0 2px 4px #00000010;
|
||||
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
& image {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
&:not(.zen-boost-button-active) {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-toolbar-wrapper,
|
||||
#zen-boost-toolbar-wrapper-colors {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& .small {
|
||||
margin: 0;
|
||||
flex: 1 1 30%;
|
||||
}
|
||||
|
||||
& .med {
|
||||
margin: 0;
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-font-select {
|
||||
width: 95px;
|
||||
height: 20px !important;
|
||||
|
||||
transition: 0.2s opacity ease-in-out;
|
||||
|
||||
color: #727272;
|
||||
background: none;
|
||||
|
||||
font-size: 9pt;
|
||||
|
||||
padding: 0;
|
||||
text-indent: 2px;
|
||||
|
||||
margin-left: 8px;
|
||||
margin-right: 3px;
|
||||
margin-top: 8px;
|
||||
opacity: 0.75;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&[has-selection="true"] {
|
||||
opacity: 1;
|
||||
background-color: #ebebed;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-font-toolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
& button {
|
||||
border-radius: 6px;
|
||||
min-width: fit-content;
|
||||
height: 20px !important;
|
||||
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
margin-top: 8px;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-font-wrapper {
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,.15);
|
||||
background-color: #ffffff;
|
||||
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .visible-separator {
|
||||
content: "";
|
||||
height: 1px;
|
||||
width: auto;
|
||||
|
||||
margin-right: 12px;
|
||||
margin-left: 12px;
|
||||
margin-top: 2px;
|
||||
|
||||
opacity: 0.25;
|
||||
|
||||
border-top: solid 1px #9a9a9a;
|
||||
}
|
||||
|
||||
& button {
|
||||
filter: none;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 50px;
|
||||
transition: 0.1s transform ease;
|
||||
font-size: 12px;
|
||||
padding: 2px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
transform-origin: center center;
|
||||
|
||||
&:hover {
|
||||
background-color: #9a9a9a30;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@property --mod-button-c1 {
|
||||
syntax: "<color>";
|
||||
inherits: false;
|
||||
initial-value: #ebebed;
|
||||
}
|
||||
|
||||
@property --mod-button-c2 {
|
||||
syntax: "<color>";
|
||||
inherits: false;
|
||||
initial-value: #ebebed;
|
||||
}
|
||||
|
||||
.mod-button[mode="orange"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #ffbb5d;
|
||||
--mod-button-c2: #ffa01d;
|
||||
}
|
||||
.mod-button[mode="orange-red"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #ff8758;
|
||||
--mod-button-c2: #ff5b1b;
|
||||
}
|
||||
.mod-button[mode="red"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #ff595f;
|
||||
--mod-button-c2: #ff121b;
|
||||
}
|
||||
.mod-button[mode="blue"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #6650fc;
|
||||
--mod-button-c2: #4125ff;
|
||||
}
|
||||
.mod-button[mode] {
|
||||
background: linear-gradient(180deg, var(--mod-button-c1) 0%, var(--mod-button-c2) 100%) border-box;
|
||||
transition:
|
||||
0.4s background-color cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s opacity cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s filter cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.75s --mod-button-c1 cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.55s --mod-button-c2 cubic-bezier(0.075, 0.82, 0.165, 1) !important;
|
||||
}
|
||||
|
||||
#zen-boost-back {
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: #e3e3e6;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-code-editor-root button {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.toggleable-button[enabled="true"] {
|
||||
filter: invert();
|
||||
opacity: 0.9;
|
||||
|
||||
&:active {
|
||||
filter: invert();
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-color-picker-gradient::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 8px;
|
||||
margin: 8px;
|
||||
|
||||
z-index: 2;
|
||||
|
||||
background: #fbfbfdea;
|
||||
|
||||
background-position: -23px -23px;
|
||||
backdrop-filter: saturate(2) blur(15px);
|
||||
|
||||
background-size: 6px 6px;
|
||||
background-image: radial-gradient(#e3e9e4, 1px, transparent 0);
|
||||
|
||||
@media (-moz-platform: macos) {
|
||||
background-size: 4px 4px;
|
||||
background-image: radial-gradient(#e3e9e4 0.5px, transparent 0);
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-color-picker-gradient::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
background: conic-gradient(
|
||||
rgba(255, 0, 0, 1) 0%,
|
||||
rgba(255, 162, 0, 1) 10%,
|
||||
rgba(255, 242, 0, 1) 20%,
|
||||
rgba(89, 255, 0, 1) 30%,
|
||||
rgba(0, 255, 128, 1) 40%,
|
||||
rgba(0, 255, 247, 1) 50%,
|
||||
rgba(0, 89, 255, 1) 60%,
|
||||
rgba(8, 0, 255, 1) 70%,
|
||||
rgba(204, 0, 255, 1) 80%,
|
||||
rgba(255, 0, 144, 1) 90%,
|
||||
rgba(255, 0, 8, 1) 100%
|
||||
)
|
||||
border-box;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.zen-boost-color-picker-gradient {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 12px #00000021;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 /1;
|
||||
margin: 10px 0 4px 0;
|
||||
|
||||
min-height: calc(var(--panel-width) - var(--panel-padding) * 2 - 2px);
|
||||
|
||||
& [animated="true"] {
|
||||
transition: all 0.4s cubic-bezier(0.14, 1.43, 0.56, 1.01) !important;
|
||||
}
|
||||
|
||||
& .zen-boost-color-picker-circle {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
transform-origin: center center;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
|
||||
outline: solid 1px #9a9a9a88;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
& .zen-boost-color-picker-dot {
|
||||
box-shadow: 0 2px 4px #00000022;
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: var(--zen-theme-picker-dot-color);
|
||||
@media (-prefers-color-scheme: dark) {
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
cursor: pointer;
|
||||
border: 3px solid #ffffff;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
transform-origin: top left;
|
||||
|
||||
&:first-of-type {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-width: 3px;
|
||||
pointer-events: all;
|
||||
transition: transform 0.2s;
|
||||
z-index: 999;
|
||||
&:hover {
|
||||
transform: scale(1.05) translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
&[dragging="true"] {
|
||||
transform: scale(1.2) translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-editor-root:hover {
|
||||
& .zen-boost-color-picker-circle {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.zen-boost-font-button-active {
|
||||
background-color: #5454572f !important;
|
||||
}
|
||||
|
||||
.zen-boost-font-button-active:hover {
|
||||
background-color: #5b5b5c28 !important;
|
||||
}
|
||||
|
||||
/** Import animation */
|
||||
/** From zen-single-components.css:211 */
|
||||
|
||||
@property --background-top {
|
||||
syntax: "<percentage>";
|
||||
inherits: false;
|
||||
initial-value: -50%;
|
||||
}
|
||||
|
||||
#import-animation {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.5) 40%, rgba(255, 255, 255, 0.6) 60%, transparent 100%);
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#import-animation-shadow {
|
||||
position: fixed;
|
||||
mix-blend-mode: multiply;
|
||||
background: #00000040;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 998;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#import-animation-border {
|
||||
position: absolute;
|
||||
border-radius: var(--zen-border-radius);
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#import-animation-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 4px; /* thickness of border */
|
||||
z-index: 1000;
|
||||
|
||||
background: radial-gradient(ellipse 100% 50% at center var(--background-top), rgba(255, 255, 255, 0.9) 80%, transparent);
|
||||
|
||||
/* Punch out the inside once (not animated) */
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
}
|
||||
200
src/zen/boosts/zen-selector.css
Normal file
200
src/zen/boosts/zen-selector.css
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
@keyframes select-component-in {
|
||||
0% {
|
||||
scale: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
scale: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#select-component {
|
||||
width: min-content;
|
||||
font-family: system-ui;
|
||||
|
||||
height: 98px;
|
||||
|
||||
pointer-events: auto;
|
||||
|
||||
transform-origin: top;
|
||||
overflow: hidden;
|
||||
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
|
||||
background-color: #f5f7fb;
|
||||
margin: 4px;
|
||||
|
||||
border-radius: 12px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
box-shadow: 0 0 20px#00000050;
|
||||
|
||||
scale: 1;
|
||||
opacity: 1;
|
||||
|
||||
@media not (-moz-platform: windows) {
|
||||
&[is-appearing="true"] {
|
||||
scale: 0;
|
||||
opacity: 0;
|
||||
|
||||
animation: 0.4s select-component-in cubic-bezier(0.14, 1.43, 0.56, 1.01) forwards;
|
||||
}
|
||||
}
|
||||
|
||||
& input {
|
||||
width: fit-content;
|
||||
min-width: 75px;
|
||||
|
||||
height: 40px;
|
||||
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
font-size: 11pt;
|
||||
transition:
|
||||
0.15s filter ease,
|
||||
0.1s box-shadow ease-out,
|
||||
scale 0.35s cubic-bezier(0.11, 1.6, 0.63, 1);
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
scale: 0.95;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#highlight-shadow {
|
||||
z-index: 999;
|
||||
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
content: "";
|
||||
background-color: #00000044;
|
||||
}
|
||||
|
||||
#highlight-container {
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
|
||||
& .highlight {
|
||||
position: fixed;
|
||||
|
||||
border: 1px solid #e9dd38;
|
||||
background-color: #c2b72046;
|
||||
border-radius: 6px;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
#select-controls {
|
||||
display: flex;
|
||||
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
@media not (-moz-platform: windows) {
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
#select-related {
|
||||
appearance: none;
|
||||
text-align: left;
|
||||
|
||||
width: 250px !important;
|
||||
--related-elements-value: 100%;
|
||||
background: linear-gradient(to top, rgb(247, 66, 0), rgb(245, 134, 86));
|
||||
|
||||
border: none;
|
||||
color: #dadada;
|
||||
box-shadow: 0 0 15px #00000052;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 14px #00000066;
|
||||
background:
|
||||
linear-gradient(to right, transparent var(--related-elements-value), gray var(--related-elements-value)),
|
||||
linear-gradient(to top, rgb(247, 66, 0), rgb(245, 134, 86));
|
||||
|
||||
@media not (-moz-platform: windows) {
|
||||
box-shadow: 0 0 20px #00000077;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#select-this {
|
||||
appearance: none;
|
||||
|
||||
background: linear-gradient(0deg, rgba(246, 27, 25, 1) 0%, rgba(254, 67, 59, 1) 100%);
|
||||
border: none;
|
||||
color: #dadada;
|
||||
|
||||
box-shadow: 0 0 15px #00000052;
|
||||
}
|
||||
|
||||
#select-cancel {
|
||||
appearance: none;
|
||||
|
||||
background: rgb(90, 94, 100);
|
||||
@media (-moz-platform: macos) {
|
||||
background: linear-gradient(0deg, rgba(81, 83, 85, 1) 0%, rgba(108, 110, 112, 1) 100%);
|
||||
}
|
||||
|
||||
border: none;
|
||||
color: #dadada;
|
||||
|
||||
box-shadow: 0 0 15px #00000052;
|
||||
}
|
||||
|
||||
#selector-preview {
|
||||
background-color: #e0e2e63d;
|
||||
outline: 1px solid #e0e2e6ae;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
text-indent: 18px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#selector-element-preview-text {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: rgb(76, 78, 80);
|
||||
}
|
||||
|
||||
#hover-div {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
|
||||
content: "";
|
||||
outline: 1px solid #e9dd38;
|
||||
border-radius: 6px;
|
||||
|
||||
background-color: #c2b72046;
|
||||
|
||||
transition: 0.1s all ease-out;
|
||||
will-change: left, top, width, height;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user