diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 05:50:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 05:50:18 +0000 |
commit | 55a5d29a66503248916f249ad2a1d8b37cde5a03 (patch) | |
tree | a3beb6a90dd3bdaaf67ecb05d42152a494aff946 /src | |
parent | Adding upstream version 1.55.0+dfsg. (diff) | |
download | ublock-origin-upstream/1.57.0+dfsg.tar.xz ublock-origin-upstream/1.57.0+dfsg.zip |
Adding upstream version 1.57.0+dfsg.upstream/1.57.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
152 files changed, 6128 insertions, 2860 deletions
diff --git a/src/1p-filters.html b/src/1p-filters.html index bafa992..bc08479 100644 --- a/src/1p-filters.html +++ b/src/1p-filters.html @@ -22,15 +22,16 @@ <div class="body"> <div id="cloudWidget" class="hide" data-cloud-entry="myFiltersPane"></div> - - <p class="vverbose"><span data-i18n="1pTrustWarning"></span> <span data-i18n="1pFormatHint"></span> <a class="fa-icon info" href="https://github.com/gorhill/uBlock/wiki/Dashboard:-My-filters" target="_blank">question-circle</a></p> <p> <button id="userFiltersApply" class="preferred iconified" type="button" disabled><span class="fa-icon">check</span><span data-i18n="1pApplyChanges">_</span><span class="hover"></span></button> <button id="userFiltersRevert" class="iconified" type="button" disabled><span class="fa-icon">undo</span><span data-i18n="genericRevert">_</span><span class="hover"></span></button> -   +   <button id="importUserFiltersFromFile" class="iconified" type="button"><span class="fa-icon">download-alt</span><span data-i18n="1pImport">_</span><span class="hover"></span></button> <button id="exportUserFiltersToFile" class="iconified" type="button"><span class="fa-icon">upload-alt</span><span data-i18n="1pExport">_</span><span class="hover"></span></button> </p> + <p data-i18n="1pTrustWarning"></p> + <div class="li"><label><span id="enableMyFilters" class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="1pEnableMyFiltersLabel"></span></label></div> + <div id="trustMyFilters" class="li"><label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="1pTrustMyFiltersLabel"></span></label></div> </div> <div id="userFilters" class="codeMirrorContainer codeMirrorBreakAll cm-theme-override" spellcheck="false"></div> <div class="hidden"> diff --git a/src/3p-filters.html b/src/3p-filters.html index a779120..c9123cc 100644 --- a/src/3p-filters.html +++ b/src/3p-filters.html @@ -17,10 +17,10 @@ <div class="body"> <div id="cloudWidget" class="hide" data-cloud-entry="tpFiltersPane"></div> -<div id="actions"> - <button id="buttonApply" class="preferred disabled iconified" type="button" data-i18n-title="3pApplyChanges"><span class="fa-icon">check</span><span data-i18n="3pApplyChanges">_</span><span class="hover"></span></button> +<p id="actions"> + <button id="buttonApply" class="preferred disabled iconified dontshrink" type="button" data-i18n-title="3pApplyChanges"><span class="fa-icon">check</span><span data-i18n="3pApplyChanges">_</span><span class="hover"></span></button> <button id="buttonUpdate" class="preferred disabled iconified" type="button" data-i18n-title="3pUpdateNow"><span class="fa-icon">refresh</span><span data-i18n="3pUpdateNow">_</span><span class="hover"></span></button> -</div> +</p> <div> <div class="li"> @@ -44,7 +44,7 @@ <div class="rootstats expandable" data-key="*"> <span class="fa-icon listExpander">angle-up</span><span id="listsOfBlockedHostsPrompt"></span> </div> - <div class="searchbar"><input type="search" spellcheck="false"/><span class="fa-icon">search</span></div> + <div class="searchfield"><input type="search" spellcheck="false" placeholder="" /><span class="fa-icon">search</span></div> <div class="listEntries"></div> <div class="li listEntry expandable" data-role="import"> <span class="detailbar"> diff --git a/src/_locales/ar/messages.json b/src/_locales/ar/messages.json index be9af18..369c408 100644 --- a/src/_locales/ar/messages.json +++ b/src/_locales/ar/messages.json @@ -483,6 +483,14 @@ "message": "مواقع مصابة أو تحتوي على فايروسات", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "اشعارات ملفات تعريف الارتباط", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "عناصر مزعجة", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "حدث خطأ في الشبكة منع تحديث المورد.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "فلتر واحد في كل سطر. يمكن ان يكون الفلتر رابط موقع او فلتر متوافق مع EasyList-compatible. السطور المسبوقة بـ<code>!</code> سوف يتم تجاهلها.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "لا تضف مرشحات من مصادر غير موثوقة.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "السماح بالفلاتر المخصصة التي تتطلب الثقة", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "استيراد و إضافة", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "تبديل تصفية مستحضرات التجميل", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "تعطيل جافا سكريبت", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "وضع الحظر المتراخي", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/az/messages.json b/src/_locales/az/messages.json index e712582..1708ea4 100644 --- a/src/_locales/az/messages.json +++ b/src/_locales/az/messages.json @@ -483,6 +483,14 @@ "message": "Zərərli domenlər", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Zəhlətökən elementlər əleyhinə filtrlər", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Şəbəkə xətası üzündən yeniləmə mümkün olmadı.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Hər sətirdə yalnız bir filtr. Bu, ya domen adı, ya da Adblock Plus formatında yazılmış filtr ola bilər. Əvvəli <code>!</code> ilə başlayan sətirlər nəzərə alınmayacaqdır.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Etibarsız mənbələrdən filtrlər əlavə etməyin.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "İdxal və əlavə et", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Kozmetik filtrləməni yandır/söndür", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Əngəlləmə rejimini yüngülləşdir", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/be/messages.json b/src/_locales/be/messages.json index 52ee835..bb7a458 100644 --- a/src/_locales/be/messages.json +++ b/src/_locales/be/messages.json @@ -4,7 +4,7 @@ "description": "extension name." }, "extShortDesc": { - "message": "Нарэшце, эфектыўны блакавальнік. Не нагружае працэсар і памяць.", + "message": "Нарэшце, эфектыўны блакіроўшчык. Не нагружае працэсар і памяць.", "description": "this will be in the Chrome web store: must be 132 characters or less" }, "dashboardName": { @@ -20,7 +20,7 @@ "description": "Label for button to prevent navigating away from unsaved changes" }, "dashboardUnsavedWarningIgnore": { - "message": "Iгнараваць", + "message": "Ігнараваць", "description": "Label for button to ignore unsaved changes" }, "settingsPageName": { @@ -483,6 +483,14 @@ "message": "Дамены шкодных праграм", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Віджэты сацыяльных сетак", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Абвесткі пра кукі", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Надакучлівасці", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Памылка сеткі не дазволіла абнавіць рэсурс.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Адзін фільтр на радок. Фільтрам можа быць адрас сайта або сумяшчальны з EasyList фільтр. Радкі, што пачынаюцца з <code>!</code>, будуць праігнараваны.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Не дадавайце фільтры з крыніц, якім не давяраеце.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Уключыць мае карыстальніцкія фільтры", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Дазволіць карыстальніцкія фільтры, якія патрабуюць даверу", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Імпартаваць і дадаць", "description": "Button in the 'My filters' pane" @@ -972,7 +984,7 @@ "description": "Label for widget to select type of issue" }, "supportS6Select1Option0": { - "message": "-- Выберыце катэгорыю --", + "message": "-- Выберыце запіс праблемы --", "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option1": { @@ -1251,6 +1263,10 @@ "message": "Пераключыць касметычнае фільтраванне", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Уключыць/адключыць JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Паслаблены рэжым блакавання", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/bg/messages.json b/src/_locales/bg/messages.json index fd2b676..aaff8db 100644 --- a/src/_locales/bg/messages.json +++ b/src/_locales/bg/messages.json @@ -483,6 +483,14 @@ "message": "Защита от зловреден софтуер, сигурност", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Социални джаджи", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Известия за бисквитки", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Досадни неща", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Грешка в мрежата възпрепятства обновяването на ресурса.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Един филтър на ред. Това може да бъде обикновен адрес или филтър, съвместим с EasyList. Редовете с представка <code>!</code> ще бъдат игнорирани.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Не добавяйте филтри от ненадеждни източници.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Активиране на моите потребителски филтри", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Разрешаване на потребителски филтри, изискващи доверие", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Внасяне и добавяне...", "description": "Button in the 'My filters' pane" @@ -708,7 +720,7 @@ "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltinEventful": { - "message": "eventful", + "message": "изпълнен със събития", "description": "A keyword in the built-in row filtering expression: all items corresponding to uBO doing something (blocked, allowed, redirected, etc.)" }, "loggerRowFiltererBuiltinBlocked": { @@ -1251,6 +1263,10 @@ "message": "Превключване на козметичното филтриране", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Превключване на JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Отпуснат режим на блокиране", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/bn/messages.json b/src/_locales/bn/messages.json index adbb21a..db7933e 100644 --- a/src/_locales/bn/messages.json +++ b/src/_locales/bn/messages.json @@ -16,7 +16,7 @@ "description": "A warning in the dashboard when navigating away from unsaved changes" }, "dashboardUnsavedWarningStay": { - "message": "থাকুন", + "message": "এখানে থাকুন", "description": "Label for button to prevent navigating away from unsaved changes" }, "dashboardUnsavedWarningIgnore": { @@ -483,6 +483,14 @@ "message": "ম্যালওয়্যার ডোমেইন", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "সোশ্যাল উইজেট", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "কুকি নোটিশ", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "বিরক্তিকর", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "একটি নেটওয়ার্ক ত্রুটি রিসোর্স বা তথ্য হালনাগাদ হওয়া রোধ করেছে।", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "লাইন প্রতি একটি ফিল্টার। একটি ফিল্টার শুধু হোস্টনেম বা অ্যাডব্লক প্লাস-এর সাথে সামঞ্জস্যপূর্ণ ফিল্টার হতে পারে। <code>!</code> দিয়ে শুরু হওয়া লাইন উপেক্ষা করা হবে।", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "সন্দেহজনক উৎস থেকে ফিল্টারে কোন কিছু যোগ করবেন না।", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "আপনার কাস্টম ফিল্টারটি চালু করুন", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "কাস্টম ফিল্টারকে বিশ্বস্ততার অনুমতি দিন", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "আমদানি করে পরিশেষে যোগ করুন", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "সৌন্দর্য্যবর্ধক ফিল্টার চালু বা বন্ধ করুন", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Javascript টগল করুন", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "রোধক মোড শিথিল করুন", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/br_FR/messages.json b/src/_locales/br_FR/messages.json index 58c7dd6..2a27e9f 100644 --- a/src/_locales/br_FR/messages.json +++ b/src/_locales/br_FR/messages.json @@ -76,7 +76,7 @@ "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Klikit evit enaouiñ uBlock₀ war al lec'hienn-mañ.", + "message": "Enaouiñ uBlock₀ war al lec'hienn-mañ.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -136,11 +136,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Klikit evit stankañ an holl brenestroù pop-up war al lec'hienn-mañ", + "message": "Stankañ an holl brenestroù pop-up war al lec'hienn-mañ", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Klikit evit aotren an holl brenestroù pop-up war al lec'hienn-mañ", + "message": "Aotren an holl brenestroù pop-up war al lec'hienn-mañ", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -148,11 +148,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Klikit evit stankañ an elfennoù media pounner war al lec'hienn-mañ", + "message": "Stankañ an elfennoù media pounner war al lec'hienn-mañ", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Klikit evit aotren an elfennoù media pounner en-dro war al lec'hienn-mañ", + "message": "Aotren an elfennoù media pounner en-dro war al lec'hienn-mañ", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -160,31 +160,31 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Klikit evit lazhañ ar silañ kenedel war al lec'hienn-mañ", + "message": "Lazhañ ar silañ kenedel war al lec'hienn-mañ", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Klikit evit enaouiñ ar silañ kenedel war al lec'hienn-mañ", + "message": "Enaouiñ ar silañ kenedel war al lec'hienn-mañ", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { - "message": "Lazhañ/enaouiñ evit stankañ an nodrezhioù diavaez war al lec'hienn-mañ", + "message": "Aotren/stankañ ar polisoù diavaez war al lec'hienn-mañ", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Klikit evit stankañ an nodrezhioù diavaez war al lec'hienn-mañ", + "message": "Stankañ ar polisoù diavaez war al lec'hienn-mañ", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Klikit evit aotren an nodrezhioù diavaez war al lec'hienn-mañ", + "message": "Aotren ar polisoù diavaez war al lec'hienn-mañ", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoScripting1": { - "message": "Klikit evit diweredekaat JavaScript war al lec'hienn-mañ", + "message": "Diweredekaat JavaScript war al lec'hienn-mañ", "description": "Tooltip for the no-scripting per-site switch" }, "popupTipNoScripting2": { - "message": "Klikit evit gweredekaat JavaScript war al lec'hienn-mañ en-dro", + "message": "Gweredekaat JavaScript war al lec'hienn-mañ en-dro", "description": "Tooltip for the no-scripting per-site switch" }, "popupNoPopups_v2": { @@ -200,7 +200,7 @@ "description": "Caption for the no-cosmetic-filtering per-site switch" }, "popupNoRemoteFonts_v2": { - "message": "Nodrezhioù diavaez", + "message": "Polisoù diavaez", "description": "Caption for the no-remote-fonts per-site switch" }, "popupNoScripting_v2": { @@ -224,11 +224,11 @@ "description": "Tooltip when hovering the top-most cell of the local-rules column." }, "popupTipSaveRules": { - "message": "Klikit evit lakaat ho kemmoù da dalvezout.", + "message": "Lakaat ho kemmoù da dalvezout.", "description": "Tooltip when hovering over the padlock in the dynamic filtering pane." }, "popupTipRevertRules": { - "message": "Klikit evit nullañ ar c'hemmoù ho peus graet.", + "message": "Nullañ ar c'hemmoù ho peus graet.", "description": "Tooltip when hovering over the eraser in the dynamic filtering pane." }, "popupAnyRulePrompt": { @@ -384,7 +384,7 @@ "description": "" }, "settingsNoRemoteFontsPrompt": { - "message": "Stankañ an nodrezhioù diavaez", + "message": "Stankañ ar polisoù diavaez", "description": "" }, "settingsNoScriptingPrompt": { @@ -444,7 +444,7 @@ "description": "English: Parse and enforce Adblock+ element hiding filters." }, "3pParseAllABPHideFiltersInfo": { - "message": "Ar siloù kenedel a servij da guzhat elfennoù ur bajenn web a c'hallfe bezañ noazadurioù d'ar gwelet ha ne c'hallont ket bezañ stanket gant silañ ar rouedad diazezet war ar rekedoù.", + "message": "Ar siloù kenedel a servij da guzhat elfennoù ur bajenn web a c'hallfe saotrañ ar gweled ha ne c'hallont ket bezañ stanket gant ar siloù rouedad diazezet war ar rekedoù.", "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." }, "3pIgnoreGenericCosmeticFilters": { @@ -452,7 +452,7 @@ "description": "This will cause uBO to ignore all generic cosmetic filters." }, "3pIgnoreGenericCosmeticFiltersInfo": { - "message": "Name Mar bez gweredekaet an dibarzh-mañ e vo dilemel an memor ha an CPU ouzhpennet d'ar bajennoù web evel ur c'helc'h ar siloù kosmetik generel.", + "message": "Ar siloù kenedel hollek a zo siloù kenedel bet savet evit bezañ implijet war an holl lec'hiennoù. Ma'z enaouit an dibarzh-mañ e vo implijer nebeutoc'h a vevor hag ar reizhiad korvoiñ (CPU) war al lec'hiennoù-se.\n\nErbedet eo deoc'h enaouiñ an dibarzh-mañ m'emaoc'h oc'h ober gant un ardivink re zic'halloudek.", "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pSuspendUntilListsAreLoaded": { @@ -483,6 +483,14 @@ "message": "Gwarez a-enep ar malware ha surentez", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Saotradurioù", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Ur fazi rouedad en deus miret an danvez da vezañ hizivaet.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Ur sil dre linenn. Ur sil a c'hall bezañ un anv ostiz hepken pe ur sil hag a glot gant EasyList.\nAl linennoù a grog gant <code>!</code> a vo dilezet.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Arabat ouzhpennañ siloù a zeu diouzh mammennoù douetus.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Gweredekaat ma siloù personelaet", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Kaout fiziañs er siloù personelaet", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Enporzhiañ hag ouzhpennañ", "description": "Button in the 'My filters' pane" @@ -952,7 +964,7 @@ "description": "Header of 'Report a filter issue' section in Support pane" }, "supportS6P1S1": { - "message": "Evit nompas sammañ ar genlabourerien a-youl vat gant meur a zanevell heñvel, gwiriit ma n'eo ket bet danevellet ho kudenn c'hoazh mar plij.", + "message": "Evit nompas sammañ ar genlabourerien a-youl vat gant meur a zanevell heñvel, gwiriit ma n'eo ket bet danevellet ho kudenn en ar-raok mar plij.", "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S1": { @@ -1251,6 +1263,10 @@ "message": "Enaouiñ ar silañ kenedel", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Enaouiñ/lazhañ JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Distanañ ar mod stankañ", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/bs/messages.json b/src/_locales/bs/messages.json index 79a2fbe..d80ae9d 100644 --- a/src/_locales/bs/messages.json +++ b/src/_locales/bs/messages.json @@ -483,6 +483,14 @@ "message": "Zlonamjerne domene", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Smetnje", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Mrežna greška je spriječila ažuriranje resursa.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Jedan filter po redu. Filter može biti običan naziv domaćina/hosta ili filter kompatibilan sa EasyList-om. Redovi sa prefiksom <code>!</code> će biti ignorisani.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Nemojte dodavati filtere iz nepouzdanih izvora.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Uvezi i dodaj", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Uključi/isključi estetsko filtriranje", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relaksiran mod blokiranja", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ca/messages.json b/src/_locales/ca/messages.json index be806f7..97ade2d 100644 --- a/src/_locales/ca/messages.json +++ b/src/_locales/ca/messages.json @@ -483,6 +483,14 @@ "message": "Dominis perillosos", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Ginys socials", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Avís de galetes", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elements molestos", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Un error de xarxa va impedir que s'actualitzés el recurs.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Un filtre per línia. Un filtre pot ser un domini, o un filtre compatible amb l'Adblock Plus. S'ignoraran les línies amb el símbol ‘!’.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "No afegiu filtres de fonts no confiables.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Habilita els meus filtres personalitzats", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Permet filtres personalitzats que requereixin confiança", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importa i annexa", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Commuta els filtres cosmètics", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Canviar l'estat de JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Mode de bloqueig relaxat", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/cs/messages.json b/src/_locales/cs/messages.json index e25baf5..025f0b5 100644 --- a/src/_locales/cs/messages.json +++ b/src/_locales/cs/messages.json @@ -483,6 +483,14 @@ "message": "Ochrana před malwarem, bezpečnost", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Widgety sociálních sítí", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Oznámení o používání souborů cookie", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Dotěrnosti", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Chyba sítě znemožnila aktualizaci tohoto zdroje.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Jeden filtr na řádek. Filtr může být prostý název hostitele nebo filtr kompatibilní s EasyList. Řádky začínající vykřičníkem <code>!</code> budou ignorovány.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Nepřidávejte filtry z nedůvěryhodných zdrojů.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Povolit mé vlastní filtry", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Povolit vlastní filtry vyžadující důvěryhodnost", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importovat a připojit…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Zapnout kosmetické filtrování", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Přepnout JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Uvolnit režim blokování", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/cv/messages.json b/src/_locales/cv/messages.json index 55f9fb5..aec33eb 100644 --- a/src/_locales/cv/messages.json +++ b/src/_locales/cv/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ku/messages.json b/src/_locales/cy/messages.json index 647fec3..7152ef9 100644 --- a/src/_locales/ku/messages.json +++ b/src/_locales/cy/messages.json @@ -4,59 +4,59 @@ "description": "extension name." }, "extShortDesc": { - "message": "Finally, an efficient blocker. Easy on CPU and memory.", + "message": "O'r diwedd, rhwystrydd effeithlon. Ysgafn ar y CPU a'r cof.", "description": "this will be in the Chrome web store: must be 132 characters or less" }, "dashboardName": { - "message": "uBlock₀ — Dashboard", + "message": "uBlock₀ — Dashfwrdd", "description": "English: uBlock₀ — Dashboard" }, "dashboardUnsavedWarning": { - "message": "Warning! You have unsaved changes", + "message": "Rhybudd! Mae yna newidiadau heb eu cadw", "description": "A warning in the dashboard when navigating away from unsaved changes" }, "dashboardUnsavedWarningStay": { - "message": "Stay here", + "message": "Aros yma", "description": "Label for button to prevent navigating away from unsaved changes" }, "dashboardUnsavedWarningIgnore": { - "message": "Ignore", + "message": "Anwybyddu", "description": "Label for button to ignore unsaved changes" }, "settingsPageName": { - "message": "Settings", + "message": "Gosodiadau", "description": "appears as tab name in dashboard" }, "3pPageName": { - "message": "Filter lists", + "message": "Rhestri hidlo", "description": "appears as tab name in dashboard" }, "1pPageName": { - "message": "My filters", + "message": "Fy hidlyddion", "description": "appears as tab name in dashboard" }, "rulesPageName": { - "message": "My rules", + "message": "Fy rheolau", "description": "appears as tab name in dashboard" }, "whitelistPageName": { - "message": "Trusted sites", + "message": "Gwefannau dibynadwy", "description": "appears as tab name in dashboard" }, "shortcutsPageName": { - "message": "Shortcuts", + "message": "Llwybrau byr", "description": "appears as tab name in dashboard" }, "statsPageName": { - "message": "uBlock₀ — Logger", + "message": "uBlock₀ — Logiwr", "description": "Title for the logger window" }, "aboutPageName": { - "message": "About", + "message": "Ynghylch", "description": "appears as tab name in dashboard" }, "supportPageName": { - "message": "Support", + "message": "Cymorth", "description": "appears as tab name in dashboard" }, "assetViewerPageName": { @@ -64,19 +64,19 @@ "description": "Title for the asset viewer page" }, "advancedSettingsPageName": { - "message": "Advanced settings", + "message": "Gosodiadau uwch", "description": "Title for the advanced settings page" }, "popupPowerSwitchInfo": { - "message": "Click: disable/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page.", + "message": "Clic: analluogi/galluogi uBlock₀ ar y wefan hon.\n\nCtrl+clic: analluogi uBlock₀ ar y dudalen hon yn unig.", "description": "English: Click: disable/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "message": "Cliciwch i analluogi uBlock₀ ar y wefan hon.\n\nDefnyddiwch ctrl+clic i analluogi uBlock₀ ar y dudalen hon yn unig.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Click to enable uBlock₀ for this site.", + "message": "Cliciwch i alluogi uBlock₀ ar y wefan hon.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -84,7 +84,7 @@ "description": "English: requests blocked" }, "popupBlockedOnThisPagePrompt": { - "message": "on this page", + "message": "ar y dudalen hon", "description": "English: on this page" }, "popupBlockedStats": { @@ -96,39 +96,39 @@ "description": "English: since install" }, "popupOr": { - "message": "or", + "message": "neu", "description": "English: or" }, "popupBlockedOnThisPage_v2": { - "message": "Blocked on this page", + "message": "Wedi rhwystro ar y dudalen hon", "description": "For the new mobile-friendly popup design" }, "popupBlockedSinceInstall_v2": { - "message": "Blocked since install", + "message": "Wedi rhwystro ers gosod", "description": "For the new mobile-friendly popup design" }, "popupDomainsConnected_v2": { - "message": "Domains connected", + "message": "Parthau wedi cysylltu", "description": "For the new mobile-friendly popup design" }, "popupTipDashboard": { - "message": "Open the dashboard", + "message": "Agor y dashfwrdd", "description": "English: Click to open the dashboard" }, "popupTipZapper": { - "message": "Enter element zapper mode", + "message": "Modd saethu elfen", "description": "Tooltip for the element-zapper icon in the popup panel" }, "popupTipPicker": { - "message": "Enter element picker mode", + "message": "Modd dewis elfen", "description": "English: Enter element picker mode" }, "popupTipLog": { - "message": "Open the logger", + "message": "Agor y logiwr", "description": "Tooltip used for the logger icon in the panel" }, "popupTipReport": { - "message": "Report an issue on this website", + "message": "Adrodd nam ar y wefan hon", "description": "Tooltip used for the 'chat' icon in the panel" }, "popupTipNoPopups": { @@ -156,15 +156,15 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { - "message": "Toggle cosmetic filtering for this site", + "message": "Toglo hidlo arwynebol ar gyfer y wefan hon", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Click to disable cosmetic filtering on this site", + "message": "Cliciwch i analluogi hidlo arwynebol ar y wefan hon", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Click to enable cosmetic filtering on this site", + "message": "Cliciwch i alluogi hidlo arwynebol ar y wefan hon", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -172,19 +172,19 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Click to block remote fonts on this site", + "message": "Cliciwch i rwystro ffontiau pell ar y wefan hon", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Click to no longer block remote fonts on this site", + "message": "Cliciwch i ganiatáu ffontiau pell ar y wefan hon", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoScripting1": { - "message": "Click to disable JavaScript on this site", + "message": "Cliciwch i analluogi JavaScript ar y wefan hon", "description": "Tooltip for the no-scripting per-site switch" }, "popupTipNoScripting2": { - "message": "Click to no longer disable JavaScript on this site", + "message": "Cliciwch i alluogi JavaScript ar y wefan hon", "description": "Tooltip for the no-scripting per-site switch" }, "popupNoPopups_v2": { @@ -196,11 +196,11 @@ "description": "Caption for the no-large-media per-site switch" }, "popupNoCosmeticFiltering_v2": { - "message": "Cosmetic filtering", + "message": "Hidlo arwynebol", "description": "Caption for the no-cosmetic-filtering per-site switch" }, "popupNoRemoteFonts_v2": { - "message": "Remote fonts", + "message": "Ffontiau pell", "description": "Caption for the no-remote-fonts per-site switch" }, "popupNoScripting_v2": { @@ -208,11 +208,11 @@ "description": "Caption for the no-scripting per-site switch" }, "popupMoreButton_v2": { - "message": "More", + "message": "Mwy", "description": "Label to be used to show popup panel sections" }, "popupLessButton_v2": { - "message": "Less", + "message": "Llai", "description": "Label to be used to hide popup panel sections" }, "popupTipGlobalRules": { @@ -268,11 +268,11 @@ "description": "appears in popup" }, "popupHitDomainCount": { - "message": "{{count}} out of {{total}}", + "message": "{{count}} o {{total}}", "description": "appears in popup" }, "popupVersion": { - "message": "Version", + "message": "Fersiwn", "description": "Example of use: Version 1.26.4" }, "popup3pScriptFilter": { @@ -284,7 +284,7 @@ "description": "Appears as an option to filter out firewall rows" }, "pickerCreate": { - "message": "Create", + "message": "Creu", "description": "English: Create" }, "pickerPick": { @@ -304,7 +304,7 @@ "description": "English: header for a type of filter in the element picker dialog" }, "pickerCosmeticFilters": { - "message": "Cosmetic filters", + "message": "Hidlyddion arwynebol", "description": "English: Cosmetic filters" }, "pickerCosmeticFiltersHint": { @@ -336,11 +336,11 @@ "description": "English: Color-blind friendly" }, "settingsAppearance": { - "message": "Appearance", + "message": "Golwg", "description": "Section for controlling user interface appearance" }, "settingsThemeLabel": { - "message": "Theme", + "message": "Thema", "description": "Label for checkbox to enable a custom dark theme" }, "settingsThemeAccent0Label": { @@ -352,7 +352,7 @@ "description": "" }, "settingsAdvancedUserPrompt": { - "message": "I am an advanced user", + "message": "Rwy'n ddefnyddiwr profiadol", "description": "Checkbox to let user access advanced, technical features" }, "settingsPrefetchingDisabledPrompt": { @@ -364,7 +364,7 @@ "description": "English: " }, "settingsWebRTCIPAddressHiddenPrompt": { - "message": "Prevent WebRTC from leaking local IP addresses", + "message": "Rhwystro WebRTC rhag datgelu eich cyfeiriad IP mewnol", "description": "English: " }, "settingPerSiteSwitchGroup": { @@ -376,7 +376,7 @@ "description": "" }, "settingsNoCosmeticFilteringPrompt": { - "message": "Disable cosmetic filtering", + "message": "Analluogi hidlo arwynebol", "description": "" }, "settingsNoLargeMediaPrompt": { @@ -384,11 +384,11 @@ "description": "" }, "settingsNoRemoteFontsPrompt": { - "message": "Block remote fonts", + "message": "Rhwystro ffontiau pell", "description": "" }, "settingsNoScriptingPrompt": { - "message": "Disable JavaScript", + "message": "Analluogi JavaScript", "description": "The default state for the per-site no-scripting switch" }, "settingsNoCSPReportsPrompt": { @@ -400,7 +400,7 @@ "description": "background information: https://github.com/uBlockOrigin/uBlock-issues/issues/1513" }, "settingsAdvanced": { - "message": "Advanced", + "message": "Uwch", "description": "Section for controlling advanced-user settings" }, "settingsAdvancedSynopsis": { @@ -408,7 +408,7 @@ "description": "Description of section controlling advanced-user settings" }, "settingsAdvancedUserSettings": { - "message": "advanced settings", + "message": "gosodiadau uwch", "description": "For the tooltip of a link which gives access to advanced settings" }, "settingsLastRestorePrompt": { @@ -424,7 +424,7 @@ "description": "Appears at the top of the _3rd-party filters_ pane" }, "3pListsOfBlockedHostsPerListStats": { - "message": "{{used}} used out of {{total}}", + "message": "Defnyddir {{used}} o {{total}}", "description": "Appears aside each filter list in the _3rd-party filters_ pane" }, "3pAutoUpdatePrompt1": { @@ -432,7 +432,7 @@ "description": "A checkbox in the _3rd-party filters_ pane" }, "3pUpdateNow": { - "message": "Update now", + "message": "Diweddaru nawr", "description": "A button in the in the _3rd-party filters_ pane" }, "3pPurgeAll": { @@ -448,11 +448,11 @@ "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." }, "3pIgnoreGenericCosmeticFilters": { - "message": "Ignore generic cosmetic filters", + "message": "Anwybyddu hidlyddion arwynebol cyffredinol", "description": "This will cause uBO to ignore all generic cosmetic filters." }, "3pIgnoreGenericCosmeticFiltersInfo": { - "message": "Generic cosmetic filters are those cosmetic filters which are meant to apply on all web sites. Enabling this option will eliminate the memory and CPU overhead added to web pages as a result of handling generic cosmetic filters.\n\nIt is recommended to enable this option on less powerful devices.", + "message": "Hidlyddion arwynebol cyffredinol yw'r hidlyddion arwynebol hynny sy'n gweithredu ar bob gwefan. Mae galluogi'r dewisiad hwn yn dileu'r pwysau a roddir ar y cof a'r CPU gan wefannau o ganlyniad i drin hidlyddion arwynebol cyffredinol.\n\nArgymhellir galluogi'r dewisiad hwn ar ddyfeisiau llai pwerus.", "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pSuspendUntilListsAreLoaded": { @@ -472,35 +472,43 @@ "description": "Filter lists section name" }, "3pGroupAds": { - "message": "Ads", + "message": "Hysbysebion", "description": "Filter lists section name" }, "3pGroupPrivacy": { - "message": "Privacy", + "message": "Preifatrwydd", "description": "Filter lists section name" }, "3pGroupMalware": { - "message": "Malware protection, security", + "message": "Diogelwch ac amddiffyn rhag maleiswedd", + "description": "Filter lists section name" + }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", "description": "Filter lists section name" }, "3pGroupAnnoyances": { - "message": "Annoyances", + "message": "Pethau diflas", "description": "Filter lists section name" }, "3pGroupMultipurpose": { - "message": "Multipurpose", + "message": "Amlbwrpas", "description": "Filter lists section name" }, "3pGroupRegions": { - "message": "Regions, languages", + "message": "Rhanbarthau, ieithoedd", "description": "Filter lists section name" }, "3pGroupCustom": { - "message": "Custom", + "message": "Addasedig", "description": "Filter lists section name" }, "3pImport": { - "message": "Import…", + "message": "Mewnforio…", "description": "The label for the checkbox used to import external filter lists" }, "3pExternalListsHint": { @@ -512,7 +520,7 @@ "description": "used as a tooltip for the out-of-date icon beside a list" }, "3pViewContent": { - "message": "view content", + "message": "gweld y cynnwys", "description": "used as a tooltip for eye icon beside a list" }, "3pLastUpdate": { @@ -527,24 +535,28 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" }, "1pExport": { - "message": "Export…", + "message": "Allforio…", "description": "Button in the 'My filters' pane" }, "1pExportFilename": { - "message": "my-ublock-static-filters_{{datetime}}.txt", + "message": "fy-hidlyddion-statig_{{datetime}}.txt", "description": "English: my-ublock-static-filters_{{datetime}}.txt" }, "1pApplyChanges": { @@ -568,11 +580,11 @@ "description": "This will persist temporary rules" }, "rulesEdit": { - "message": "Edit", + "message": "Golygu", "description": "Will enable manual-edit mode (textarea)" }, "rulesEditSave": { - "message": "Save", + "message": "Cadw", "description": "Will save manually-edited content and exit manual-edit mode" }, "rulesEditDiscard": { @@ -588,11 +600,11 @@ "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { - "message": "my-ublock-dynamic-rules_{{datetime}}.txt", + "message": "fy-hidlyddion-deinamig_{{datetime}}.txt", "description": "default file name to use" }, "rulesHint": { - "message": "List of your dynamic filtering rules.", + "message": "Rhestr o'ch rheolau hidlo deinamig. ", "description": "English: List of your dynamic filtering rules." }, "rulesFormatHint": { @@ -600,19 +612,19 @@ "description": "English: dynamic rule syntax and full documentation." }, "rulesSort": { - "message": "Sort:", + "message": "Trefnu:", "description": "English: label for sort option." }, "rulesSortByType": { - "message": "Rule type", + "message": "Math o reol", "description": "English: a sort option for list of rules." }, "rulesSortBySource": { - "message": "Source", + "message": "Ffynhonnell", "description": "English: a sort option for list of rules." }, "rulesSortByDestination": { - "message": "Destination", + "message": "Cyrchfan", "description": "English: a sort option for list of rules." }, "whitelistPrompt": { @@ -624,7 +636,7 @@ "description": "Button in the 'Trusted sites' pane" }, "whitelistExport": { - "message": "Export…", + "message": "Allforio…", "description": "Button in the 'Trusted sites' pane" }, "whitelistExportFilename": { @@ -636,11 +648,11 @@ "description": "English: Apply changes" }, "logRequestsHeaderType": { - "message": "Type", + "message": "Math", "description": "English: Type" }, "logRequestsHeaderDomain": { - "message": "Domain", + "message": "Parth", "description": "English: Domain" }, "logRequestsHeaderURL": { @@ -648,11 +660,11 @@ "description": "English: URL" }, "logRequestsHeaderFilter": { - "message": "Filter", + "message": "Hidlydd", "description": "English: Filter" }, "logAll": { - "message": "All", + "message": "Y cyfan", "description": "Appears in the logger's tab selector" }, "logBehindTheScene": { @@ -660,7 +672,7 @@ "description": "Pretty name for behind-the-scene network requests" }, "loggerCurrentTab": { - "message": "Current tab", + "message": "Y tab presennol", "description": "Appears in the logger's tab selector" }, "loggerReloadTip": { @@ -680,31 +692,31 @@ "description": "Tooltip for the top-right info label in the logger page" }, "loggerClearTip": { - "message": "Clear logger", + "message": "Clirio'r logiwr", "description": "Tooltip for the eraser in the logger page; used to blank the content of the logger" }, "loggerPauseTip": { - "message": "Pause logger (discard all incoming data)", + "message": "Oedi'r logiwr (diystyru data sy'n cyrraedd)", "description": "Tooltip for the pause button in the logger page" }, "loggerUnpauseTip": { - "message": "Unpause logger", + "message": "Dad-oedi'r logiwr", "description": "Tooltip for the play button in the logger page" }, "loggerRowFiltererButtonTip": { - "message": "Toggle logger filtering", + "message": "Toglo hidlo'r logiwr", "description": "Tooltip for the row filterer button in the logger page" }, "logFilterPrompt": { - "message": "filter logger content", + "message": "hidlo cynnwys yr hidlydd", "description": "Placeholder string for logger output filtering input field" }, "loggerRowFiltererBuiltinTip": { - "message": "Logger filtering options", + "message": "Dewisiadau hidlo'r logiwr", "description": "Tooltip for the button to bring up logger output filtering options" }, "loggerRowFiltererBuiltinNot": { - "message": "Not", + "message": "Nid", "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltinEventful": { @@ -712,15 +724,15 @@ "description": "A keyword in the built-in row filtering expression: all items corresponding to uBO doing something (blocked, allowed, redirected, etc.)" }, "loggerRowFiltererBuiltinBlocked": { - "message": "blocked", + "message": "wedi rhwystro", "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltinAllowed": { - "message": "allowed", + "message": "wedi caniatáu", "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltinModified": { - "message": "modified", + "message": "wedi addasu", "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltin1p": { @@ -732,11 +744,11 @@ "description": "A keyword in the built-in row filtering expression" }, "loggerEntryDetailsHeader": { - "message": "Details", + "message": "Manylion", "description": "Small header to identify the 'Details' pane for a specific logger entry" }, "loggerEntryDetailsFilter": { - "message": "Filter", + "message": "Hidlydd", "description": "Label to identify a filter field" }, "loggerEntryDetailsFilterList": { @@ -744,11 +756,11 @@ "description": "Label to identify a filter list field" }, "loggerEntryDetailsRule": { - "message": "Rule", + "message": "Rheol", "description": "Label to identify a rule field" }, "loggerEntryDetailsContext": { - "message": "Context", + "message": "Cyd-destun", "description": "Label to identify a context field (typically a hostname)" }, "loggerEntryDetailsRootContext": { @@ -760,7 +772,7 @@ "description": "Label to identify a field providing partyness information" }, "loggerEntryDetailsType": { - "message": "Type", + "message": "Math", "description": "Label to identify the type of an entry" }, "loggerEntryDetailsURL": { @@ -772,15 +784,15 @@ "description": "Small header to identify the dynamic URL filtering section" }, "loggerURLFilteringContextLabel": { - "message": "Context:", + "message": "Cyd-destun:", "description": "Label for the context selector" }, "loggerURLFilteringTypeLabel": { - "message": "Type:", + "message": "Math:", "description": "Label for the type selector" }, "loggerStaticFilteringHeader": { - "message": "Static filter", + "message": "Hidlydd statig", "description": "Small header to identify the static filtering section" }, "loggerStaticFilteringSentence": { @@ -788,15 +800,15 @@ "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartBlock": { - "message": "Block", + "message": "Rhwystro", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartAllow": { - "message": "Allow", + "message": "Caniatáu", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartType": { - "message": "type “{{type}}”", + "message": "math \"{{type}}\"", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartAnyType": { @@ -804,7 +816,7 @@ "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartOrigin": { - "message": "from “{{origin}}”", + "message": "o \"{{origin}}\"", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartAnyOrigin": { @@ -848,19 +860,19 @@ "description": "A logger setting" }, "loggerSettingHideColumnsPrompt": { - "message": "Hide columns:", + "message": "Cuddio colofnau:", "description": "Logger settings: a sentence to describe the purpose of the checkboxes below" }, "loggerSettingHideColumnTime": { - "message": "{{input}} Time", + "message": "{{input}} Amser", "description": "A label for the time column" }, "loggerSettingHideColumnFilter": { - "message": "{{input}} Filter/rule", + "message": "{{input}} Hidlydd/rheol", "description": "A label for the filter or rule column" }, "loggerSettingHideColumnContext": { - "message": "{{input}} Context", + "message": "{{input}} Cyd-destun", "description": "A label for the context column" }, "loggerSettingHideColumnPartyness": { @@ -868,15 +880,15 @@ "description": "A label for the partyness column" }, "loggerExportFormatList": { - "message": "List", + "message": "Rhestr", "description": "Label for radio-button to pick export format" }, "loggerExportFormatTable": { - "message": "Table", + "message": "Tabl", "description": "Label for radio-button to pick export format" }, "loggerExportEncodePlain": { - "message": "Plain", + "message": "Plaen", "description": "Label for radio-button to pick export text format" }, "loggerExportEncodeMarkdown": { @@ -884,19 +896,19 @@ "description": "Label for radio-button to pick export text format" }, "supportOpenButton": { - "message": "Open", + "message": "Agor", "description": "Text for button which open an external webpage in Support pane" }, "supportReportSpecificButton": { - "message": "Create new report", + "message": "Creu adroddiad newydd", "description": "Text for button which open an external webpage in Support pane" }, "supportFindSpecificButton": { - "message": "Find similar reports", + "message": "Canfod adroddiadau tebyg", "description": "A clickable link in the filter issue reporter section" }, "supportS1H": { - "message": "Documentation", + "message": "Dogfennaeth", "description": "Header of 'Documentation' section in Support pane" }, "supportS1P1": { @@ -904,7 +916,7 @@ "description": "First paragraph of 'Documentation' section in Support pane" }, "supportS2H": { - "message": "Questions and support", + "message": "Cwestiynau a chymorth", "description": "Header of 'Questions and support' section in Support pane" }, "supportS2P1": { @@ -948,7 +960,7 @@ "description": "Second paragraph of 'Troubleshooting Information' section in Support pane" }, "supportS6H": { - "message": "Report a filter issue", + "message": "Adrodd nam ar hidlydd", "description": "Header of 'Report a filter issue' section in Support pane" }, "supportS6P1S1": { @@ -964,11 +976,11 @@ "description": "A paragraph in the filter issue reporter section" }, "supportS6URL": { - "message": "Address of the web page:", + "message": "Cyfeiriad y dudalen we:", "description": "Label for the URL of the page" }, "supportS6Select1": { - "message": "The web page…", + "message": "Y dudalen we…", "description": "Label for widget to select type of issue" }, "supportS6Select1Option0": { @@ -1012,7 +1024,7 @@ "description": "Text for 'Unredact' button" }, "aboutPrivacyPolicy": { - "message": "Privacy policy", + "message": "Polisi preifatrwydd", "description": "Link to privacy policy on GitHub (English)" }, "aboutChangelog": { @@ -1020,23 +1032,23 @@ "description": "" }, "aboutCode": { - "message": "Source code (GPLv3)", + "message": "Cod ffynhonnell (GPLv3)", "description": "English: Source code (GPLv3)" }, "aboutContributors": { - "message": "Contributors", + "message": "Cyfranwyr", "description": "English: Contributors" }, "aboutSourceCode": { - "message": "Source code", + "message": "Cod ffynhonnell", "description": "Link text to source code repo" }, "aboutTranslations": { - "message": "Translations", + "message": "Cyfieithiadau", "description": "Link text to translations repo" }, "aboutFilterLists": { - "message": "Filter lists", + "message": "Rhestri hidlo", "description": "Link text to uBO's own filter lists repo" }, "aboutDependencies": { @@ -1088,39 +1100,39 @@ "description": "No longer used" }, "subscribeButton": { - "message": "Subscribe", + "message": "Tanysgrifio", "description": "For the button used to subscribe to a filter list" }, "elapsedOneMinuteAgo": { - "message": "a minute ago", + "message": "funud yn ôl", "description": "English: a minute ago" }, "elapsedManyMinutesAgo": { - "message": "{{value}} minutes ago", + "message": "{{value}} o funudau yn ôl", "description": "English: {{value}} minutes ago" }, "elapsedOneHourAgo": { - "message": "an hour ago", + "message": "awr yn ôl", "description": "English: an hour ago" }, "elapsedManyHoursAgo": { - "message": "{{value}} hours ago", + "message": "{{value}} awr yn ôl", "description": "English: {{value}} hours ago" }, "elapsedOneDayAgo": { - "message": "a day ago", + "message": "ddiwrnod yn ôl", "description": "English: a day ago" }, "elapsedManyDaysAgo": { - "message": "{{value}} days ago", + "message": "{{value}} o ddiwrnodau yn ôl", "description": "English: {{value}} days ago" }, "showDashboardButton": { - "message": "Show Dashboard", + "message": "Dangos y dashfwrdd", "description": "Firefox/Fennec-specific: Show Dashboard" }, "showNetworkLogButton": { - "message": "Show Logger", + "message": "Dangos y logiwr", "description": "Firefox/Fennec-specific: Show Logger" }, "fennecMenuItemBlockingOff": { @@ -1148,11 +1160,11 @@ "description": "English: List of filter list names follows" }, "docblockedBack": { - "message": "Go back", + "message": "Yn ôl", "description": "English: Go back" }, "docblockedClose": { - "message": "Close this window", + "message": "Cau'r ffenestr hon", "description": "English: Close this window" }, "docblockedDontWarn": { @@ -1164,15 +1176,15 @@ "description": "English: Disable strict blocking for {{hostname}} ..." }, "docblockedDisableTemporary": { - "message": "Temporarily", + "message": "Dros dro", "description": "English: Temporarily" }, "docblockedDisablePermanent": { - "message": "Permanently", + "message": "Yn barhaol", "description": "English: Permanently" }, "docblockedDisable": { - "message": "Proceed", + "message": "Parhau", "description": "Button text to navigate to the blocked page" }, "cloudPush": { @@ -1192,7 +1204,7 @@ "description": "" }, "cloudDeviceNamePrompt": { - "message": "This device name:", + "message": "Enw'r ddyfais hon:", "description": "used as a prompt for the user to provide a custom device name" }, "advancedSettingsWarning": { @@ -1200,7 +1212,7 @@ "description": "A warning to users at the top of 'Advanced settings' page" }, "genericSubmit": { - "message": "Submit", + "message": "Cyflwyno", "description": "for generic 'Submit' buttons" }, "genericApplyChanges": { @@ -1228,7 +1240,7 @@ "description": "A context menu entry, present when large media elements have been blocked on the current site" }, "contextMenuViewSource": { - "message": "View source code…", + "message": "Gweld y cod ffynhonnell…", "description": "A context menu entry, to view the source code of the target resource" }, "shortcutCapturePlaceholder": { @@ -1240,23 +1252,27 @@ "description": "Tooltip for the button used to lock scrolling between the views in the 'My rules' pane" }, "genericCopyToClipboard": { - "message": "Copy to clipboard", + "message": "Copïo i'r clipfwrdd", "description": "Label for buttons used to copy something to the clipboard" }, "genericSelectAll": { - "message": "Select all", + "message": "Dewis y cyfan", "description": "Label for buttons used to select all text in editor" }, "toggleCosmeticFiltering": { - "message": "Toggle cosmetic filtering", + "message": "Toglo hidlo arwynebol", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toglo JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" }, "storageUsed": { - "message": "Storage used: {{value}} {{unit}}", + "message": "Storfa: {{value}} {{unit}}", "description": " In Setting pane, renders as (example): Storage used: 13.2 MB" }, "KB": { @@ -1276,7 +1292,7 @@ "description": "Message used in frame placeholders" }, "linterMainReport": { - "message": "Errors: {{count}}", + "message": "Gwallau: {{count}}", "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { diff --git a/src/_locales/da/messages.json b/src/_locales/da/messages.json index 7714874..061e781 100644 --- a/src/_locales/da/messages.json +++ b/src/_locales/da/messages.json @@ -483,6 +483,14 @@ "message": "Malware-beskyttelse, sikkerhed", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sociale widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie-meddelelser", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Gener", "description": "Filter lists section name" @@ -527,20 +535,24 @@ "message": "En netværksfejl forhindrede opdatering af ressourcen.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Ét filter pr. linje. Et filter kan være et almindeligt værtsnavn eller et EasyList-kompatibelt filter. Linjer startende med <code>!</code> ignoreres.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Tilføj ikke filtre fra ikke-betroede kilder.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Aktivér mine tilpassede filtre", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Tillad tilpassede filtre som kræver tillid", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { - "message": "Importer og tilføj…", + "message": "Importér og tilføj…", "description": "Button in the 'My filters' pane" }, "1pExport": { - "message": "Eksporter…", + "message": "Eksportér…", "description": "Button in the 'My filters' pane" }, "1pExportFilename": { @@ -584,7 +596,7 @@ "description": "" }, "rulesExport": { - "message": "Eksporter til fil…", + "message": "Eksportér til fil…", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { @@ -620,11 +632,11 @@ "description": "A concise description of the 'Trusted sites' pane." }, "whitelistImport": { - "message": "Importer og tilføj…", + "message": "Importér og tilføj…", "description": "Button in the 'Trusted sites' pane" }, "whitelistExport": { - "message": "Eksporter…", + "message": "Eksportér…", "description": "Button in the 'Trusted sites' pane" }, "whitelistExportFilename": { @@ -1251,6 +1263,10 @@ "message": "Kosmetisk filtrering til/fra", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript til/fra", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Lemp blokeringstilstand", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json index 80e8706..4736735 100644 --- a/src/_locales/de/messages.json +++ b/src/_locales/de/messages.json @@ -168,7 +168,7 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { - "message": "Externe Schriftarten für diese Website zulassen/blockieren", + "message": "Externe Schriftarten für diese Website blockieren/zulassen", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { @@ -320,7 +320,7 @@ "description": "English: Hide placeholders of blocked elements" }, "settingsIconBadgePrompt": { - "message": "Anzahl der blockierten Anfragen auf dem Symbol anzeigen", + "message": "Anzahl der blockierten Anfragen auf dem Symbol in der Symbolleiste anzeigen", "description": "English: Show the number of blocked requests on the icon" }, "settingsTooltipsPrompt": { @@ -483,6 +483,14 @@ "message": "Schutz vor Schadsoftware, Sicherheit", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Soziale Widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie-Hinweise", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Belästigungen", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Ein Netzwerkfehler verhinderte die Aktualisierung der Ressource.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Ein Filter pro Zeile. Ein Filter kann ein einfacher Hostname oder ein EasyList-kompatibler Filter sein. Zeilen mit vorangestelltem <code>!</code> werden ignoriert.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Verwenden Sie keine Filter aus unseriösen Quellen.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Eigene Filter aktivieren", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Eigene Filter zulassen, die Vertrauen erfordern", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importieren und anfügen …", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Kosmetisches Filtern ein-/ausschalten", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript aktivieren/deaktivieren", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Blockiermodus lockern", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/el/messages.json b/src/_locales/el/messages.json index cb8595c..ea6a4a6 100644 --- a/src/_locales/el/messages.json +++ b/src/_locales/el/messages.json @@ -483,6 +483,14 @@ "message": "Τομείς κακόβουλου λογισμικού", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Γραφικά στοιχεία κοινωνικής δικτύωσης", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Ειδοποιήσεις για cookies", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ενοχλήσεις", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Ένα σφάλμα δικτύου εμπόδισε την ενημέρωση του πόρου.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Ένα φίλτρο ανά γραμμή. Ένα φίλτρο μπορεί να είναι ένα απλό όνομα κεντρικού υπολογιστή ή ένα φίλτρο συμβατό με την EasyList. Οι γραμμές με πρόθεμα <code>!</code> θα παραβλέπονται.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Να μην προστίθενται φίλτρα από μη αξιόπιστες πηγές.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Ενεργοποίηση των προσαρμοσμένων φίλτρων μου", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Επιτρέψτε προσαρμοσμένα φίλτρα που απαιτούν εμπιστοσύνη", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Εισαγωγή και προσάρτηση", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Εναλλαγή διακοσμητικού φιλτραρίσματος", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Εναλλαγή JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Χαλάρωση κατάστασης φραγής", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index b894121..92fb260 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" @@ -1253,6 +1265,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/en_GB/messages.json b/src/_locales/en_GB/messages.json index cb79ea5..8fd9b46 100644 --- a/src/_locales/en_GB/messages.json +++ b/src/_locales/en_GB/messages.json @@ -483,6 +483,14 @@ "message": "Malware domains", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/eo/messages.json b/src/_locales/eo/messages.json index 6e63e04..1d69a1f 100644 --- a/src/_locales/eo/messages.json +++ b/src/_locales/eo/messages.json @@ -452,11 +452,11 @@ "description": "This will cause uBO to ignore all generic cosmetic filters." }, "3pIgnoreGenericCosmeticFiltersInfo": { - "message": "Generic cosmetic filters are those cosmetic filters which are meant to apply on all web sites. Enabling this option will eliminate the memory and CPU overhead added to web pages as a result of handling generic cosmetic filters.\n\nIt is recommended to enable this option on less powerful devices.", + "message": "Ĝenerikaj kozmetikaj filtriloj estas tiuj kozmetikaj filtriloj, kiuj estas intencitaj esti aplikitaj sur ĉiuj retejoj. Aktivigante tiun opcion, oni eliminigos la memoran kaj procesorecan ŝarĝon, kiu estas aldonita al retpaĝoj pro la pritraktado de ĝenerikaj kozmetikaj filtriloj.\n\nEstas rekomendite aktivigi tiun opcion sur aparatoj ne estas potencaj.", "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pSuspendUntilListsAreLoaded": { - "message": "Suspend network activity until all filter lists are loaded", + "message": "Suspendu retan aktivecon ĝis ĉiuj filtraj listoj estas ŝargitaj.", "description": "A checkbox in the 'Filter lists' pane" }, "3pListsOfBlockedHostsHeader": { @@ -483,6 +483,14 @@ "message": "Domajno kun fiprogramaro", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ĝenoj", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Reta eraro malhelpis ĝisdatigon de la resurso.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Unu filtrilo por ĉiu linio. Filtrilo povas esti ordinara gastignomo aŭ Adblock Plus-kongrua filtrilo. Prefiksitaj linioj kun ‘!’ estos ignorataj.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { - "message": "Do not add filters from untrusted sources.", + "message": "Ne aldonu filtrilojn el ne fidindaj fontoj.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importi kaj postaldoni", "description": "Button in the 'My filters' pane" @@ -824,11 +836,11 @@ "description": "Below this sentence, the filter list(s) in which the filter was found" }, "loggerStaticFilteringFinderSentence2": { - "message": "Static filter could not be found in any of the currently enabled filter lists", + "message": "Stata filtro ne povis esti trovita en iu ajn el la nuntempe aktivigitaj filtro-listoj.", "description": "Message to show when a filter cannot be found in any filter lists" }, "loggerSettingDiscardPrompt": { - "message": "Logger entries which do not fulfill all three conditions below will be automatically discarded:", + "message": "Logeto-enskriboj, kiuj ne plenumas ĉiujn tri kondiĉojn sube, estos aŭtomate forĵetitaj:", "description": "Logger setting: A sentence to describe the purpose of the settings below" }, "loggerSettingPerEntryMaxAge": { @@ -1251,6 +1263,10 @@ "message": "Baskuligi ornaman filtradon", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Baskuli Javascript-kodoj", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Malstriktigi blokadan reĝimon", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json index 17652b9..55bd2e9 100644 --- a/src/_locales/es/messages.json +++ b/src/_locales/es/messages.json @@ -483,6 +483,14 @@ "message": "Protección de malware, seguridad", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Widgets sociales", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Avisos de cookies", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elementos molestos", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Un error de red impide que se actualicen los recursos.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Un filtro por línea. El filtro puede ser un nombre de dominio, o un filtro compatible con EasyList. Las líneas que comiencen con <code>!</code> serán ignoradas.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "No añadir filtros de fuentes no confiables.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Habilitar mis filtros personalizados", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Permitir filtros personalizados que requieran confianza", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importar y anexar…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Alternar filtrado cosmético", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Alternar JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Modo de bloqueo relajado", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/et/messages.json b/src/_locales/et/messages.json index 07d5abd..5ab8cc9 100644 --- a/src/_locales/et/messages.json +++ b/src/_locales/et/messages.json @@ -483,6 +483,14 @@ "message": "Pahavara kaitse, turvalisus", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sotsiaalvõrgustike vidinad", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Küpsise teatised", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Tüütused", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Ressursi uuendamist takistas võrgu viga.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Üks filter rea kohta. Filtriks võib olla tavaline hostinimi või EasyListiga ühilduv filter. Eesliitega <code>!</code> algavaid ridu eiratakse.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Ära lisa filtreid tundmatutest allikatest.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Luba enda loodud filtrid", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Luba usaldusväärsust tõestama peavad enda loodud filtrid", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Impordi ja lisa…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Lülita kosmeetilist filtreerimist", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Lülita JavaScripti", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Rahulik blokeerimisrežiim", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/eu/messages.json b/src/_locales/eu/messages.json index 051bfd9..f5028e5 100644 --- a/src/_locales/eu/messages.json +++ b/src/_locales/eu/messages.json @@ -483,6 +483,14 @@ "message": "Malware domeinuak", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Eragozpenak", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Sare errore batek baliabidea eguneratzea eragotzi du.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Iragazki bat lerroko. Iragazkia hostalari izen soila izan daiteke, edo Adblock Plusekin bateragarria den iragazki bat. Hasieran <code>!</code> duten lerroak ezikusiko dira.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { - "message": "Do not add filters from untrusted sources.", + "message": "Ez gehitu jatorri ezezaguneko iragazkirik", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Inportatu eta gehitu", "description": "Button in the 'My filters' pane" @@ -956,11 +968,11 @@ "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S1": { - "message": "Filter lists are updated daily. Be sure your issue has not already been addressed in the most recent filter lists.", + "message": "Iragazki-zerrendak egunero eguneratzen dira. Begiratu zure kasua azken iragazki-zerrendetako baten konpondu den.", "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S2": { - "message": "Verify that the issue still exists after reloading the problematic webpage.", + "message": "Egiaztatu zure kasua arazoak eman dizkizun webgunea berriz kargatu ostean oraindik ere gertatzen den.", "description": "A paragraph in the filter issue reporter section" }, "supportS6URL": { @@ -1251,6 +1263,10 @@ "message": "Iragazte kosmetikoa txandakatu", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScripta aktibatu/desaktibatu", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Baretu blokeatze-modua", "description": "Label for keyboard shortcut used to relax blocking mode" @@ -1280,7 +1296,7 @@ "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { - "message": "Could not filter properly at browser launch. Reload the page to ensure proper filtering.", + "message": "Iragazkia ezin izan da kargatu nabigatzailea irekitzeak. Kargatu berriz orria iragazkiak ondo funtzionatzen duela ziurtatzeko.", "description": "A warning which will appear in the popup panel if needed" }, "dummy": { diff --git a/src/_locales/fa/messages.json b/src/_locales/fa/messages.json index 7736f8d..ef05c73 100644 --- a/src/_locales/fa/messages.json +++ b/src/_locales/fa/messages.json @@ -483,6 +483,14 @@ "message": "دامنه های مخرب", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "اطلاعیههای کلوچک", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "مزاحمها", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "یک خطای شبکه از بروزشدن این منبع جلوگیری کرد.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "یک فیلتر در هر خط. یک فیلتر می تواند آدرس هاست ساده یا فیلتر سازگار با Adblock plus باشد. خطوط با پیشوند ‘!’ نادیده گرفته می شوند.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { - "message": "Do not add filters from untrusted sources.", + "message": "پالایهها از منابع نامعتبر افزوده نشود.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "فعالسازی پالایههای سفارشی من", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "اجازهدهی به پالایههای سفارشی نیازمند اعتمادسازی ", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "وارد کردن و الحاق", "description": "Button in the 'My filters' pane" @@ -956,7 +968,7 @@ "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S1": { - "message": "Filter lists are updated daily. Be sure your issue has not already been addressed in the most recent filter lists.", + "message": "فهرستهای پالایش روزانه بهروز میشوند. مطمئن شوید که مشکل شما پیشتر در فهرستهای پالایش اخیر بررسی نشده باشد.", "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S2": { @@ -1128,7 +1140,7 @@ "description": "Firefox-specific: appears as 'uBlock₀ (off)'" }, "docblockedTitle": { - "message": "Page blocked", + "message": "صفحه مسدود شده است", "description": "Used as a title for the document-blocked page" }, "docblockedPrompt1": { @@ -1156,7 +1168,7 @@ "description": "English: Close this window" }, "docblockedDontWarn": { - "message": "Don't warn me again about this site", + "message": "دیگر در مورد این تارنما هشداری به من داده نشود", "description": "Label for checkbox in document-blocked page" }, "docblockedProceed": { @@ -1172,7 +1184,7 @@ "description": "English: Permanently" }, "docblockedDisable": { - "message": "Proceed", + "message": "ادامه", "description": "Button text to navigate to the blocked page" }, "cloudPush": { @@ -1228,7 +1240,7 @@ "description": "A context menu entry, present when large media elements have been blocked on the current site" }, "contextMenuViewSource": { - "message": "View source code…", + "message": "مشاهده کد منبع…", "description": "A context menu entry, to view the source code of the target resource" }, "shortcutCapturePlaceholder": { @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "تغییر وضعیت جاوا اسکریپت", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "حالت بلاککردن غیرسختگیر", "description": "Label for keyboard shortcut used to relax blocking mode" @@ -1276,7 +1292,7 @@ "description": "Message used in frame placeholders" }, "linterMainReport": { - "message": "Errors: {{count}}", + "message": "خطا: {{count}}", "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { diff --git a/src/_locales/fi/messages.json b/src/_locales/fi/messages.json index fb3028e..da52acf 100644 --- a/src/_locales/fi/messages.json +++ b/src/_locales/fi/messages.json @@ -483,6 +483,14 @@ "message": "Haittaohjelmasuojaus, tietoturva", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sosiaaliset widgetit", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Evästeilmoitukset", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ärsykkeet", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Verkkovirhe esti resurssin päivityksen.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Yksi suodatin riviä kohden. Suodatin voi olla pelkkä osoite tai EasyList-yhteensopiva suodatin. Rivit, joiden alussa on <code>!</code>, ohitetaan.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Älä lisää suodattimia lähteistä, joihin et luota.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Ota omat suodattimet käyttöön", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Tuo ja lisää…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Kytke kosmeettinen suodatus", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Kytke JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Kevennetty estotila", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/fil/messages.json b/src/_locales/fil/messages.json index e5b679d..d38e062 100644 --- a/src/_locales/fil/messages.json +++ b/src/_locales/fil/messages.json @@ -483,6 +483,14 @@ "message": "Mga domain na may malware", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Mga nakakaabalang bagay", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Hindi na-update ang resource dahil sa isang network error.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Isang filter kada linya. Pwedeng simpleng hostname o EasyList-compatible ang filter. Hindi papansinin ang mga linyang nagsisimula sa <code>!</code>.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Huwag magdagdag ng pangsala mula sa mga hindi katiwa-tiwalang pinagmulan.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "I-import at idagdag", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Paandarin/patayin ang kosmetikong pagfi-filter", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Babaan ang lebel ng pagharang", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json index 8b8cfc5..fda2589 100644 --- a/src/_locales/fr/messages.json +++ b/src/_locales/fr/messages.json @@ -483,6 +483,14 @@ "message": "Protection anti-malware et sécurité", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Widgets de réseaux sociaux", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Bannières de cookie", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Nuisances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Une erreur réseau a empêché la mise à jour de la ressource.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Une règle par ligne. Une règle peut être un simple nom d'hôte, ou encore une règle compatible EasyList. Les lignes débutant par <code>!</code> seront ignorées.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Il est recommandé de ne pas ajouter de filtres en provenance de sources non fiables.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Activer mes filtres personnalisés", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Faire confiance aux filtres personnalisés", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importer", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Commuter le filtrage esthétique", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Commuter JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Détendre le mode de blocage", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/fy/messages.json b/src/_locales/fy/messages.json index 886b764..ef97ec1 100644 --- a/src/_locales/fy/messages.json +++ b/src/_locales/fy/messages.json @@ -483,6 +483,14 @@ "message": "Malwaredomeinen", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sosjale widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookiemeldingen", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ungeriif", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "In netwurkflater hat opkeard dat de boarne bywurke waard.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Ien filter per rigel. In filter kin in gewoane hostnamme of in Adblock Plus-kompatibel filter wêze. Rigels begjinnend mei <code>!</code> wurde negearre.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Foegje gjin filters fan ûnbekende boarnen ta.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Myn oanpaste filters ynskeakelje", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Oanpaste filters dy’t fertrouwen fereaskje tastean", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Ymportearje en tafoegje", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Kosmetyske filters yn-/útskeakelje", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript yn-/útskeakelje", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Blokkearringsmodus beheine", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/gl/messages.json b/src/_locales/gl/messages.json index b97b2d5..291dc09 100644 --- a/src/_locales/gl/messages.json +++ b/src/_locales/gl/messages.json @@ -483,6 +483,14 @@ "message": "Dominios de <i>malware</i>", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Widgets sociais", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Avisos de rastro", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Molestias", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Un erro de rede está a impedir que se actualicen os recursos.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Un filtro por cada liña. O filtro pode ser un nome de servidor, ou un filtro compatible co Adblock Plus. As liñas que comecen con <code>!</code> seranche ignoradas.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Non engadir filtros de fontes non confiables.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Activar os meus filtros personais", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Requerir confiar nos filtros personalizados", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importar e engadir…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Activar filtrado cosmético", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Alternar JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relaxar o modo de bloqueo", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/gu/messages.json b/src/_locales/gu/messages.json index 3e3b5db..9db2f73 100644 --- a/src/_locales/gu/messages.json +++ b/src/_locales/gu/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/he/messages.json b/src/_locales/he/messages.json index 3bc7bd2..b036e93 100644 --- a/src/_locales/he/messages.json +++ b/src/_locales/he/messages.json @@ -480,7 +480,15 @@ "description": "Filter lists section name" }, "3pGroupMalware": { - "message": "ביטחון והגנה מפני נוזקה", + "message": "הגנה מפני נוזקות, אבטחה", + "description": "Filter lists section name" + }, + "3pGroupSocial": { + "message": "יישומונים חברתיים", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "הודעות בקשר לעוגיות", "description": "Filter lists section name" }, "3pGroupAnnoyances": { @@ -527,20 +535,24 @@ "message": "בעיית רשת מנעה מהמשאב להתעדכן.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "מסנן אחד לכל שורה. המסנן יכול להיות דומיין פשוט, או מסנן התואם ל- EasyList. שורות עם קידומת <code>!</code> לא יפורשו.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { - "message": "Do not add filters from untrusted sources.", + "message": "אל תוסיף מסננים ממקורות לא מהימנים.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "אפשור מסננים מותאמים אישית", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "אפשור למסננים מותאמים אישית לדרוש אמון", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { - "message": "ייבא וצרף", + "message": "ייבא וצרף…", "description": "Button in the 'My filters' pane" }, "1pExport": { - "message": "ייצוא", + "message": "ייצוא…", "description": "Button in the 'My filters' pane" }, "1pExportFilename": { @@ -584,7 +596,7 @@ "description": "" }, "rulesExport": { - "message": "ייצא לקובץ...", + "message": "ייצא לקובץ…", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { @@ -620,11 +632,11 @@ "description": "A concise description of the 'Trusted sites' pane." }, "whitelistImport": { - "message": "ייבא וצרף", + "message": "ייבא וצרף…", "description": "Button in the 'Trusted sites' pane" }, "whitelistExport": { - "message": "ייצוא", + "message": "ייצוא…", "description": "Button in the 'Trusted sites' pane" }, "whitelistExportFilename": { @@ -1251,6 +1263,10 @@ "message": "הפעל/כבה מסננים קוסמטיים", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "מיתוג JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "הרפה את מצב החסימה הנוכחי", "description": "Label for keyboard shortcut used to relax blocking mode" @@ -1280,7 +1296,7 @@ "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { - "message": "לא ניתן היה לסנן כראוי בעת הפעלת הדפדפן. טען מחדש את הדף כדי להבטיח סינון מתאים.", + "message": "לא ניתן היה לסנן כראוי בעת הפעלת הדפדפן. נא לטעון את העמוד מחדש להבטחת סינון ראוי.", "description": "A warning which will appear in the popup panel if needed" }, "dummy": { diff --git a/src/_locales/hi/messages.json b/src/_locales/hi/messages.json index 0671acb..9a596a8 100644 --- a/src/_locales/hi/messages.json +++ b/src/_locales/hi/messages.json @@ -4,7 +4,7 @@ "description": "extension name." }, "extShortDesc": { - "message": "आख़िरकार, क्रोमियम-बेस्ड ब्राउज़रों के लिए एक कुशल अवरोधक। CPU और स्मृति पर आसान।", + "message": "आख़िरकार, क्रोमियम-बेस्ड ब्राउज़रों के लिए एक कुशल अवरोधक। सीपीयू और मेमोरी पर कम भार के साथ।\n", "description": "this will be in the Chrome web store: must be 132 characters or less" }, "dashboardName": { @@ -16,7 +16,7 @@ "description": "A warning in the dashboard when navigating away from unsaved changes" }, "dashboardUnsavedWarningStay": { - "message": "रुकें", + "message": "यहाँ रुकें", "description": "Label for button to prevent navigating away from unsaved changes" }, "dashboardUnsavedWarningIgnore": { @@ -36,7 +36,7 @@ "description": "appears as tab name in dashboard" }, "rulesPageName": { - "message": "मेरे नियम", + "message": "मेरे नियमों", "description": "appears as tab name in dashboard" }, "whitelistPageName": { @@ -240,7 +240,7 @@ "description": "" }, "popup3pAnyRulePrompt": { - "message": "तृतीय-पक्ष", + "message": "तीसरे पक्ष", "description": "" }, "popup3pPassiveRulePrompt": { @@ -352,7 +352,7 @@ "description": "" }, "settingsAdvancedUserPrompt": { - "message": "मैं एक उच्च उपयोगकर्ता हूँ (<a href='https://github.com/gorhill/uBlock/wiki/Advanced-user-features'>आवश्यक पठन</a>)", + "message": "मैं एक उन्नत प्रयोक्ता हूँ", "description": "Checkbox to let user access advanced, technical features" }, "settingsPrefetchingDisabledPrompt": { @@ -483,6 +483,14 @@ "message": "मैलवेयर डोमेन", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "सोशल विज़ेट्स", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "कुकी सूचनाएं", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "सतानेवाले विज्ञापन", "description": "Filter lists section name" @@ -500,7 +508,7 @@ "description": "Filter lists section name" }, "3pImport": { - "message": "इम्पोर्ट ", + "message": "इम्पोर्ट...", "description": "The label for the checkbox used to import external filter lists" }, "3pExternalListsHint": { @@ -516,7 +524,7 @@ "description": "used as a tooltip for eye icon beside a list" }, "3pLastUpdate": { - "message": "अन्तिम अद्यातन: {{ago}}. कृत्रिम नवीकरण के लिए क्लिक की जिए", + "message": "अन्तिम अद्यातन: {{ago}}. \nकृत्रिम नवीकरण के लिए क्लिक की जिए", "description": "used as a tooltip for the clock icon beside a list" }, "3pUpdating": { @@ -527,14 +535,18 @@ "message": "एक नेटवर्क त्रुटि ने रिसोर्स को अपडेट होने से रोक दिया। ", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "एक लाइन में एक फ़िल्टर। एक फ़िल्टर सीधा होस्टनाम, या एक Adblock Plus- योग्य फ़िल्टर हो सकता है। <code>!<code></code>से शुरू होने वाली लाइनों को नज़रअंदाज किया जायेगा।", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "अविश्वसनीय स्रोतों से फ़िल्टर न जोड़ें.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "मेरे कस्टम फ़िल्टर सक्षम करें", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "विश्वास की आवश्यकता वाले कस्टम फ़िल्टर की अनुमति दें", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "इम्पोर्ट करें और जोड़ें", "description": "Button in the 'My filters' pane" @@ -584,7 +596,7 @@ "description": "" }, "rulesExport": { - "message": "फाइल में एक्सपोर्ट करें ", + "message": "फाइल में एक्सपोर्ट करें...", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { @@ -1080,7 +1092,7 @@ "description": "Message asking user to confirm reset" }, "errorCantConnectTo": { - "message": "नेटवर्क त्रुटि : {{msg}}", + "message": "नेटवर्क त्रुटि: {{msg}}", "description": "English: Network error: {{msg}}" }, "subscriberConfirm": { @@ -1088,19 +1100,19 @@ "description": "No longer used" }, "subscribeButton": { - "message": "सबˈस्क्राइब्", + "message": "फ़िल्टर सूची का उपयोग करें", "description": "For the button used to subscribe to a filter list" }, "elapsedOneMinuteAgo": { - "message": "एक मिनट पहले", + "message": "एक मिनट पेहेले ", "description": "English: a minute ago" }, "elapsedManyMinutesAgo": { - "message": "{{value}} मिनट पहले", + "message": "{{value}} मिनट पेहेले ", "description": "English: {{value}} minutes ago" }, "elapsedOneHourAgo": { - "message": "एक घंटे पहले", + "message": "एक घंटे पेहेले ", "description": "English: an hour ago" }, "elapsedManyHoursAgo": { @@ -1148,7 +1160,7 @@ "description": "English: List of filter list names follows" }, "docblockedBack": { - "message": "वापस जाएँ", + "message": "पीछे जाएं", "description": "English: Go back" }, "docblockedClose": { @@ -1172,7 +1184,7 @@ "description": "English: Permanently" }, "docblockedDisable": { - "message": "आगे बढ़ें", + "message": "आगे बढ़ें", "description": "Button text to navigate to the blocked page" }, "cloudPush": { @@ -1251,6 +1263,10 @@ "message": "कॉस्मेटिक फ़िल्टरिंग टॉगल करें", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "जावास्क्रिप्ट टॉगल करें", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "ब्लैकिंग मोड को ढील दें", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/hr/messages.json b/src/_locales/hr/messages.json index edceb34..bb5ed41 100644 --- a/src/_locales/hr/messages.json +++ b/src/_locales/hr/messages.json @@ -483,6 +483,14 @@ "message": "Zaštita od zlonamjernog softvera, sigurnost", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Socijalni widgeti", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Obavijest o kolačićima", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Nametljivost", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Mrežna pogreška je sprječila ažuriranje resursa.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Jedan filter po retku. Filter može biti običan hostname ili filter kompatibilan sa EasyList-om. Linije sa prefiksom <code>!</code> zanemarit će se.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Nemojte dodavati filtere iz nepouzdanih izvora.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Omogući moje prilagođene filtre", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Dopusti prilagođene filtre koji zahtijevaju povjerenje", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Uvesti i dodati...", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Prekidač estetskog filtriranja", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Uključi/isključi JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Opušteni način blokiranja", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/hu/messages.json b/src/_locales/hu/messages.json index f4c8e47..b41a850 100644 --- a/src/_locales/hu/messages.json +++ b/src/_locales/hu/messages.json @@ -483,6 +483,14 @@ "message": "Malware domainek", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Közösségi widgetek", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Sütiértesítések", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Kellemetlenségek", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Egy hálózati hiba megakadályozta az erőforrás frissítését.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Soronként egy szűrő. A szűrő lehet egy hostnév, vagy egy Adblock Plus kompatibilis szűrő.\nA <code>!</code> kezdetű sorok figyelmen kívül maradnak.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Ne adj hozzá szűrőket megbízhatatlan forrásokból.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Egyedi szűrőim engedélyezése", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Megbízhatóságot igénylő egyéni szűrők engedélyezése", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importál és hozzáad", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Kozmetikai szűrés kapcsolása", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Javascript ki/bekapcsolása", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relaxáló blokkolási mód", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/hy/messages.json b/src/_locales/hy/messages.json index 291d67c..7910731 100644 --- a/src/_locales/hy/messages.json +++ b/src/_locales/hy/messages.json @@ -483,6 +483,14 @@ "message": "Վնասակար տիրույթներ", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ջղայնացնող տարրեր", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Ցանցի սխալի պատճառով թարմացումը տեղի չունեցավ։", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Մեկ տողում մեկ զտիչ։ Որպես զտիչ կարող է լինել կայքի անուն կամ EasyList-համաեղելի զտիչ։ <code>! </code>-ով սկսվող տողերը կանտեսվեն։", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Մի՛ ավելացրեք զտիչներ անվստահելի աղբյուրներից։", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Ներմուծել և հավելել", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Փոխանջատել կոսմետիկ զտումը", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Արգելափակման թուլացված ռեժիմ", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/id/messages.json b/src/_locales/id/messages.json index 21629c1..684fcc6 100644 --- a/src/_locales/id/messages.json +++ b/src/_locales/id/messages.json @@ -483,6 +483,14 @@ "message": "Perlindungan malware, keamanan", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Gangguan", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Galat jaringan mencegah sumber daya diperbarui.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Satu filter per baris. Filter dapat berupa nama hos, atau filter yang kompatibel dengan EasyList. Baris yang diawali dengan <code>!</code> akan diabaikan.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Jangan tambah filter dari sumber yang tidak tepercaya.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Impor dan tambahkan…", "description": "Button in the 'My filters' pane" @@ -704,7 +716,7 @@ "description": "Tooltip for the button to bring up logger output filtering options" }, "loggerRowFiltererBuiltinNot": { - "message": "Bukan", + "message": "Tidak", "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltinEventful": { @@ -1251,6 +1263,10 @@ "message": "Aktif/Nonaktifkan filter tampilan", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Aktif/Nonaktifkan JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Perlonggar mode pemblokiran", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/it/messages.json b/src/_locales/it/messages.json index 000b19e..c87a90f 100644 --- a/src/_locales/it/messages.json +++ b/src/_locales/it/messages.json @@ -40,7 +40,7 @@ "description": "appears as tab name in dashboard" }, "whitelistPageName": { - "message": "Whitelist", + "message": "Siti affidabili", "description": "appears as tab name in dashboard" }, "shortcutsPageName": { @@ -483,6 +483,14 @@ "message": "Domini con Malware", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Widget sociali", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Avviso sui cookie", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elementi fastidiosi", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Un errore di rete ha impedito l'aggiornamento della risorsa.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Un filtro per riga. Un filtro può essere un semplice hostname, o un filtro compatibile con EasyList. Ogni riga che comincia con <code>!</code> verrà ignorata.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Non aggiungere filtri da fonti non attendibili.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Abilita i miei filtri personalizzati", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Consenti i filtri personalizzati che richiedono affidabilità", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importa e aggiungi", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Attiva/Disattiva filtri cosmetici", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Attiva o disattiva JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Rilassa la modalità di blocco", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ja/messages.json b/src/_locales/ja/messages.json index 0293e53..add025f 100644 --- a/src/_locales/ja/messages.json +++ b/src/_locales/ja/messages.json @@ -483,6 +483,14 @@ "message": "マルウェアドメイン", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "SNS ウィジェット", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "クッキー通知", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "迷惑系", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "ネットワークエラーが発生したため、リソースを更新できませんでした。", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "1 行につき 1 つのフィルターです。フィルターはただのホスト名でも EasyList と同じ形式でも構いません。<code>!</code> を先頭に付けた行は無視されます。", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "信頼できないソースからフィルターを追加しないでください。", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "マイカスタムフィルターを有効化", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "信頼が必要なカスタムフィルターを許可する", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "インポートと追加", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "整形フィルタリングの有効/無効を切り替える", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript の切り替え", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "ブロッキングモードを緩和する", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ka/messages.json b/src/_locales/ka/messages.json index 999553c..da9ea63 100644 --- a/src/_locales/ka/messages.json +++ b/src/_locales/ka/messages.json @@ -60,7 +60,7 @@ "description": "appears as tab name in dashboard" }, "assetViewerPageName": { - "message": "uBlock₀ — რესურსები", + "message": "uBlock₀ — მასალების ნახვა", "description": "Title for the asset viewer page" }, "advancedSettingsPageName": { @@ -72,11 +72,11 @@ "description": "English: Click: disable/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "დააწკაპეთ uBlock₀-ის გამოსართავად ამ საიტზე.\n\nCtrl+დაწკაპებით uBlock₀ მხოლოდ ამ გვერდზე გამოირთვება.", + "message": "დაწკაპეთ, რომ გაითიშოს uBlock₀ ამ საიტისთვის.\n\nCtrl+დაწკაპებით uBlock₀ მხოლოდ ამ გვერდზე გაითიშება.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "დააწკაპეთ uBlock₀-ის ჩასართავად ამ საიტზე.", + "message": "დაწკაპეთ, რომ ჩაირთოს uBlock₀ ამ საიტისთვის.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -136,11 +136,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "დააწკაპეთ ყველა ამომხტომი ფანჯრის შესაზღუდად ამ საიტზე", + "message": "დაწკაპეთ, რომ ყველა ამომხტომი ფანჯარა შეიზღუდოს ამ საიტზე", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "დააწკაპეთ, ამომხტომი ფანჯრების შეზღუდვის გასაუქმებლად ამ საიტზე", + "message": "დაწკაპეთ, რომ ამომხტომი ფანჯრები აღარ შეიზღუდოს ამ საიტზე", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -148,11 +148,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "დააწკაპეთ დიდი მედიაფაილების შესაზღუდად ამ საიტზე", + "message": "დაწკაპეთ, რომ დიდი მედიაფაილები შეიზღუდოს ამ საიტზე", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "დააწკაპეთ დიდი მედიაფაილების შეზღუდვის გასაუქმებლად ამ საიტზე", + "message": "დაწკაპეთ, რომ დიდი მედიაფაილები აღარ შეიზღუდოს ამ საიტზე", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -164,7 +164,7 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "დააწკაპეთ გარეგნული ნაწილების გასაფილტრად ამ საიტზე", + "message": "დაწკაპეთ გარეგნული ნაწილების გასაფილტრად ამ საიტზე", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -172,19 +172,19 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "დააწკაპეთ ვებშრიფტების შესაზღუდად ამ საიტზე", + "message": "დაწკაპეთ, რომ ვებშრიფტები შეიზღუდოს ამ საიტზე", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "დააწკაპეთ ვებშრიფტების შეზღუდვის გასაუქმებლად ამ საიტზე", + "message": "დაწკაპეთ, რომ ვებშრიფტები აღარ შეიზღუდოს ამ საიტზე", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoScripting1": { - "message": "დააწკაპეთ JavaScript-ის გასათიშად ამ საიტზე", + "message": "დაწკაპეთ, რომ გაითიშოს JavaScript ამ საიტზე", "description": "Tooltip for the no-scripting per-site switch" }, "popupTipNoScripting2": { - "message": "დააწკაპეთ JavaScript-ზე შეზღუდვის მოსახსნელად ამ საიტზე", + "message": "დაწკაპეთ, რომ აღარ გაითიშოს JavaScript ამ საიტზე", "description": "Tooltip for the no-scripting per-site switch" }, "popupNoPopups_v2": { @@ -332,7 +332,7 @@ "description": "English: Make use of context menu where appropriate" }, "settingsColorBlindPrompt": { - "message": "ფერთა გასწორება დაქვეითებულად მხედველთათვის", + "message": "ფერთა მორგება სუსტად მხედველთათვის", "description": "English: Color-blind friendly" }, "settingsAppearance": { @@ -360,7 +360,7 @@ "description": "English: " }, "settingsHyperlinkAuditingDisabledPrompt": { - "message": "ბმულებით სარგებლობაზე თვალყურის დევნების არიდება", + "message": "ბმულებით სარგებლობისას აღრიცხვის არიდება", "description": "English: " }, "settingsWebRTCIPAddressHiddenPrompt": { @@ -376,7 +376,7 @@ "description": "" }, "settingsNoCosmeticFilteringPrompt": { - "message": "გარეგნული ნაწილების ფილტრის გათიშვა", + "message": "გარეგნული ნაწილების გაფილტვრის გათიშვა", "description": "" }, "settingsNoLargeMediaPrompt": { @@ -424,7 +424,7 @@ "description": "Appears at the top of the _3rd-party filters_ pane" }, "3pListsOfBlockedHostsPerListStats": { - "message": "{{total}}-დან გამოიყენება {{used}}", + "message": "{{total}}-იდან გამოიყენება {{used}}", "description": "Appears aside each filter list in the _3rd-party filters_ pane" }, "3pAutoUpdatePrompt1": { @@ -444,7 +444,7 @@ "description": "English: Parse and enforce Adblock+ element hiding filters." }, "3pParseAllABPHideFiltersInfo": { - "message": "გარეგნული ფილტრები, ძირითადად, იმ ხილული ნაწილების დასამალად გამოიყენება ვებგვერდზე, რომელთაც ქსელის მოთხოვნაზე დაფუძნებული ფილტრები ვერ ზღუდავს.", + "message": "გარეგნული ფილტრები, ჩვეულებრივ, ვებგვერდზე იმ ხილული ნაწილების დასამალად გამოიყენება, რომლებზეც ქსელში მოთხოვნის შემზღუდავი ფილტრები ვერ მოქმედებს.", "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." }, "3pIgnoreGenericCosmeticFilters": { @@ -452,7 +452,7 @@ "description": "This will cause uBO to ignore all generic cosmetic filters." }, "3pIgnoreGenericCosmeticFiltersInfo": { - "message": "გარეგნული საერთო ფილტრები ისეთი ფილტრებია, რომლებიც ყველა ვებსაიტზე მოქმედებს. ამ პარამეტრის ჩართვით კი თავიდან აიცილებთ გარეგნული საერთო ფილტრების შედეგად მეხსიერებისა და პროცესორის ზედმეტ დატვირთვას.\n\nამ პარამეტრის ჩართვა სასურველია სუსტ მოწყობილობებზე.", + "message": "გარეგნული საერთო ფილტრებია, რომლებიც ყველა ვებსაიტზე მოქმედებს. ამ პარამეტრის ჩართვით კი თავიდან აიცილებთ გარეგნული საერთო ფილტრებით მეხსიერებისა და პროცესორის ზედმეტ დატვირთვას.\n\nამ პარამეტრის ჩართვა სასურველია სუსტ მოწყობილობებზე.", "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pSuspendUntilListsAreLoaded": { @@ -483,6 +483,14 @@ "message": "მავნე დომენები", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "სოცქსელების ნაწილები", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "ცნობები ფუნთუშების შესახებ", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "შემაწუხებელი შიგთავსი", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "ქსელის შეცდომის შედეგად, შიგთავსის განახლება ვერ მოხერხდა.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "თითოეული ფილტრი ცალკეულ ხაზზე. ფილტრი შეიძლება იყოს საიტის უბრალო დასახელება ან Adblock Plus-სთან თავსებადი მითითებები. ხაზები ‘!’ თავსართით უგულებელყოფილი იქნება.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "ფილტრების არიდება არასანდო წყაროებიდან.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "საკუთარი ფილტრების ამოქმედება", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "ნების დართვა მორგებული ფილტრებისთვის, რომლებიც ნდობას ითხოვს", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "შემოტანა და დამატება", "description": "Button in the 'My filters' pane" @@ -544,7 +556,7 @@ "description": "Button in the 'My filters' pane" }, "1pExportFilename": { - "message": "ჩემი-ublock-მუდმივი-ფილტრები_{{datetime}}.txt", + "message": "ჩემი-უცვლელი-ublock-ფილტრები_{{datetime}}.txt", "description": "English: my-ublock-static-filters_{{datetime}}.txt" }, "1pApplyChanges": { @@ -676,7 +688,7 @@ "description": "Tooltip for the popup panel button in the logger page" }, "loggerInfoTip": { - "message": "uBlock Origin-ის ვიკი: აღმრიცხავი", + "message": "uBlock-Origin-ცნობარი: აღმრიცხავი", "description": "Tooltip for the top-right info label in the logger page" }, "loggerClearTip": { @@ -780,7 +792,7 @@ "description": "Label for the type selector" }, "loggerStaticFilteringHeader": { - "message": "მუდმივი ფილტრები", + "message": "უცვლელი ფილტრები", "description": "Small header to identify the static filtering section" }, "loggerStaticFilteringSentence": { @@ -820,11 +832,11 @@ "description": "Used in the static filtering wizard" }, "loggerStaticFilteringFinderSentence1": { - "message": "მუდმივი ფილტრი <code>{{filter}}</code> ნაპოვნია სიაში:", + "message": "უცვლელი ფილტრი <code>{{filter}}</code> ნაპოვნია სიაში:", "description": "Below this sentence, the filter list(s) in which the filter was found" }, "loggerStaticFilteringFinderSentence2": { - "message": "მუდმივი ფილტრი <code>{{filter}}</code> ვერ მოიძებნა ამჟამად გამოყენებულ ფილტრებს შორის", + "message": "უცვლელი ფილტრი ვერ მოიძებნა ამჟამად გამოყენებულ ფილტრებში", "description": "Message to show when a filter cannot be found in any filter lists" }, "loggerSettingDiscardPrompt": { @@ -900,7 +912,7 @@ "description": "Header of 'Documentation' section in Support pane" }, "supportS1P1": { - "message": "დამატებითი მასალებისთვის იხილეთ <code>uBlock/wiki</code>, რომ უკეთ გაეცნოთ uBlock Origin-ის შესაძლებლობებს.", + "message": "დამატებითი მასალებისთვის იხილეთ <code>uBlock/wiki</code>, რომ უკეთ გაიცნოთ uBlock Origin და მისი შესაძლებლობები.", "description": "First paragraph of 'Documentation' section in Support pane" }, "supportS2H": { @@ -912,7 +924,7 @@ "description": "First paragraph of 'Questions and support' section in Support pane" }, "supportS3H": { - "message": "ფილტრის ხარვეზი/საიტი დაზიანდა", + "message": "ფილტრის ხარვეზი/გაუმართაობა საიტზე", "description": "Header of 'Filter issues' section in Support pane" }, "supportS3P1": { @@ -920,7 +932,7 @@ "description": "First paragraph of 'Filter issues' section in Support pane" }, "supportS3P2": { - "message": "<b>ყურადღება:</b> ეცადეთ, არ გამოიყენოთ სხვა მსგავსი დანიშნულების შემზღუდავები uBlock Origin-თან ერთად, ვინაიდან წარმოშობს ფილტრების ხარვეზებს ცალკეულ საიტებზე.", + "message": "<b>ყურადღება:</b> ეცადეთ, არ გამოიყენოთ uBlock Origin და სხვა მსგავსი შემზღუდავები ერთდროულად, ვინაიდან წარმოიქმნება ფილტრების ხარვეზები ცალკეულ საიტებზე.", "description": "Second paragraph of 'Filter issues' section in Support pane" }, "supportS3P3": { @@ -1132,11 +1144,11 @@ "description": "Used as a title for the document-blocked page" }, "docblockedPrompt1": { - "message": "uBlock Origin-მა შეზღუდა მოცემული გვერდის ჩატვირთვა:", + "message": "uBlock Origin ზღუდავს მოცემული გვერდის ჩატვირთვას:", "description": "Used in the strict-blocking page" }, "docblockedPrompt2": { - "message": "მოცემული ფილტრიდან გამომდინარე", + "message": "აღნიშნული ფილტრის მიხედვით:", "description": "Used in the strict-blocking page" }, "docblockedNoParamsPrompt": { @@ -1144,7 +1156,7 @@ "description": "label to be used for the parameter-less URL: https://cloud.githubusercontent.com/assets/585534/9832014/bfb1b8f0-593b-11e5-8a27-fba472a5529a.png" }, "docblockedFoundIn": { - "message": "პოვნა:", + "message": "შეიცავს:", "description": "English: List of filter list names follows" }, "docblockedBack": { @@ -1196,7 +1208,7 @@ "description": "used as a prompt for the user to provide a custom device name" }, "advancedSettingsWarning": { - "message": "გაფრთხილება! გაფართოებული პარამეტრების ცვლილების შედეგებზე, თავად იქნებით პასუხისმგებელი.", + "message": "გაფრთხილება! გაფართოებული პარამეტრების ჩასწორების შედეგებზე თავად იქნებით პასუხისმგებელი.", "description": "A warning to users at the top of 'Advanced settings' page" }, "genericSubmit": { @@ -1251,6 +1263,10 @@ "message": "გარეგნული ფილტრის ჩამრთველი", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript-ის ჩამრთველი", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "მსუბუქი შეზღუდვის რეჟიმი", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/kk/messages.json b/src/_locales/kk/messages.json index 9cf48a4..bd037e0 100644 --- a/src/_locales/kk/messages.json +++ b/src/_locales/kk/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Бөгеу режимі", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/kn/messages.json b/src/_locales/kn/messages.json index 567a8e5..8b9fccc 100644 --- a/src/_locales/kn/messages.json +++ b/src/_locales/kn/messages.json @@ -4,7 +4,7 @@ "description": "extension name." }, "extShortDesc": { - "message": "ಕೊನೆಗೂ, ಒಂದು ದಕ್ಷ ನಿರ್ಬಂಧಕ. ಮಿತವಾದ ಸಿಪಿಯೂ ಹಾಗು ಮೆಮೊರಿ ಬಳಕೆ.", + "message": "ಕೊನೆಗೆ, ಒಂದು ದಕ್ಷ ನಿರ್ಬಂಧಕ. ಮಿತವಾದ ಸಿಪಿಯೂ ಹಾಗು ಮೆಮೊರಿ ಬಳಿಕೇಒಂದಿಗೆ .", "description": "this will be in the Chrome web store: must be 132 characters or less" }, "dashboardName": { @@ -16,11 +16,11 @@ "description": "A warning in the dashboard when navigating away from unsaved changes" }, "dashboardUnsavedWarningStay": { - "message": "Stay here", + "message": "ಇಲ್ಲೇ ಇರು", "description": "Label for button to prevent navigating away from unsaved changes" }, "dashboardUnsavedWarningIgnore": { - "message": "Ignore", + "message": "ನಿರ್ಲಕ್ಷಿಸು", "description": "Label for button to ignore unsaved changes" }, "settingsPageName": { @@ -44,7 +44,7 @@ "description": "appears as tab name in dashboard" }, "shortcutsPageName": { - "message": "Shortcuts", + "message": "ಶಾರ್ಟ್ಕಟ್ಗಳು", "description": "appears as tab name in dashboard" }, "statsPageName": { @@ -56,11 +56,11 @@ "description": "appears as tab name in dashboard" }, "supportPageName": { - "message": "Support", + "message": "ಬೆಂಬಲ", "description": "appears as tab name in dashboard" }, "assetViewerPageName": { - "message": "uBlock₀ — Asset viewer", + "message": "uBlock₀ — ಆಸ್ತಿ ವೀಕ್ಷಕ", "description": "Title for the asset viewer page" }, "advancedSettingsPageName": { @@ -108,7 +108,7 @@ "description": "For the new mobile-friendly popup design" }, "popupDomainsConnected_v2": { - "message": "Domains connected", + "message": "ಸಂಪರ್ಕಳಗಿಸಿದ ಡೊಮೈನುಗಳು ", "description": "For the new mobile-friendly popup design" }, "popupTipDashboard": { @@ -204,15 +204,15 @@ "description": "Caption for the no-remote-fonts per-site switch" }, "popupNoScripting_v2": { - "message": "JavaScript", + "message": "ಜಾವಾಸ್ಕ್ರಿಪ್ಟ್", "description": "Caption for the no-scripting per-site switch" }, "popupMoreButton_v2": { - "message": "More", + "message": "ಅಧಿಕ", "description": "Label to be used to show popup panel sections" }, "popupLessButton_v2": { - "message": "Less", + "message": "ಕಡಿಮೆ", "description": "Label to be used to hide popup panel sections" }, "popupTipGlobalRules": { @@ -272,15 +272,15 @@ "description": "appears in popup" }, "popupVersion": { - "message": "Version", + "message": "ಆವೃತ್ತಿ", "description": "Example of use: Version 1.26.4" }, "popup3pScriptFilter": { - "message": "script", + "message": "ಸ್ಕ್ರಿಪ್ಟ್", "description": "Appears as an option to filter out firewall rows" }, "popup3pFrameFilter": { - "message": "frame", + "message": "ಫ್ರೇಮ್", "description": "Appears as an option to filter out firewall rows" }, "pickerCreate": { @@ -296,7 +296,7 @@ "description": "English: Quit" }, "pickerPreview": { - "message": "Preview", + "message": "ಮುನ್ನೋಟ", "description": "Element picker preview mode: will cause the elements matching the current filter to be removed from the page" }, "pickerNetFilters": { @@ -336,11 +336,11 @@ "description": "English: Color-blind friendly" }, "settingsAppearance": { - "message": "Appearance", + "message": "ನೋಟ", "description": "Section for controlling user interface appearance" }, "settingsThemeLabel": { - "message": "Theme", + "message": "ಥೀಮ್", "description": "Label for checkbox to enable a custom dark theme" }, "settingsThemeAccent0Label": { @@ -352,7 +352,7 @@ "description": "" }, "settingsAdvancedUserPrompt": { - "message": "I am an advanced user", + "message": "ನಾನು ಮುಂದುವರಿದ ಬಳಕೆದಾರ", "description": "Checkbox to let user access advanced, technical features" }, "settingsPrefetchingDisabledPrompt": { @@ -400,7 +400,7 @@ "description": "background information: https://github.com/uBlockOrigin/uBlock-issues/issues/1513" }, "settingsAdvanced": { - "message": "Advanced", + "message": "ಉನ್ನತ ಸಂಯೋಜನೆಗಳು", "description": "Section for controlling advanced-user settings" }, "settingsAdvancedSynopsis": { @@ -408,7 +408,7 @@ "description": "Description of section controlling advanced-user settings" }, "settingsAdvancedUserSettings": { - "message": "advanced settings", + "message": "ಮು೦ದುವರಿದ ಸೆಟ್ಟಿಂಗ್ಸ್", "description": "For the tooltip of a link which gives access to advanced settings" }, "settingsLastRestorePrompt": { @@ -416,11 +416,11 @@ "description": "English: Last restore:" }, "settingsLastBackupPrompt": { - "message": "Last backup:", + "message": "ಕೊನೆಯ ಬ್ಯಾಕಪ್:", "description": "English: Last backup:" }, "3pListsOfBlockedHostsPrompt": { - "message": "{{netFilterCount}} network filters + {{cosmeticFilterCount}} cosmetic filters from:", + "message": "ಕೆಳಗಿನ ಫೈಲಟ್ರ್ಪಟ್ಟಿಗೆಯಿಂದ {{netFilterCount}} ನೆಟ್ವರ್ಕ್ ಫಿಲ್ಟರ್ಗಳು + {{cosmeticfiltercount}} ಕಾಸ್ಮೆಟಿಕ್ ಶೋಧಕಗಳು ಕ್ರಿಯಾಶೀಲವಾಗಿದೆ:", "description": "Appears at the top of the _3rd-party filters_ pane" }, "3pListsOfBlockedHostsPerListStats": { @@ -480,7 +480,15 @@ "description": "Filter lists section name" }, "3pGroupMalware": { - "message": "Malware protection, security", + "message": "ಮಾಲ್ವೇರ್ ರಕ್ಷಣೆ, ಭದ್ರತೆ", + "description": "Filter lists section name" + }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", "description": "Filter lists section name" }, "3pGroupAnnoyances": { @@ -500,7 +508,7 @@ "description": "Filter lists section name" }, "3pImport": { - "message": "Import…", + "message": "ಆಮದು...", "description": "The label for the checkbox used to import external filter lists" }, "3pExternalListsHint": { @@ -520,23 +528,27 @@ "description": "used as a tooltip for the clock icon beside a list" }, "3pUpdating": { - "message": "Updating…", + "message": "ಸೇರಿಸಲಾಗುತ್ತಿದೆ...", "description": "used as a tooltip for the spinner icon beside a list" }, "3pNetworkError": { "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { - "message": "ಆಮದಿಸಿ ಸೇರ್ಪಡಿಸು", + "message": "ಆಮದಿಸಿ ಸೇರ್ಪಡಿಸು...", "description": "Button in the 'My filters' pane" }, "1pExport": { @@ -544,7 +556,7 @@ "description": "Button in the 'My filters' pane" }, "1pExportFilename": { - "message": "my-ublock-static-filters_{{datetime}}.txt", + "message": "ನನ್ನ-ublock-ಸ್ಥಿರ-ಶೋಧಕಗಳು_{{datetime}}.txt", "description": "English: my-ublock-static-filters_{{datetime}}.txt" }, "1pApplyChanges": { @@ -580,7 +592,7 @@ "description": "Will discard manually-edited content and exit manual-edit mode" }, "rulesImport": { - "message": "Import from file…", + "message": "ಫೈಲಿಂದ ಆಮದಿಸು...\n", "description": "" }, "rulesExport": { @@ -588,7 +600,7 @@ "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { - "message": "my-ublock-dynamic-rules_{{datetime}}.txt", + "message": "ನನ್ನ-ublock-ಡೈನಾಮಿಕ್-ಫಿಲ್ಟರ್-ನಿಯಮಗಳು_{{datetime}}.txt", "description": "default file name to use" }, "rulesHint": { @@ -608,11 +620,11 @@ "description": "English: a sort option for list of rules." }, "rulesSortBySource": { - "message": "Source", + "message": "ಮೂಲ", "description": "English: a sort option for list of rules." }, "rulesSortByDestination": { - "message": "Destination", + "message": "ಗಮ್ಯಸ್ಥಾನ", "description": "English: a sort option for list of rules." }, "whitelistPrompt": { @@ -620,27 +632,27 @@ "description": "A concise description of the 'Trusted sites' pane." }, "whitelistImport": { - "message": "Import and append…", + "message": "ಆಮದಿಸಿ ಸೇರ್ಪಡಿಸು", "description": "Button in the 'Trusted sites' pane" }, "whitelistExport": { - "message": "Export…", + "message": "ರಫ್ತು...\n", "description": "Button in the 'Trusted sites' pane" }, "whitelistExportFilename": { - "message": "my-ublock-trusted-sites_{{datetime}}.txt", + "message": "ನನ್ನ-ublock-ವಿಶ್ವಾಸಾರ್ಹ-ಸೈಟ್ಗಳು_{{datetime}}.txt", "description": "The default filename to use for import/export purpose" }, "whitelistApply": { - "message": "Apply changes", + "message": "ಬದಲಾವಣೆಗಳನ್ನು ಅನ್ವಯಿಸಿ", "description": "English: Apply changes" }, "logRequestsHeaderType": { - "message": "Type", + "message": "ವಿಧ", "description": "English: Type" }, "logRequestsHeaderDomain": { - "message": "Domain", + "message": "ಡೊಮೇನ್", "description": "English: Domain" }, "logRequestsHeaderURL": { @@ -660,7 +672,7 @@ "description": "Pretty name for behind-the-scene network requests" }, "loggerCurrentTab": { - "message": "Current tab", + "message": "ಪ್ರಸ್ತುತ ಟ್ಯಾಬ್", "description": "Appears in the logger's tab selector" }, "loggerReloadTip": { @@ -716,7 +728,7 @@ "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltinAllowed": { - "message": "allowed", + "message": "ಅನುಮತಿಸಲಾಗಿದೆ", "description": "A keyword in the built-in row filtering expression" }, "loggerRowFiltererBuiltinModified": { @@ -732,7 +744,7 @@ "description": "A keyword in the built-in row filtering expression" }, "loggerEntryDetailsHeader": { - "message": "Details", + "message": "ವಿವರಗಳು", "description": "Small header to identify the 'Details' pane for a specific logger entry" }, "loggerEntryDetailsFilter": { @@ -740,11 +752,11 @@ "description": "Label to identify a filter field" }, "loggerEntryDetailsFilterList": { - "message": "Filter list", + "message": "ಫಿಲ್ಟರ್ ಪಟ್ಟಿ", "description": "Label to identify a filter list field" }, "loggerEntryDetailsRule": { - "message": "Rule", + "message": "ನಿಯಮ", "description": "Label to identify a rule field" }, "loggerEntryDetailsContext": { @@ -760,7 +772,7 @@ "description": "Label to identify a field providing partyness information" }, "loggerEntryDetailsType": { - "message": "Type", + "message": "ವಿಧ", "description": "Label to identify the type of an entry" }, "loggerEntryDetailsURL": { @@ -776,7 +788,7 @@ "description": "Label for the context selector" }, "loggerURLFilteringTypeLabel": { - "message": "Type:", + "message": "ವಿಧ:", "description": "Label for the type selector" }, "loggerStaticFilteringHeader": { @@ -788,11 +800,11 @@ "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartBlock": { - "message": "Block", + "message": "ನಿರ್ಬಂಧಿಸು", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartAllow": { - "message": "Allow", + "message": "ಅನುಮತಿಸು", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartType": { @@ -804,7 +816,7 @@ "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartOrigin": { - "message": "from “{{origin}}”", + "message": "\"{{origin}} \"ಇಂದಾ", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartAnyOrigin": { @@ -876,7 +888,7 @@ "description": "Label for radio-button to pick export format" }, "loggerExportEncodePlain": { - "message": "Plain", + "message": "ಸಾದಾ", "description": "Label for radio-button to pick export text format" }, "loggerExportEncodeMarkdown": { @@ -884,7 +896,7 @@ "description": "Label for radio-button to pick export text format" }, "supportOpenButton": { - "message": "Open", + "message": "ತೆರೆ", "description": "Text for button which open an external webpage in Support pane" }, "supportReportSpecificButton": { @@ -1012,11 +1024,11 @@ "description": "Text for 'Unredact' button" }, "aboutPrivacyPolicy": { - "message": "Privacy policy", + "message": "ಗೌಪ್ಯತಾ ನೀತಿ\n", "description": "Link to privacy policy on GitHub (English)" }, "aboutChangelog": { - "message": "Changelog", + "message": "ಬದಲಾವಣೆಗಳು", "description": "" }, "aboutCode": { @@ -1028,15 +1040,15 @@ "description": "English: Contributors" }, "aboutSourceCode": { - "message": "Source code", + "message": "ಮೂಲ ಕೊಡ್", "description": "Link text to source code repo" }, "aboutTranslations": { - "message": "Translations", + "message": "ಅನುವಾದ", "description": "Link text to translations repo" }, "aboutFilterLists": { - "message": "Filter lists", + "message": "ಫಿಲ್ಟರ್ ಪಟ್ಟಿಗಳು", "description": "Link text to uBO's own filter lists repo" }, "aboutDependencies": { @@ -1088,7 +1100,7 @@ "description": "No longer used" }, "subscribeButton": { - "message": "Subscribe", + "message": "ಫಿಲ್ಟರ್ ಪಟ್ಟಿಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ", "description": "For the button used to subscribe to a filter list" }, "elapsedOneMinuteAgo": { @@ -1124,7 +1136,7 @@ "description": "Firefox/Fennec-specific: Show Logger" }, "fennecMenuItemBlockingOff": { - "message": "off", + "message": "ಆಫ್", "description": "Firefox-specific: appears as 'uBlock₀ (off)'" }, "docblockedTitle": { @@ -1204,7 +1216,7 @@ "description": "for generic 'Submit' buttons" }, "genericApplyChanges": { - "message": "Apply changes", + "message": "ಬದಲಾವಣೆಗಳನ್ನು ಅನ್ವಯಿಸಿ", "description": "for generic 'Apply changes' buttons" }, "genericRevert": { @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ko/messages.json b/src/_locales/ko/messages.json index a43e85c..486e54f 100644 --- a/src/_locales/ko/messages.json +++ b/src/_locales/ko/messages.json @@ -483,6 +483,14 @@ "message": "멀웨어 도메인", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "소셜 위젯", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "쿠키 알림", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "골칫거리", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "네트워크 오류로 인해 리소스가 업데이트되지 못했습니다.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "한 줄에 한 개의 필터를 입력하세요. 필터는 순수 호스트이름, 혹은 Adblock Plus-호환 필터가 될 수 있습니다. 시작 부분이 ‘!’ 로 시작되면 무시됩니다.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "신뢰할 수 없는 출처의 필터를 추가하지 마십시오.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "내 사용자 정의 필터 활성화", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "신뢰가 필요한 사용자 정의 필터 허용", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "가져오기 및 추가하기", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "표면 필터 토글", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript 토글", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "차단 모드 완화", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/lt/messages.json b/src/_locales/lt/messages.json index 3c2358f..a44db57 100644 --- a/src/_locales/lt/messages.json +++ b/src/_locales/lt/messages.json @@ -128,7 +128,7 @@ "description": "Tooltip used for the logger icon in the panel" }, "popupTipReport": { - "message": "Report an issue on this website", + "message": "Pranešti apie problemą šioje svetainėje", "description": "Tooltip used for the 'chat' icon in the panel" }, "popupTipNoPopups": { @@ -483,6 +483,14 @@ "message": "Kenksmingos sritys", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Erzinimas", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Tinklo klaida sutrukdė atnaujinti resursą.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Vienas filtras eilutėje. Filtras gali būti paprastas serverio adresas, arba su Adblock Plus suderinamas filtras. Eilutės pradėtos <code>!</code> bus ignoruotos.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importuoti ir papildyti", "description": "Button in the 'My filters' pane" @@ -888,11 +900,11 @@ "description": "Text for button which open an external webpage in Support pane" }, "supportReportSpecificButton": { - "message": "Create new report", + "message": "Sukurti naują ataskaitą", "description": "Text for button which open an external webpage in Support pane" }, "supportFindSpecificButton": { - "message": "Find similar reports", + "message": "Rasti panašias ataskaitas", "description": "A clickable link in the filter issue reporter section" }, "supportS1H": { @@ -904,7 +916,7 @@ "description": "First paragraph of 'Documentation' section in Support pane" }, "supportS2H": { - "message": "Questions and support", + "message": "Klausimai ir pagalba", "description": "Header of 'Questions and support' section in Support pane" }, "supportS2P1": { @@ -1172,7 +1184,7 @@ "description": "English: Permanently" }, "docblockedDisable": { - "message": "Proceed", + "message": "Tęsti", "description": "Button text to navigate to the blocked page" }, "cloudPush": { @@ -1244,13 +1256,17 @@ "description": "Label for buttons used to copy something to the clipboard" }, "genericSelectAll": { - "message": "Select all", + "message": "Žymėti viską", "description": "Label for buttons used to select all text in editor" }, "toggleCosmeticFiltering": { "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" @@ -1276,7 +1292,7 @@ "description": "Message used in frame placeholders" }, "linterMainReport": { - "message": "Errors: {{count}}", + "message": "Klaidos: {{count}}", "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { diff --git a/src/_locales/lv/messages.json b/src/_locales/lv/messages.json index 6b62cc6..c529a68 100644 --- a/src/_locales/lv/messages.json +++ b/src/_locales/lv/messages.json @@ -483,6 +483,14 @@ "message": "Ļaundabīgo programmu domēni", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sabiedriskās ekrānvadīklas", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Sīkdatņu paziņojumi", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Traucējoši elementi", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Tīkla kļūda neļāva atjaunināt resursu.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Vienu filtru katrā rindā. Filtrs var būt vienkārši resursa adrese, vai ar Adblock Plus saderīgs filtrs. Rindiņas, kuras sākas ar ‘!’ tiks ignorētas.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Nevajag pievienot filtrus no neuzticamiem avotiem.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Iespējot manas pielāgotās atlases", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Ļaut pielāgotas atlases, kas pieprasa uzticēšanos", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importēt un pievienot", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Pārslēgt kosmētisko atlasi", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Pārslēgt JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Atslābinātais aizturēšanas režīms", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/mk/messages.json b/src/_locales/mk/messages.json index 4a38be4..0a7777f 100644 --- a/src/_locales/mk/messages.json +++ b/src/_locales/mk/messages.json @@ -483,6 +483,14 @@ "message": "Домени на малвер", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Досадни", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Внеси и додај", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Релаксиран мод на блокирање", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ml/messages.json b/src/_locales/ml/messages.json index bd4452e..b4c1b3a 100644 --- a/src/_locales/ml/messages.json +++ b/src/_locales/ml/messages.json @@ -400,7 +400,7 @@ "description": "background information: https://github.com/uBlockOrigin/uBlock-issues/issues/1513" }, "settingsAdvanced": { - "message": "Advanced", + "message": "വിപുലമായ ക്രമീകരണങ്ങൾ", "description": "Section for controlling advanced-user settings" }, "settingsAdvancedSynopsis": { @@ -483,6 +483,14 @@ "message": "മാല്വെയര് ഡൊമൈനുകള്", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "ശല്യപ്പെടുത്തലുകൾ", "description": "Filter lists section name" @@ -520,21 +528,25 @@ "description": "used as a tooltip for the clock icon beside a list" }, "3pUpdating": { - "message": "അപ്ഡേറ്റുചെയ്യുന്നു ...", + "message": "അപ്ഡേറ്റുചെയ്യുന്നു...", "description": "used as a tooltip for the spinner icon beside a list" }, "3pNetworkError": { "message": "ഒരു നെറ്റ്വർക്ക് പിശക് ഉറവിടം അപ്ഡേറ്റുചെയ്യുന്നതിൽ നിന്ന് തടഞ്ഞു.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "ഒരു വരിയില് ഒരു ഫില്റ്റര് എന്ന രീതിയില്. ഒരു ഫില്റ്റര് എന്നത്, ഹോസ്റ്റ് നെയിം, അല്ലെങ്കില് ആഡ് ബ്ലോക്ക് പ്ലസ്-നോട് കംപാറ്റബിള് ആയ ഫില്റ്റര് എന്നിവ ആകാം. ‘!’ എന്നിവയില് ആരംഭിക്കുന്ന വരികള് ഇഗ്നോര് ചെയ്യപ്പെടും.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "ഇമ്പോര്ട്ടും കൂട്ടിചേര്ക്കലും ചെയ്യുക", "description": "Button in the 'My filters' pane" @@ -884,7 +896,7 @@ "description": "Label for radio-button to pick export text format" }, "supportOpenButton": { - "message": "Open", + "message": "തുറക്കുക", "description": "Text for button which open an external webpage in Support pane" }, "supportReportSpecificButton": { @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "തടയൽ മോഡ് വിശ്രമിക്കുക", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/mr/messages.json b/src/_locales/mr/messages.json index 9a60cc8..6530b32 100644 --- a/src/_locales/mr/messages.json +++ b/src/_locales/mr/messages.json @@ -483,6 +483,14 @@ "message": "मालवेअर डोमेन", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "चिडवणाऱ्या गोष्टी", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "प्रति ओळ एक फिल्टर. फिल्टर एक साधे होस्ट नावाचा असू शकतो, किंवा एक अद्ब्लोक प्लस सुसंगत फिल्टर असू शकतो. ओळी सह प्रिफिक्स ‘!’ कढे दुर्लक्ष केले जाईल.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "आयात आणि समावेश करा", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ms/messages.json b/src/_locales/ms/messages.json index 263303d..ed5b060 100644 --- a/src/_locales/ms/messages.json +++ b/src/_locales/ms/messages.json @@ -40,7 +40,7 @@ "description": "appears as tab name in dashboard" }, "whitelistPageName": { - "message": "Senarai putih", + "message": "Halaman dipercayai", "description": "appears as tab name in dashboard" }, "shortcutsPageName": { @@ -276,11 +276,11 @@ "description": "Example of use: Version 1.26.4" }, "popup3pScriptFilter": { - "message": "script", + "message": "skrip", "description": "Appears as an option to filter out firewall rows" }, "popup3pFrameFilter": { - "message": "frame", + "message": "kerangka", "description": "Appears as an option to filter out firewall rows" }, "pickerCreate": { @@ -483,6 +483,14 @@ "message": "Domain perisian hasad", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Kejengkelan", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Ralat rangkaian menghalang sumber dikemas kini..", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Satu penapis setiap baris. Penapis boleh menjadi nama hos biasa, atau penapis yang sesuai dengan Daftar Mudah. Garis yang diawali dengan <code>! </code> akan diabaikan.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { - "message": "Do not add filters from untrusted sources.", + "message": "Jangan tambah penapis daripada sumber yang tidak dipercayai.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import dan melampirkan", "description": "Button in the 'My filters' pane" @@ -956,11 +968,11 @@ "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S1": { - "message": "Filter lists are updated daily. Be sure your issue has not already been addressed in the most recent filter lists.", + "message": "Senarai penapis dikemas kini setiap hari. Pastikan isu anda belum ditangani dalam senarai penapis terbaharu.", "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S2": { - "message": "Verify that the issue still exists after reloading the problematic webpage.", + "message": "Sahkan bahawa isu itu masih wujud selepas memuat semula halaman web yang bermasalah.", "description": "A paragraph in the filter issue reporter section" }, "supportS6URL": { @@ -1228,7 +1240,7 @@ "description": "A context menu entry, present when large media elements have been blocked on the current site" }, "contextMenuViewSource": { - "message": "View source code…", + "message": "Lihat kod sumber…", "description": "A context menu entry, to view the source code of the target resource" }, "shortcutCapturePlaceholder": { @@ -1251,6 +1263,10 @@ "message": "Togol penapis kosmetik", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Tenang mod menyekat", "description": "Label for keyboard shortcut used to relax blocking mode" @@ -1276,11 +1292,11 @@ "description": "Message used in frame placeholders" }, "linterMainReport": { - "message": "Errors: {{count}}", + "message": "Ralat: {{count}}", "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { - "message": "Could not filter properly at browser launch. Reload the page to ensure proper filtering.", + "message": "Tidak dapat menapis dengan betul semasa pelancaran pelayar. Muat semula halaman untuk memastikan penapisan yang betul.", "description": "A warning which will appear in the popup panel if needed" }, "dummy": { diff --git a/src/_locales/nb/messages.json b/src/_locales/nb/messages.json index 811ab77..271ae2d 100644 --- a/src/_locales/nb/messages.json +++ b/src/_locales/nb/messages.json @@ -483,6 +483,14 @@ "message": "Beskyttelse mot skadelig programvare, sikkerhet", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sosiale medie-widgeter", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie meldinger", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Irritasjonsmomenter", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "En nettverksfeil forhindret ressursen i å bli oppdatert.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Ett filter per linje. Et filter kan være et vanlig vertsnavn eller et EasyList-kompatibelt filter. Linjer med prefikset <code>!</code> blir ignorert.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Ikke legg til filtre fra ikke-betrodde kilder.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importer og legg til…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Slå av/på kosmetisk filtrering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Slå av/på JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Lemp på blokkeringsmodus", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json index 8c50f79..554544c 100644 --- a/src/_locales/nl/messages.json +++ b/src/_locales/nl/messages.json @@ -483,6 +483,14 @@ "message": "Bescherming tegen malware, beveiliging", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sociale widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookiemeldingen", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Storende elementen", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Een netwerkfout heeft voorkomen dat de bron werd bijgewerkt.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Eén filter per regel. Een filter kan een gewone hostnaam of een EasyList-compatibel filter zijn. Regels beginnend met <code>!</code> worden genegeerd.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Voeg geen filters van niet-vertrouwde bronnen toe.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Mijn aangepaste filters inschakelen", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Aangepaste filters die vertrouwen vereisen toestaan", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importeren en toevoegen…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Cosmetische filters in-/uitschakelen", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript in-/uitschakelen", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Blokkeringsmodus beperken", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/oc/messages.json b/src/_locales/oc/messages.json index 2c4b085..6d141a6 100644 --- a/src/_locales/oc/messages.json +++ b/src/_locales/oc/messages.json @@ -483,6 +483,14 @@ "message": "Domenis malfasents", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importar e apondre", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/pa/messages.json b/src/_locales/pa/messages.json index 10438ef..b58a120 100644 --- a/src/_locales/pa/messages.json +++ b/src/_locales/pa/messages.json @@ -483,6 +483,14 @@ "message": "ਮਾਲਵੇਅਰਾਂ ਤੋਂ ਬਚਾਅ, ਸੁਰੱਖਿਆ", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "ਅਣਚਾਹੇ", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "ਨੈੱਟਵਰਕ ਗਲਤੀ ਸਰੋਤ ਨੂੰ ਅੱਪਡੇਟ ਹੋਣ ਤੋਂ ਰੋਕਦੀ ਹੈ।", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "ਗ਼ੈਰ-ਭਰੋਸੇਯੋਗ ਸਰੋਤਾਂ ਤੋਂ ਫਿਲਟਰ ਨਾ ਜੋੜੋ।", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "ਇੰਪੋਰਟ ਕਰੋ ਤੇ ਜੋੜੋ…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript ਨੂੰ ਬਦਲੋ", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json index 3b2d7cf..a4ce634 100644 --- a/src/_locales/pl/messages.json +++ b/src/_locales/pl/messages.json @@ -252,7 +252,7 @@ "description": "" }, "popup1pScriptRulePrompt": { - "message": "skrypty własne", + "message": "skrypty tej samej domeny", "description": "" }, "popup3pScriptRulePrompt": { @@ -480,7 +480,15 @@ "description": "Filter lists section name" }, "3pGroupMalware": { - "message": "Ochrona przed malware, bezpieczeństwo", + "message": "Ochrona przed złośliwym oprogramowaniem, bezpieczeństwo", + "description": "Filter lists section name" + }, + "3pGroupSocial": { + "message": "Widżety społecznościowe", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Powiadomienia o ciasteczkach", "description": "Filter lists section name" }, "3pGroupAnnoyances": { @@ -527,14 +535,18 @@ "message": "Błąd sieci uniemożliwił aktualizację zasobów.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "W wierszu może być tylko jeden filtr. Filtrem może być nazwa hosta lub filtr kompatybilny z EasyList. Wiersze poprzedzone znakiem <code>!</code> będą pomijane.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Nie dodawaj filtrów z niezaufanych źródeł.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Włącz moje własne filtry", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Zezwól na filtry wymagające zaufania", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importuj i dołącz…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Włącz/wyłącz filtrowanie kosmetyczne", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Włącz/wyłącz JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Rozluźnij tryb blokowania", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/pt_BR/messages.json b/src/_locales/pt_BR/messages.json index e644a4d..1704ab2 100644 --- a/src/_locales/pt_BR/messages.json +++ b/src/_locales/pt_BR/messages.json @@ -483,6 +483,14 @@ "message": "Proteção contra malware, segurança", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Widgets sociais", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Avisos dos cookies", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Aborrecimentos", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Um erro de rede impediu o recurso de ser atualizado.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Um filtro por linha. Um filtro pode ser um simples nome de hospedeiro ou um filtro compatível com o EasyList. As linhas prefixadas com o <code>!</code> serão ignoradas.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Não adicionar filtros de fontes não confiáveis.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Ativar meus filtros personalizados", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Permitir os filtros personalizados que requerem confiança", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importar e anexar", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Alternar filtragem cosmética", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Alternar JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relaxar modo de bloqueio", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/pt_PT/messages.json b/src/_locales/pt_PT/messages.json index 15019cb..05443e2 100644 --- a/src/_locales/pt_PT/messages.json +++ b/src/_locales/pt_PT/messages.json @@ -483,6 +483,14 @@ "message": "Proteção contra malware, segurança", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Widgets sociais", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Avisos de cookie", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Inconveniências", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Um erro de rede impediu que o recurso fosse atualizado.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Um filtro por linha. Um filtro pode ser um simples nome de anfitrião, ou um filtro compatível com a EasyList. Linhas começadas por <code>!</code> serão ignoradas.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Não adicione filtros de fontes não confiáveis.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Ativar os meus filtros personalizados", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Permitir filtros personalizados que exijam confiança", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importar e anexar…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Alternar filtragem cosmética", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Alternar JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relaxar modo de bloqueio", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ro/messages.json b/src/_locales/ro/messages.json index a338cbe..92ca0e2 100644 --- a/src/_locales/ro/messages.json +++ b/src/_locales/ro/messages.json @@ -56,7 +56,7 @@ "description": "appears as tab name in dashboard" }, "supportPageName": { - "message": "Ajută-ne", + "message": "Asistență", "description": "appears as tab name in dashboard" }, "assetViewerPageName": { @@ -483,6 +483,14 @@ "message": "Domenii malițioase", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Neplăceri", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "O eroare de rețea a împiedicat actualizarea resursei.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Un filtru pe linie. Un filtru poate fi un simplu nume de gazdă sau un filtru compatibil EasyList. Liniile precedate de <code>!</code> vor fi ignorate.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Nu adăuga filtre din surse nesigure.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importă și adaugă", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Comută filtrele cosmetice", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Comută JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relaxați modul de blocare", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json index 1702d1b..2d535dd 100644 --- a/src/_locales/ru/messages.json +++ b/src/_locales/ru/messages.json @@ -483,6 +483,14 @@ "message": "Защита от вредоносных сайтов, безопасность", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Виджеты соцсетей", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Уведомления о файлах куки", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Раздражающие элементы", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "В результате ошибки сети обновление не произошло.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Одно правило на строку. Правилом может быть имя сайта или EasyList-совместимый фильтр. Строки, начинающиеся с <code>!</code>, будут проигнорированы.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Не добавляйте фильтры из ненадёжных источников.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Включить мои пользовательские фильтры", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Разрешить пользовательские фильтры, требующие доверия", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Импортировать и добавить…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Вкл/Выкл косметическую фильтрацию", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Вкл/Выкл JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Ослабленный режим блокировки", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/si/messages.json b/src/_locales/si/messages.json index f414f59..afd9c19 100644 --- a/src/_locales/si/messages.json +++ b/src/_locales/si/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/sk/messages.json b/src/_locales/sk/messages.json index d794627..ab778d9 100644 --- a/src/_locales/sk/messages.json +++ b/src/_locales/sk/messages.json @@ -483,6 +483,14 @@ "message": "Domény malvéru", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sociálne widgety", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Oznámenie o cookies", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Obťažujúce", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Chyba siete zabránila aktualizácii zdroja.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Jeden filter na riadok. Filter môže byť jednoduchý názov hostiteľa alebo filter kompatibilný s Adblock Plus. Riadky začínajúce s <code>!</code> budú ignorované.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Nepridávajte filtre z nedôveryhodných zdrojov.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Povoliť moje vlastné filtre", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Povoliť vlastné filtre vyžadujúce dôveru", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importovať a pripojiť", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Prepnúť kozmetické filtrovanie", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Prepnúť JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Zmierniť režim blokovania", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/sl/messages.json b/src/_locales/sl/messages.json index 7963d85..bb2cfa2 100644 --- a/src/_locales/sl/messages.json +++ b/src/_locales/sl/messages.json @@ -483,6 +483,14 @@ "message": "Zlonamerne domene", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "\t\nNadlegovanje", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Omrežna napaka je preprečila posodobitev virov.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "En filter na vrstico. Filter je lahko navadno ime gostitelja, ali pa Adblock Plus kompatibilen filter. Vrstice z znakom ‘!’ bodo ignorirane.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Uvozi in dodaj", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Preklopi lepotno filtriranje", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Sprostite način blokiranja.", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/so/messages.json b/src/_locales/so/messages.json index 08cc0ad..b8c9c13 100644 --- a/src/_locales/so/messages.json +++ b/src/_locales/so/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Waxaa lagu talinayaa in lagu suurtageliyo doorashadan aaladaha awoodda yar.", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Caadiyan", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Soo deji...", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Hal URL laynkiiba. URL-yada aan ansax ahayn waa la iska indho-tiraa aamusnaan.", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "... ", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/sq/messages.json b/src/_locales/sq/messages.json index d6c5a65..3fc972c 100644 --- a/src/_locales/sq/messages.json +++ b/src/_locales/sq/messages.json @@ -483,6 +483,14 @@ "message": "Domenet e rrezikshme, siguria", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Veglat sociale", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Njoftimi për cookies", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elementet e bezdisshme", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Një problem me rrjetin kompjuterik pengoi përditësimin e informacionit.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Një filtër për rresht. Filtri mund të jetë thjesht emri i një hosti ose si ata që përdor EasyList. Nuk do të merren parasysh rreshtat që fillojnë me <code>!</code>.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Mos shtoni filtra nga burime të pabesueshme.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Aktivizoj filtrat e mi të personalizuar", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Lejoj filtrat e personalizuar që duhen besuar", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importoj dhe shtoj…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Ç/Aktivizoj filtrat kozmetikë", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Ç/Aktivizoj JavaScript-in", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Dobësoj mënyrën e bllokimit", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/sr/messages.json b/src/_locales/sr/messages.json index 54ee936..35e56fc 100644 --- a/src/_locales/sr/messages.json +++ b/src/_locales/sr/messages.json @@ -483,6 +483,14 @@ "message": "Заштита од злонамерног софтвера, безбедност", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Друштвени виџети", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Обавештења о колачићима", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Сметње", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Грешка на мрежи је спречила ажурирање ресурса.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Један филтер по реду. Филтер може бити назив хоста или филтер компатибилан са EasyList форматом. Редови са префиксом <code>!</code> ће бити игнорисани.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Не додавај филтере из непоузданих извора.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Омогући моје прилагођене филтере", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Дозволи прилагођене филтере који захтевају поверење", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Увези и додај", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Укључи/искључи естетско филтрирање", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Укључи/искључи JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Опуштени режим блокирања", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/sv/messages.json b/src/_locales/sv/messages.json index b091a77..828bb2c 100644 --- a/src/_locales/sv/messages.json +++ b/src/_locales/sv/messages.json @@ -483,6 +483,14 @@ "message": "Skydd mot skadlig programvara, säkerhet", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Sociala widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie-meddelanden", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Störande", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Ett nätverksproblem har förhindrat resursen från att uppdateras.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Ett filter per rad. Ett filter kan vara ett vanligt värdnamn eller ett EasyList-kompatibelt filter. Rader med prefixet <code>!</code> ignoreras.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { - "message": "Lägg inte till filter från opålitliga källor.", + "message": "Lägg inte till filter från obetrodda källor.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Aktivera mina anpassade filter", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Att tillåta anpassade filter kräver tillit", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Importera och lägg till…", "description": "Button in the 'My filters' pane" @@ -644,7 +656,7 @@ "description": "English: Domain" }, "logRequestsHeaderURL": { - "message": "URL", + "message": "Webbadress", "description": "English: URL" }, "logRequestsHeaderFilter": { @@ -656,7 +668,7 @@ "description": "Appears in the logger's tab selector" }, "logBehindTheScene": { - "message": "Bakom kulissen", + "message": "Tabless", "description": "Pretty name for behind-the-scene network requests" }, "loggerCurrentTab": { @@ -784,7 +796,7 @@ "description": "Small header to identify the static filtering section" }, "loggerStaticFilteringSentence": { - "message": "{{action}} nätverksförfrågningar av {{type}} {{br}}vilkas webbadresser matchar {{url}} {{br}}och som kommer från {{origin}},{{br}}{{importance}} det finns ett matchande undantagsfilter.", + "message": "{{action}} nätverksförfrågningar av {{type}} {{br}}vilken webbadress matchar {{url}} {{br}}och som kommer från {{origin}},{{br}}{{importance}} det finns ett matchande undantagsfilter.", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartBlock": { @@ -1251,6 +1263,10 @@ "message": "Växla kosmetisk filtrering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Växla JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Lätta på blockeringsläge", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/sw/messages.json b/src/_locales/sw/messages.json index a546d80..ad3e428 100644 --- a/src/_locales/sw/messages.json +++ b/src/_locales/sw/messages.json @@ -483,6 +483,14 @@ "message": "Vikoa vya programu hasidi", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Vikasirisho", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Tatizo la mtandao imezuia rasilimali kusasishwa.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Chujia moja kila laini. Chujio inaweza kuwa kuwa jina pangishi (hostname), au chujio linalotumika na Adblock Plus. Laini zilizo na viambishi awali za <code>!</code> zitapuuzwa.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Leta na ambatisha", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Pumzisha mtindo wa kuzuia", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ta/messages.json b/src/_locales/ta/messages.json index 5a13a9d..c762935 100644 --- a/src/_locales/ta/messages.json +++ b/src/_locales/ta/messages.json @@ -483,6 +483,14 @@ "message": "தீப்பொருள் ஆள்களங்கள்", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "எரிச்சல்கள்", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "ஒரு பிணைய பிழை வளத்தைப் புதுப்பிப்பதைத் தடுத்தது.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "ஒரு வரிக்கு ஒரு வடிகட்டி. வடிகட்டி என்பது வெறும் வழங்கிப்பெயராக இருக்கலாம், அல்லது Adblock Plus-க்குப் பொருந்தும் வடிகட்டியாக இருக்கலாம். ‘!’ எனும் எழுத்தில் தொடங்கும் வரிகள் புறக்கணிக்கப்படும்.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "இறக்குமதி செய் மற்றும் இணை", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "ஒப்பனை வடிகட்டுதலை மறுநிலைமாற்று", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "தடுப்பு பயன்முறையை தளர்த்தவும்", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/te/messages.json b/src/_locales/te/messages.json index 21797c8..0f4e085 100644 --- a/src/_locales/te/messages.json +++ b/src/_locales/te/messages.json @@ -483,6 +483,14 @@ "message": "మాల్వేర్ డొమైన్లు", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "అసౌకర్యాల పట్టిక", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "ఈ చిట్టా అనుసంధాన వైఫల్యం వలన నవికరించబడలేదు.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "ఒక్కో పంక్తిలో ఒక ఫిల్టర్ నమోదు. పేర్కొనబడే ఫిల్టర్, కేవలం హోస్టుపేరు లేదా Adblock Plusకి అనువైన ఫిల్టర్ కావొచ్చు. పంక్తిలో మొదట ‘!’ ఉన్నచో ఆ పంక్తి పరిగణలోకి తీసుకొబడదు.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "అవిశ్వసనీయ మూలాల నుండి ఫిల్టర్లను జోడించవద్దు.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "దిగుమతిచేసి పోడిగించుము", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "నిరోధించే మోడ్ను రిలాక్స్ చేయండి", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/th/messages.json b/src/_locales/th/messages.json index e812407..268cd4d 100644 --- a/src/_locales/th/messages.json +++ b/src/_locales/th/messages.json @@ -4,7 +4,7 @@ "description": "extension name." }, "extShortDesc": { - "message": "มาแล้ว! โปรแกรมบล็อกโฆษณาเบาเบา ไม่กิน ซีพียู หรือ แรม", + "message": "มาแล้ว! โปรแกรมบล็อกโฆษณาได้อย่างมีประสิทธิภาพ โดยที่ไม่กินซีพียูหรือแรม", "description": "this will be in the Chrome web store: must be 132 characters or less" }, "dashboardName": { @@ -92,7 +92,7 @@ "description": "Example: 15 (13%)" }, "popupBlockedSinceInstallPrompt": { - "message": "จากวันที่ติดตตั้ง", + "message": "ตั้งแต่ติดตั้ง", "description": "English: since install" }, "popupOr": { @@ -100,11 +100,11 @@ "description": "English: or" }, "popupBlockedOnThisPage_v2": { - "message": "Blocked on this page", + "message": "ถูกบล็อกบนหน้านี้", "description": "For the new mobile-friendly popup design" }, "popupBlockedSinceInstall_v2": { - "message": "Blocked since install", + "message": "ถูกบล็อกตั้งแต่ติดตั้ง", "description": "For the new mobile-friendly popup design" }, "popupDomainsConnected_v2": { @@ -356,7 +356,7 @@ "description": "Checkbox to let user access advanced, technical features" }, "settingsPrefetchingDisabledPrompt": { - "message": "Disable pre-fetching (to prevent any connection for blocked network requests)", + "message": "ปิดใช้งานการดึงข้อมูลล่วงหน้า (เพื่อป้องกันการเชื่อมต่อสำหรับคำขอเครือข่ายที่ถูกบล็อก)", "description": "English: " }, "settingsHyperlinkAuditingDisabledPrompt": { @@ -364,7 +364,7 @@ "description": "English: " }, "settingsWebRTCIPAddressHiddenPrompt": { - "message": "Prevent WebRTC from leaking local IP addresses", + "message": "ป้องกันไม่ให้ WebRTC รั่วไหลที่อยู่ IP ภายในเครื่อง", "description": "English: " }, "settingPerSiteSwitchGroup": { @@ -392,7 +392,7 @@ "description": "The default state for the per-site no-scripting switch" }, "settingsNoCSPReportsPrompt": { - "message": "Block CSP reports", + "message": "บล็อกรายงาน CSP", "description": "background information: https://github.com/gorhill/uBlock/issues/3150" }, "settingsUncloakCnamePrompt": { @@ -404,7 +404,7 @@ "description": "Section for controlling advanced-user settings" }, "settingsAdvancedSynopsis": { - "message": "Features suitable only for technical users", + "message": "เป็นฟีเจอร์ที่เหมาะสำหรับผู้ใช้ที่เชี่ยวชาญเท่านั้น", "description": "Description of section controlling advanced-user settings" }, "settingsAdvancedUserSettings": { @@ -412,7 +412,7 @@ "description": "For the tooltip of a link which gives access to advanced settings" }, "settingsLastRestorePrompt": { - "message": "Last restore:", + "message": "การคืนค่าครั้งล่าสุด:", "description": "English: Last restore:" }, "settingsLastBackupPrompt": { @@ -440,11 +440,11 @@ "description": "A button in the in the _3rd-party filters_ pane" }, "3pParseAllABPHideFiltersPrompt1": { - "message": "Parse and enforce cosmetic filters", + "message": "แยกวิเคราะห์และบังคับใช้ฟิลเตอร์ตกแต่ง", "description": "English: Parse and enforce Adblock+ element hiding filters." }, "3pParseAllABPHideFiltersInfo": { - "message": "Cosmetic filters serve to hide elements in a web page which are deemed to be a visual nuisance, and which can't be blocked by the network request-based filtering engines.", + "message": "ฟิลเตอร์ตกแต่งทำหน้าที่ซ่อนองค์ประกอบในหน้าเว็บซึ่งถือว่าเป็นภาพที่รบกวนสายตา และไม่สามารถบล็อกได้โดยกลไกการกรองตามคำขอของเครือข่าย", "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." }, "3pIgnoreGenericCosmeticFilters": { @@ -456,7 +456,7 @@ "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pSuspendUntilListsAreLoaded": { - "message": "Suspend network activity until all filter lists are loaded", + "message": "ระงับกิจกรรมเครือข่ายจนกว่าจะโหลดรายการตัวกรองทั้งหมด", "description": "A checkbox in the 'Filter lists' pane" }, "3pListsOfBlockedHostsHeader": { @@ -483,6 +483,14 @@ "message": "Malware domains", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "หนึ่งตัวกรองต่อบรรทัด ตัวกรองอาจเป็น hostname หรือตัวกรองที่เข้ากันได้กับ EasyList, บรรทัดที่ขึ้นต้นด้วย <code>!</code> จะถูกละเว้น\n", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" @@ -552,11 +564,11 @@ "description": "English: Apply changes" }, "rulesPermanentHeader": { - "message": "Permanent rules", + "message": "กฏถาวร", "description": "header" }, "rulesTemporaryHeader": { - "message": "Temporary rules", + "message": "กฏชั่วคราว", "description": "header" }, "rulesRevert": { @@ -580,11 +592,11 @@ "description": "Will discard manually-edited content and exit manual-edit mode" }, "rulesImport": { - "message": "Import from file…", + "message": "นำเข้าจากไฟล์", "description": "" }, "rulesExport": { - "message": "Export to file…", + "message": "ส่งออกไปยังไฟล์", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { @@ -604,7 +616,7 @@ "description": "English: label for sort option." }, "rulesSortByType": { - "message": "Rule type", + "message": "ประเภทกฎ", "description": "English: a sort option for list of rules." }, "rulesSortBySource": { @@ -612,15 +624,15 @@ "description": "English: a sort option for list of rules." }, "rulesSortByDestination": { - "message": "Destination", + "message": "ปลายทาง", "description": "English: a sort option for list of rules." }, "whitelistPrompt": { - "message": "The trusted site directives dictate on which web pages uBlock Origin should be disabled. One entry per line.", + "message": "ระบุไซต์ที่เชื่อถือได้เพื่อกำหนดว่าควรปิดการใช้งาน uBlock Origin ที่หน้าเว็บใด พิมพ์หนึ่งรายการต่อบรรทัด", "description": "A concise description of the 'Trusted sites' pane." }, "whitelistImport": { - "message": "Import and append…", + "message": "นำเข้าและรวม", "description": "Button in the 'Trusted sites' pane" }, "whitelistExport": { @@ -632,7 +644,7 @@ "description": "The default filename to use for import/export purpose" }, "whitelistApply": { - "message": "Apply changes", + "message": "ใช้การเปลี่ยนแปลง", "description": "English: Apply changes" }, "logRequestsHeaderType": { @@ -640,7 +652,7 @@ "description": "English: Type" }, "logRequestsHeaderDomain": { - "message": "Domain", + "message": "โดเมน", "description": "English: Domain" }, "logRequestsHeaderURL": { @@ -648,7 +660,7 @@ "description": "English: URL" }, "logRequestsHeaderFilter": { - "message": "Filter", + "message": "ตัวกรอง", "description": "English: Filter" }, "logAll": { @@ -660,11 +672,11 @@ "description": "Pretty name for behind-the-scene network requests" }, "loggerCurrentTab": { - "message": "Current tab", + "message": "แท็บปัจจุบัน", "description": "Appears in the logger's tab selector" }, "loggerReloadTip": { - "message": "Reload the tab content", + "message": "โหลดเนื้อหาแท็บปัจจุบันใหม่", "description": "Tooltip for the reload button in the logger page" }, "loggerDomInspectorTip": { @@ -852,11 +864,11 @@ "description": "Logger settings: a sentence to describe the purpose of the checkboxes below" }, "loggerSettingHideColumnTime": { - "message": "{{input}} Time", + "message": "{{input}} เวลา", "description": "A label for the time column" }, "loggerSettingHideColumnFilter": { - "message": "{{input}} Filter/rule", + "message": "{{input}} ตัวกรอง/กฏ", "description": "A label for the filter or rule column" }, "loggerSettingHideColumnContext": { @@ -872,7 +884,7 @@ "description": "Label for radio-button to pick export format" }, "loggerExportFormatTable": { - "message": "Table", + "message": "ตาราง", "description": "Label for radio-button to pick export format" }, "loggerExportEncodePlain": { @@ -1012,7 +1024,7 @@ "description": "Text for 'Unredact' button" }, "aboutPrivacyPolicy": { - "message": "Privacy policy", + "message": "นโยบายความเป็นส่วนตัว", "description": "Link to privacy policy on GitHub (English)" }, "aboutChangelog": { @@ -1088,7 +1100,7 @@ "description": "No longer used" }, "subscribeButton": { - "message": "Subscribe", + "message": "สมัครสมาชิก", "description": "For the button used to subscribe to a filter list" }, "elapsedOneMinuteAgo": { @@ -1124,7 +1136,7 @@ "description": "Firefox/Fennec-specific: Show Logger" }, "fennecMenuItemBlockingOff": { - "message": "off", + "message": "ปิด", "description": "Firefox-specific: appears as 'uBlock₀ (off)'" }, "docblockedTitle": { @@ -1152,7 +1164,7 @@ "description": "English: Go back" }, "docblockedClose": { - "message": "Close this window", + "message": "ปิดหน้าต่างนี้", "description": "English: Close this window" }, "docblockedDontWarn": { @@ -1196,7 +1208,7 @@ "description": "used as a prompt for the user to provide a custom device name" }, "advancedSettingsWarning": { - "message": "Warning! Change these advanced settings at your own risk.", + "message": "คำเตือน! ปรับเปลี่ยนการตั้งค่าขั้นสูงเหล่านี้ด้วยความระมัดระวัง", "description": "A warning to users at the top of 'Advanced settings' page" }, "genericSubmit": { @@ -1244,13 +1256,17 @@ "description": "Label for buttons used to copy something to the clipboard" }, "genericSelectAll": { - "message": "Select all", + "message": "เลือกทั้งหมด", "description": "Label for buttons used to select all text in editor" }, "toggleCosmeticFiltering": { "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/tr/messages.json b/src/_locales/tr/messages.json index f02c332..31f6aa5 100644 --- a/src/_locales/tr/messages.json +++ b/src/_locales/tr/messages.json @@ -480,7 +480,15 @@ "description": "Filter lists section name" }, "3pGroupMalware": { - "message": "Zararlı alan adları", + "message": "Kötü amaçlı yazılım koruması, güvenlik.", + "description": "Filter lists section name" + }, + "3pGroupSocial": { + "message": "Sosyal gereçler", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Çerez bildirimleri", "description": "Filter lists section name" }, "3pGroupAnnoyances": { @@ -527,14 +535,18 @@ "message": "Bir ağ hatası kaynağın güncellenmesini engelledi.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Her satırda bir filtre. Bir filtre yalın bir alan adı veya EasyList-uyumlu bir filtre olabilir. <code>!</code> ile başlayan satırlar yok sayılacaktır.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Güvenilmeyen kaynaklardan filtre eklemeyin.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Özel filtrelerimi etkinleştir", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Güven gerektiren özel filtrelere izin ver", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "İçe aktar ve ekle", "description": "Button in the 'My filters' pane" @@ -584,7 +596,7 @@ "description": "" }, "rulesExport": { - "message": "Dosyaya aktar", + "message": "Dosyaya aktar…", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { @@ -1251,6 +1263,10 @@ "message": "Kozmetik filtrelemeyi aç/kapat", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript'i Aç/Kapa", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Engelleme modunu gevşet", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/uk/messages.json b/src/_locales/uk/messages.json index 6145210..c78caea 100644 --- a/src/_locales/uk/messages.json +++ b/src/_locales/uk/messages.json @@ -483,6 +483,14 @@ "message": "Домени шкідливих програм", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Віджети соціальних мереж", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Повідомлення про файли cookie", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Надокучливості", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Оновлення не вдалося, у зв'язку з помилкою мережі.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Один фільтр на рядок. Фільтром може бути адреса сайту або фільтр у Adblock Plus-сумісному записі. Рядки, що починаються з <code>!</code> будуть проігноровані.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Не додавати фільтри з невідомих джерел.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Увімкнути власні фільтри", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Дозволити власні фільтри, які потребують довіри", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Імпортувати та додати", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Увімкнути/вимкнути косметичні фільтри", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Перемкнути JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Послаблений режим блокування", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/ur/messages.json b/src/_locales/ur/messages.json index 1b62e56..6fbc6fc 100644 --- a/src/_locales/ur/messages.json +++ b/src/_locales/ur/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "A network error prevented the resource from being updated.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "One filter per line. A filter can be a plain hostname, or an EasyList-compatible filter. Lines prefixed with <code>!</code> will be ignored.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Do not add filters from untrusted sources.", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Import and append…", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Toggle cosmetic filtering", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Toggle JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Relax blocking mode", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/vi/messages.json b/src/_locales/vi/messages.json index b358554..9c50188 100644 --- a/src/_locales/vi/messages.json +++ b/src/_locales/vi/messages.json @@ -483,6 +483,14 @@ "message": "Bảo mật, bảo vệ khỏi phần mềm nguy hiểm", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Tiện ích xã hội", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Các thông báo cookie", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Phiền toái", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "Lỗi mạng ngăn tài nguyên được cập nhật.", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "Một bộ lọc cho mỗi dòng. Bộ lọc có thể là tên máy chủ thuần hoặc bộ lọc tương thích với EasyList. Các dòng có tiền tố <code>!</code> sẽ bị bỏ qua.", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "Không thêm các bộ lọc từ các nguồn không đáng tin cậy. ", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Bật bộ lọc tùy chỉnh của tôi", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Cho phép các bộ lọc tùy chỉnh yêu cầu sự tin cậy", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "Nhập và thêm vào", "description": "Button in the 'My filters' pane" @@ -1251,6 +1263,10 @@ "message": "Bật/tắt lọc phần tử", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "Cho phép/Vô hiệu hoá JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "Nới lỏng chế độ chặn", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json index 76ff0e1..5b50b86 100644 --- a/src/_locales/zh_CN/messages.json +++ b/src/_locales/zh_CN/messages.json @@ -483,6 +483,14 @@ "message": "恶意软件防护、安全", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "社交网络小部件", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie提醒", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "骚扰", "description": "Filter lists section name" @@ -527,14 +535,18 @@ "message": "资源更新因网络错误受阻。", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "一行一条过滤规则。每条规则可以是一个普通的主机名或者是一条与 EasyList 兼容的过滤规则。以 <code>!</code> 开头的行将被忽略。", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { "message": "请勿添加未信任来源的过滤规则。", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "Enable my custom filters", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "Allow custom filters requiring trust", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "导入并添加…", "description": "Button in the 'My filters' pane" @@ -900,7 +912,7 @@ "description": "Header of 'Documentation' section in Support pane" }, "supportS1P1": { - "message": "请至 <code>uBlock/wiki</code> 参阅 uBlock Origin 的所有功用以及使用说明。", + "message": "请至 <code>uBlock/wiki</code> 参阅 uBlock Origin 的所有功能以及使用说明。", "description": "First paragraph of 'Documentation' section in Support pane" }, "supportS2H": { @@ -912,7 +924,7 @@ "description": "First paragraph of 'Questions and support' section in Support pane" }, "supportS3H": { - "message": "过滤规则或者网页的问题", + "message": "过滤规则问题/网页被损坏", "description": "Header of 'Filter issues' section in Support pane" }, "supportS3P1": { @@ -1251,6 +1263,10 @@ "message": "是否应用元素过滤规则", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "JavaScript 开关", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "放宽拦截限制", "description": "Label for keyboard shortcut used to relax blocking mode" diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json index 10f947b..f07258b 100644 --- a/src/_locales/zh_TW/messages.json +++ b/src/_locales/zh_TW/messages.json @@ -68,7 +68,7 @@ "description": "Title for the advanced settings page" }, "popupPowerSwitchInfo": { - "message": "點擊:對此網站 停用/啟用 uBlock₀ 。\n\nCtrl + 點擊:僅在此頁面停用 uBlock₀ 。", + "message": "點擊:在此網站 停用/啟用 uBlock₀ 。\n\nCtrl + 點擊:僅在此頁面停用 uBlock₀ 。", "description": "English: Click: disable/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { @@ -356,7 +356,7 @@ "description": "Checkbox to let user access advanced, technical features" }, "settingsPrefetchingDisabledPrompt": { - "message": "停用預先載入模式(避免連線至已阻擋的網路要求)", + "message": "停用「預先取回連結」功能(避免連接至已阻擋的網路請求)", "description": "English: " }, "settingsHyperlinkAuditingDisabledPrompt": { @@ -404,7 +404,7 @@ "description": "Section for controlling advanced-user settings" }, "settingsAdvancedSynopsis": { - "message": "僅適合技術使用者的功能", + "message": "僅適合技術性使用者的功能", "description": "Description of section controlling advanced-user settings" }, "settingsAdvancedUserSettings": { @@ -444,7 +444,7 @@ "description": "English: Parse and enforce Adblock+ element hiding filters." }, "3pParseAllABPHideFiltersInfo": { - "message": "元素隱藏過濾規則 用於隱藏網頁中礙眼,且不能被基於網路請求的過濾引擎所阻擋的元素。", + "message": "「元素隱藏過濾規則」用於隱藏網頁中礙眼,且不能被以網路請求為基礎之過濾引擎所阻擋的元素。", "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." }, "3pIgnoreGenericCosmeticFilters": { @@ -452,7 +452,7 @@ "description": "This will cause uBO to ignore all generic cosmetic filters." }, "3pIgnoreGenericCosmeticFiltersInfo": { - "message": "通用元素隱藏過濾規則 是會套用在所有網頁的元素隱藏過濾規則。啟用此選項會減少每個網頁,因處理一般元素隱藏過濾規則,而增加的記憶體與 CPU 使用量。\n\n建議在效能較差的裝置上啟用此選項。", + "message": "「通用元素隱藏過濾規則」是會套用在所有網站上的元素隱藏過濾規則。啟用此選項會消除網頁處理此類規則時增加的額外記憶體與 CPU 使用量。\n\n建議在較低效能的裝置上啟用此選項。", "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pSuspendUntilListsAreLoaded": { @@ -480,7 +480,15 @@ "description": "Filter lists section name" }, "3pGroupMalware": { - "message": "惡意軟體防護及安全性", + "message": "惡意軟體保護及保安", + "description": "Filter lists section name" + }, + "3pGroupSocial": { + "message": "社交媒體小工具", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie 通知", "description": "Filter lists section name" }, "3pGroupAnnoyances": { @@ -516,7 +524,7 @@ "description": "used as a tooltip for eye icon beside a list" }, "3pLastUpdate": { - "message": "上次更新:{{ago}}。\n點擊此處以要求更新。", + "message": "上次更新:{{ago}}。\n點擊此處以強制更新。", "description": "used as a tooltip for the clock icon beside a list" }, "3pUpdating": { @@ -527,14 +535,18 @@ "message": "因網路錯誤無法更新資源。", "description": "used as a tooltip for error icon beside a list" }, - "1pFormatHint": { - "message": "每行一個過濾規則。規則可以單純是主機名稱,或是與 EasyList 相容的過濾規則。以 <code>!</code> 開頭的行將被忽略。", - "description": "Short information about how to create custom filters" - }, "1pTrustWarning": { - "message": "請勿添加未信任來源的過濾規則。", + "message": "切勿添加來歷不明的過濾規則。", "description": "Warning against copy-pasting filters from random sources" }, + "1pEnableMyFiltersLabel": { + "message": "啟用自訂過濾器", + "description": "Label for the checkbox use to enable/disable 'My filters' list" + }, + "1pTrustMyFiltersLabel": { + "message": "允許需要信任的自訂過濾規則", + "description": "Label for the checkbox use to trust the content of 'My filters' list" + }, "1pImport": { "message": "匯入並加入…", "description": "Button in the 'My filters' pane" @@ -756,7 +768,7 @@ "description": "Label to identify a root context field (typically a hostname)" }, "loggerEntryDetailsPartyness": { - "message": "所屬方", + "message": "第一方/第三方", "description": "Label to identify a field providing partyness information" }, "loggerEntryDetailsType": { @@ -864,7 +876,7 @@ "description": "A label for the context column" }, "loggerSettingHideColumnPartyness": { - "message": "{{input}} 所屬方", + "message": "{{input}} 第一方/第三方", "description": "A label for the partyness column" }, "loggerExportFormatList": { @@ -888,11 +900,11 @@ "description": "Text for button which open an external webpage in Support pane" }, "supportReportSpecificButton": { - "message": "發出新報告", + "message": "建立新報告", "description": "Text for button which open an external webpage in Support pane" }, "supportFindSpecificButton": { - "message": "尋找類似的報告", + "message": "尋找類似報告", "description": "A clickable link in the filter issue reporter section" }, "supportS1H": { @@ -908,11 +920,11 @@ "description": "Header of 'Questions and support' section in Support pane" }, "supportS2P1": { - "message": "問題及其他類型的求助,可以在 <code>/r/uBlockOrigin</code> subreddit 獲得協助與解答。", + "message": "如有問題或需要其他類型的幫助,可以在 <code>/r/uBlockOrigin</code> subreddit 獲得協助與解答。", "description": "First paragraph of 'Questions and support' section in Support pane" }, "supportS3H": { - "message": "過濾器問題 / 網站被搞壞", + "message": "過濾器問題 / 網站被破壞", "description": "Header of 'Filter issues' section in Support pane" }, "supportS3P1": { @@ -920,11 +932,11 @@ "description": "First paragraph of 'Filter issues' section in Support pane" }, "supportS3P2": { - "message": "<b>特別注意:</b>不要把其他同性質的阻擋工具跟 uBlock Origin 混用,否則可能會造成特定網站的過濾器問題。", + "message": "<b>特別注意:</b>不要把其他同性質的阻擋工具跟 uBlock Origin 混用,否則可能會在特定網站造成過濾問題。", "description": "Second paragraph of 'Filter issues' section in Support pane" }, "supportS3P3": { - "message": "<b>小提示:</b>請確定您的過濾器清單已經更新至最新版本。我們主要用 <span data-url=\"logger-ui.html#_\">記錄器</span> 來分析過濾器相關問題。", + "message": "<b>小提示:</b>請確定您的過濾器清單已經更新至最新版本。我們主要用<span data-url=\"logger-ui.html#_\">「記錄器」</span>來分析過濾器相關問題。", "description": "Third paragraph of 'Filter issues' section in Support pane" }, "supportS4H": { @@ -944,7 +956,7 @@ "description": "First paragraph of 'Troubleshooting Information' section in Support pane" }, "supportS5P2": { - "message": "<b>特別注意:</b>預設情況下,潛在隱私或敏感資料會被替換掉。替換過的資訊可能會加大問題解決的難度。", + "message": "<b>特別注意:</b>預設情況下,潛在私人或敏感的資料會被略去。略去的資訊可能會讓問題更難獲解決。", "description": "Second paragraph of 'Troubleshooting Information' section in Support pane" }, "supportS6H": { @@ -952,11 +964,11 @@ "description": "Header of 'Report a filter issue' section in Support pane" }, "supportS6P1S1": { - "message": "請先確認是否已經有人回報過此問題,以免重複回報造成維護者的而外負擔。", + "message": "為免給志願者帶來額外負擔,請先檢查問題有沒有被回報過,避免重複回報。", "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S1": { - "message": "過濾器清單每天更新。請確認您的問題無法用最新的過濾器清單解決。", + "message": "過濾器清單每天更新,請確保問題尚未在最新版本中解決。", "description": "A paragraph in the filter issue reporter section" }, "supportS6P2S2": { @@ -976,11 +988,11 @@ "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option1": { - "message": "顯示廣告或廣告移除後留下的天窗", + "message": "會顯示廣告或殘留空位", "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option2": { - "message": "會覆蓋內容或有其他煩人的內容", + "message": "含有覆蓋物或其他滋擾", "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option3": { @@ -988,15 +1000,15 @@ "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option4": { - "message": "有隱私權相關問題", + "message": "有私穩相關問題", "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option5": { - "message": "開啟 uBlock Origin 的時候網頁運作不正常", + "message": "開啟 uBlock Origin 的時候運作不正常", "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option6": { - "message": "開啟不想出現的分頁或視窗", + "message": "會開啟不想要的分頁或視窗", "description": "An entry in the widget used to select the type of issue" }, "supportS6Checkbox1": { @@ -1020,7 +1032,7 @@ "description": "" }, "aboutCode": { - "message": "原始碼(GPLv3)", + "message": "源碼(GPLv3)", "description": "English: Source code (GPLv3)" }, "aboutContributors": { @@ -1028,7 +1040,7 @@ "description": "English: Contributors" }, "aboutSourceCode": { - "message": "原始碼", + "message": "源碼", "description": "Link text to source code repo" }, "aboutTranslations": { @@ -1228,7 +1240,7 @@ "description": "A context menu entry, present when large media elements have been blocked on the current site" }, "contextMenuViewSource": { - "message": "檢視原始碼…", + "message": "檢視源碼…", "description": "A context menu entry, to view the source code of the target resource" }, "shortcutCapturePlaceholder": { @@ -1251,6 +1263,10 @@ "message": "切換是否啟用元素隱藏過濾規則", "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, + "toggleJavascript": { + "message": "切換 JavaScript", + "description": "Label for keyboard shortcut used to toggle no-scripting switch" + }, "relaxBlockingMode": { "message": "放寬封鎖量模式", "description": "Label for keyboard shortcut used to relax blocking mode" @@ -1280,7 +1296,7 @@ "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { - "message": "無法在瀏覽器啟動的時候正確過濾。請重新載入頁面來確保過濾正確。", + "message": "無法在瀏覽器啟動的時候正確過濾頁面。請重新載入來確保過濾正確。", "description": "A warning which will appear in the popup panel if needed" }, "dummy": { diff --git a/src/css/1p-filters.css b/src/css/1p-filters.css index 679e4b8..9e087cb 100644 --- a/src/css/1p-filters.css +++ b/src/css/1p-filters.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } @@ -14,7 +15,11 @@ body { .body { flex-shrink: 0; } -[data-i18n="1pTrustWarning"] { +/* https://github.com/uBlockOrigin/uBlock-issues/issues/3058 */ +:root.mobile body { + min-height: unset; + } +html:not(.mobile) [data-i18n="1pTrustWarning"] { font-weight: bold; } .codeMirrorContainer { diff --git a/src/css/3p-filters.css b/src/css/3p-filters.css index 014dd20..f5bed6b 100644 --- a/src/css/3p-filters.css +++ b/src/css/3p-filters.css @@ -7,7 +7,6 @@ body { } #actions { background-color: var(--surface-1); - padding: var(--default-gap-small) 0 var(--default-gap-xsmall) 0; position: sticky; top: 0; z-index: 10; @@ -15,7 +14,8 @@ body { #buttonUpdate.active { pointer-events: none; } -#buttonUpdate.active .fa-icon svg { +#buttonUpdate.active .fa-icon svg, +body.working #buttonUpdate:not(.disabled) .fa-icon svg { animation: spin 1s linear infinite; transform-origin: 50%; } @@ -24,7 +24,7 @@ body.updating #actions, body.working #actions { cursor: progress; } -body.updating #actions button, +body.updating #actions #buttonUpdate, body.working #actions button { pointer-events: none; } @@ -54,23 +54,9 @@ body.working #actions button { transform: rotate(180deg); } -#lists .searchbar { - align-items: center; - column-gap: var(--default-gap-xxsmall); - display: inline-flex; +#lists .searchfield { margin-block-start: calc(var(--font-size) * 0.75); margin-inline-start: var(--checkbox-size); - position: relative; - } -#lists .searchbar input { - padding-inline-start: var(--default-gap-large); - } -#lists .searchbar .fa-icon { - color: var(--ink-4); - fill: var(--ink-4); - left: 4px; - position: absolute; - transform: none; } #lists.searchMode > .listEntries .listEntries, #lists.searchMode > .listEntries .listEntry.searchMatch { @@ -212,13 +198,14 @@ body.working #actions button { color: var(--info2-ink); fill: var(--info2-ink); } -body:not(.updating) #lists .listEntry.checked.obsolete > .detailbar .iconbar .obsolete { +body:not(.updating,.working) #lists .listEntry.checked.obsolete > .detailbar .iconbar .obsolete { display: inline-flex; } #lists .iconbar .updating { transform-origin: 50%; } -body.updating #lists .listEntry.checked.obsolete > .detailbar .iconbar .updating { +body.updating #lists .listEntry.checked.obsolete > .detailbar .iconbar .updating, +body.working #lists .listEntry.checked.obsolete:not(.cached) > .detailbar .iconbar .updating { animation: spin 1s steps(8) infinite; display: inline-flex; } diff --git a/src/css/advanced-settings.css b/src/css/advanced-settings.css index c67e750..10225dd 100644 --- a/src/css/advanced-settings.css +++ b/src/css/advanced-settings.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/css/asset-viewer.css b/src/css/asset-viewer.css index 8b6f1da..d2df68a 100644 --- a/src/css/asset-viewer.css +++ b/src/css/asset-viewer.css @@ -23,6 +23,7 @@ body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; padding: 0; diff --git a/src/css/code-viewer.css b/src/css/code-viewer.css index 774fa69..40b5114 100644 --- a/src/css/code-viewer.css +++ b/src/css/code-viewer.css @@ -3,6 +3,7 @@ body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; padding: 0; diff --git a/src/css/codemirror.css b/src/css/codemirror.css index 2d15bf8..fce571b 100644 --- a/src/css/codemirror.css +++ b/src/css/codemirror.css @@ -3,6 +3,13 @@ overflow: hidden; position: relative; } +.codeMirrorContainer.cm-maximized { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + } .CodeMirror { background-color: var(--surface-0); box-sizing: border-box; @@ -153,7 +160,6 @@ } .cm-search-widget { - align-items: center; background-color: var(--cm-gutter-surface); border-bottom: 1px solid var(--cm-gutter-border); cursor: default; @@ -162,6 +168,7 @@ flex-shrink: 0; flex-wrap: wrap; justify-content: space-between; + line-height: 1.5; padding: var(--default-gap-xsmall); row-gap: var(--default-gap-xsmall); user-select: none; @@ -176,20 +183,44 @@ text-align: end; } +.cm-search-widget .cm-maximize { + fill: none; + flex-grow: 0; + font-size: 130%; + height: 1em; + stroke-width: 3px; + stroke: var(--ink-0); + width: 1em; + } +.cm-search-widget .cm-maximize * { + pointer-events: none; + } +.codeMirrorContainer[data-maximizable="false"] .cm-search-widget .cm-maximize { + display: none; + } +.codeMirrorContainer .cm-search-widget .cm-maximize svg > path:nth-child(2), +.codeMirrorContainer.cm-maximized .cm-search-widget .cm-maximize svg > path:nth-child(1) { + display: none; + } +.codeMirrorContainer.cm-maximized .cm-search-widget .cm-maximize svg > path:nth-child(2) { + display: initial; + } +html:not(.mobile) .cm-search-widget .cm-maximize:hover { + transform: scale(1.2); + } + .cm-search-widget-input { display: inline-flex; flex-grow: 1; + flex-wrap: nowrap; } .cm-search-widget .fa-icon { - fill: var(--cm-gutter-ink); font-size: 140%; } -.cm-search-widget .fa-icon:not(.fa-icon-ro):hover { - fill: var(--ink-1); +html:not(.mobile) .cm-search-widget .fa-icon:not(.fa-icon-ro):hover { + transform: scale(1.2); } .cm-search-widget-input input { - border: 1px solid var(--cm-gutter-ink); - display: inline-flex; flex-grow: 1; max-width: 16em; } @@ -198,7 +229,6 @@ display: inline-flex; flex-grow: 0; font-size: var(--font-size-smaller); - min-width: 6em; visibility: hidden; } .cm-search-widget[data-query] .cm-search-widget-count { @@ -207,9 +237,6 @@ .cm-search-widget[data-query] .cm-search-widget-count:empty { visibility: hidden; } -.cm-search-widget .cm-search-widget-button:hover { - color: #000; - } .cm-search-widget .sourceURL[href=""] { visibility: hidden; } diff --git a/src/css/common.css b/src/css/common.css index 1a8ba0b..fae4bb5 100644 --- a/src/css/common.css +++ b/src/css/common.css @@ -52,11 +52,11 @@ body { margin: 0; padding: 0; } -a { +a:not(.fa-icon) { color: var(--link-ink); fill: var(--link-ink); } -a:hover { +a:not(.fa-icon):hover { color: var(--link-hover-ink); fill: var(--link-hover-ink); } @@ -271,6 +271,24 @@ select { padding: 2px; } +.searchfield { + align-items: center; + column-gap: var(--default-gap-xxsmall); + display: inline-flex; + position: relative; + } +.searchfield .fa-icon { + color: var(--ink-4); + fill: var(--ink-4); + font-size: 1em !important; + left: 2px; + position: absolute; + transform: none; + } +.searchfield input:not(:placeholder-shown) ~ .fa-icon { + display: none; + } + .hidden { display: none; height: 0; @@ -309,7 +327,7 @@ select { font-size: 1.2rem; padding: 0; } - button.iconified > [data-i18n] { + button.iconified:not(.dontshrink) > [data-i18n] { display: none; } } @@ -331,6 +349,22 @@ select { width: 100%; } +.wikilink[href=""] { + display: none; + } +.wikilink.fa-icon { + color: var(--info0-ink); + fill: var(--info0-ink); + padding: var(--default-gap-xxsmall) var(--default-gap-xsmall); + } +.wikilink.fa-icon:hover { + transform: scale(1.2); + } +.wikilink.fa-icon > svg { + height: 1.25rem; + width: 1.25rem; + } + /* high dpi devices */ :root.hidpi button { font-family: Metropolis, sans-serif; diff --git a/src/css/dashboard.css b/src/css/dashboard.css index ba02d97..b75949e 100644 --- a/src/css/dashboard.css +++ b/src/css/dashboard.css @@ -2,6 +2,7 @@ html, body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; justify-content: stretch; overflow: hidden; position: relative; @@ -11,11 +12,12 @@ body.notReady { display: none; } #dashboard-nav { + align-items: center; border: 0; border-bottom: 1px solid var(--border-1); display: flex; flex-shrink: 0; - flex-wrap: wrap; + justify-content: space-between; overflow-x: hidden; padding: 0; position: sticky; @@ -23,6 +25,10 @@ body.notReady { width: 100%; z-index: 10; } +#dashboard-nav > span { + display: flex; + flex-wrap: wrap; + } .tabButton { background-color: transparent; border: 0; @@ -75,6 +81,7 @@ iframe { } #unsavedWarning > div:last-of-type { height: 100vh; + height: 100svh; position: absolute; width: 100vw; } @@ -105,8 +112,7 @@ body.noDashboard #dashboard-nav { border-bottom-color: var(--dashboard-tab-hover-border); } -/* touch-screen devices */ -:root.mobile #dashboard-nav { +:root.mobile #dashboard-nav > span { flex-wrap: nowrap; overflow-x: auto; } diff --git a/src/css/devtools.css b/src/css/devtools.css index 425aac4..bb4c40d 100644 --- a/src/css/devtools.css +++ b/src/css/devtools.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/css/document-blocked.css b/src/css/document-blocked.css index 25dd204..62d4921 100644 --- a/src/css/document-blocked.css +++ b/src/css/document-blocked.css @@ -42,10 +42,10 @@ a { word-break: break-all; } #warningSign { - width: 100%; - } -#warningSign > a { + color: var(--accent-surface-1); + fill: var(--accent-surface-1); font-size: 96px; + width: 100%; } #theURL { color: var(--ink-2); diff --git a/src/css/dom-inspector.css b/src/css/dom-inspector.css index 71ba348..2f2ca14 100644 --- a/src/css/dom-inspector.css +++ b/src/css/dom-inspector.css @@ -3,6 +3,7 @@ html#ublock0-inspector, background: transparent; box-sizing: border-box; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; width: 100vw; diff --git a/src/css/dyna-rules.css b/src/css/dyna-rules.css index 35e0f8c..9a6bd8d 100644 --- a/src/css/dyna-rules.css +++ b/src/css/dyna-rules.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/css/epicker-ui.css b/src/css/epicker-ui.css index d09e1ef..8ce5ade 100644 --- a/src/css/epicker-ui.css +++ b/src/css/epicker-ui.css @@ -3,6 +3,7 @@ html#ublock0-epicker, background: transparent; cursor: not-allowed; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; width: 100vw; @@ -13,29 +14,41 @@ html#ublock0-epicker, #ublock0-epicker aside { background-color: var(--surface-1); border: 1px solid var(--border-2); - bottom: 2px; box-sizing: border-box; cursor: default; display: none; - max-height: calc(100vh - 4px); - max-width: 36rem; - min-width: 24rem; + flex-direction: column; + max-width: min(32rem, 100vw - 4px); + min-width: min(24rem, 100vw - 4px); overflow-y: auto; - padding: 4px; position: fixed; - right: 2px; - width: calc(40% - 2px); + width: min(32rem, 100vw - 4px); + z-index: 100; } -/* https://github.com/uBlockOrigin/uBlock-issues/discussions/2114 */ -#ublock0-epicker aside { - min-width: min(24rem, 100vw - 4px); +#ublock0-epicker:not(.zap) aside { + display: flex; +} +#ublock0-epicker:not(.paused) aside, +#ublock0-epicker.minimized aside { + min-width: min(16rem, 100vw - 4px); + overflow: hidden; + width: min(16rem, 100vw - 4px); +} +#ublock0-epicker:not(.paused) aside > *:not(#windowbar), +#ublock0-epicker.minimized aside > *:not(#windowbar) { + display: none; } -#ublock0-epicker.paused:not(.zap) aside { - display: block; +#ublock0-epicker aside > *:not(:first-child) { + padding: 0 2px; } + #ublock0-epicker #toolbar { display: flex; + justify-content: space-between; } +#ublock0-epicker #toolbar button { + min-width: 5em; + } #ublock0-epicker ul { margin: 0.25em 0 0 0; } @@ -43,16 +56,6 @@ html#ublock0-epicker, background-color: var(--button-preferred-surface); color: var(--button-preferred-ink); } -#ublock0-epicker #move { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAFElEQVQI12NgwAfKy8v/M5ANYLoBshgEyQo6H9UAAAAASUVORK5CYII='); - cursor: grab; - flex-grow: 1; - margin: 2px 4px; - opacity: 0.8; - } -#ublock0-epicker aside.moving #move { - cursor: grabbing; -} #ublock0-epicker section { border: 0; box-sizing: border-box; @@ -70,8 +73,8 @@ html#ublock0-epicker, #ublock0-epicker section .codeMirrorContainer { border: none; box-sizing: border-box; - height: 8em; - max-height: 50vh; + height: 6em; + max-height: min(6em, 10vh); min-height: 1em; padding: 2px; width: 100%; @@ -129,8 +132,8 @@ html#ublock0-epicker, ); display: inline-block; flex-shrink: 0; - height: 16px; - width: 16px; + height: 20px; + width: 20px; } .resultsetModifier > span > span:nth-of-type(3) { background-color: var(--surface-3); @@ -174,12 +177,9 @@ html#ublock0-epicker, overflow: hidden; } #ublock0-epicker #candidateFilters { - max-height: 14em; + max-height: min(12em, 18vh); overflow-y: auto; } -#ublock0-epicker #candidateFilters > li:first-of-type { - margin-bottom: 0.5em; -} #ublock0-epicker .changeFilter > li > span:nth-of-type(1) { font-weight: bold; } @@ -187,6 +187,9 @@ html#ublock0-epicker, font-size: smaller; color: gray; } +#ublock0-epicker #candidateFilters [data-i18n] { + font-size: 90%; +} #ublock0-epicker #candidateFilters .changeFilter { list-style-type: none; margin: 0 0 0 1em; @@ -207,36 +210,7 @@ html#ublock0-epicker, background-color: var(--surface-2); } -/** - https://github.com/gorhill/uBlock/issues/3449 - https://github.com/uBlockOrigin/uBlock-issues/issues/55 -**/ -@keyframes startDialog { - 0% { opacity: 1.0; } - 60% { opacity: 1.0; } - 100% { opacity: 0.1; } -} -#ublock0-epicker.paused aside { - opacity: 0.1; - visibility: visible; - z-index: 100; -} -#ublock0-epicker.paused:not(.show):not(.hide) aside:not(:hover) { - animation-duration: 1.6s; - animation-name: startDialog; - animation-timing-function: linear; -} -#ublock0-epicker.paused aside:hover { - opacity: 1; -} -#ublock0-epicker.paused.show aside { - opacity: 1; -} -#ublock0-epicker.paused.hide aside { - opacity: 0.1; -} - -#ublock0-epicker svg { +#ublock0-epicker svg#sea { cursor: crosshair; box-sizing: border-box; height: 100%; @@ -245,26 +219,62 @@ html#ublock0-epicker, top: 0; width: 100%; } -#ublock0-epicker.paused svg { +#ublock0-epicker.paused svg#sea { cursor: not-allowed; } -#ublock0-epicker svg > path:first-child { +#ublock0-epicker svg#sea > path:first-child { fill: rgba(0,0,0,0.5); fill-rule: evenodd; } -#ublock0-epicker svg > path + path { +#ublock0-epicker svg#sea > path + path { stroke: #F00; stroke-width: 0.5px; fill: rgba(255,63,63,0.20); } -#ublock0-epicker.zap svg > path + path { +#ublock0-epicker.zap svg#sea > path + path { stroke: #FF0; stroke-width: 0.5px; fill: rgba(255,255,63,0.20); } -#ublock0-epicker.preview svg > path { +#ublock0-epicker.preview svg#sea > path { fill: rgba(0,0,0,0.10); } -#ublock0-epicker.preview svg > path + path { +#ublock0-epicker.preview svg#sea > path + path { stroke: none; } + + +#ublock0-epicker #windowbar { + display: flex; +} +#ublock0-epicker #windowbar svg { + fill: none; + pointer-events: none; + stroke: var(--ink-1); + stroke-width: 3px; +} +#ublock0-epicker #windowbar #move { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAFElEQVQI12NgwAfKy8v/M5ANYLoBshgEyQo6H9UAAAAASUVORK5CYII='); + cursor: grab; + flex-grow: 1; + opacity: 0.8; +} +#ublock0-epicker aside.moving #windowbar #move { + cursor: grabbing; +} +#windowbar #quit, +#windowbar #minimize { + height: 2em; + width: 2em; +} +#windowbar #quit:hover, +#windowbar #minimize:hover { + background-color: var(--surface-2) +} +#ublock0-epicker.minimized #windowbar #minimize svg > path, +#windowbar #minimize svg > rect { + display: none; +} +#ublock0-epicker.minimized #windowbar #minimize svg > rect { + display: initial; +} diff --git a/src/css/fa-icons.css b/src/css/fa-icons.css index f6d517d..adac445 100644 --- a/src/css/fa-icons.css +++ b/src/css/fa-icons.css @@ -89,12 +89,14 @@ .fa-icon > .fa-icon_sun-o { width: calc(1em * 1708 / 1792); } +.fa-icon > .fa-icon_book, .fa-icon > .fa-icon_download-alt, .fa-icon > .fa-icon_font, .fa-icon > .fa-icon_search, .fa-icon > .fa-icon_spinner, .fa-icon > .fa-icon_unlink, .fa-icon > .fa-icon_upload-alt, +.fa-icon > .fa-icon_volume-up, .fa-icon > .fa-icon_zoom-in, .fa-icon > .fa-icon_zoom-out { width: calc(1em * 1664 / 1792); diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index 8e5065c..7ae632e 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -2,6 +2,7 @@ body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } @@ -218,6 +219,7 @@ body[dir="rtl"] #netInspector #filterExprPicker { width: 100%; } #vwRenderer .logEntry { + background-color: var(--surface-1); display: block; left: 0; overflow: hidden; @@ -228,7 +230,11 @@ body[dir="rtl"] #netInspector #filterExprPicker { display: none; } #vwRenderer .logEntry > div { + border-bottom: 1px dotted var(--border-1); + box-sizing: border-box; + display: flex; height: 100%; + max-height: 200px; white-space: nowrap; } #vwRenderer .logEntry > div[data-status="1"], @@ -271,7 +277,7 @@ body[dir="rtl"] #netInspector #filterExprPicker { color: white; } #vwRenderer .logEntry > div[data-type="error"] { - color: var(--sf-error-ink); + color: var(--cm-negative); } #vwRenderer .logEntry > div[data-type="info"] { color: var(--sf-def-ink); @@ -283,10 +289,8 @@ body[dir="rtl"] #netInspector #filterExprPicker { opacity: 0.7; } -#vwRenderer .logEntry > div > span { - border: 1px dotted var(--border-1); - border-top: 0; - border-right: 0; +#vwRenderer .logEntry > .fields > span { + border-left: 1px dotted var(--border-1); box-sizing: border-box; display: inline-block; height: 100%; @@ -296,86 +300,74 @@ body[dir="rtl"] #netInspector #filterExprPicker { white-space: nowrap; word-break: break-all; } -#vwRenderer .logEntry > div.canDetails:hover > span { +#vwRenderer .logEntry > div:hover > span { background-color: rgba(0,0,0,0.04); } -body[dir="ltr"] #vwRenderer .logEntry > div > span:first-child { +body[dir="ltr"] #vwRenderer .logEntry > .fields > span:first-child { border-left: 0; } -body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child { +body[dir="rtl"] #vwRenderer .logEntry > .fields > span:first-child { border-right: 0; } #vwRenderer .logEntry > div > span:nth-of-type(1) { } #vwRenderer .logEntry > div > span:nth-of-type(2) { } -#vwRenderer .logEntry > div > span:nth-of-type(2) { +#vwRenderer .logEntry > .fields > span:nth-of-type(2) { text-overflow: ellipsis; } -#vwRenderer .logEntry > div.messageRealm > span:nth-of-type(2) ~ span { +#vwRenderer .logEntry > .fields.messageRealm > span:nth-of-type(2) ~ span { display: none; } -.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(2) { +.vExpanded #vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(2) { overflow-y: auto; white-space: pre-line; } -#vwRenderer .logEntry > div.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) { +#vwRenderer .logEntry > .fields.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) { text-align: center; } -#vwRenderer .logEntry > div.extendedRealm > span:nth-of-type(2) > span:first-of-type { +#vwRenderer .logEntry > .fields.extendedRealm > span:nth-of-type(2) > span:first-of-type { display: none; } -#vwRenderer .logEntry > div.extendedRealm > span:nth-of-type(2) > span:last-of-type { - pointer-events: none; - } -#vwRenderer .logEntry > div.extendedRealm.isException > span:nth-of-type(2) > span:last-of-type { +#vwRenderer .logEntry > .fields.extendedRealm.isException > span:nth-of-type(2) > span:last-of-type { text-decoration: line-through rgba(0,0,255,0.7); } -#vwRenderer .logEntry > div > span:nth-of-type(3) { +#vwRenderer .logEntry > .fields > span:nth-of-type(3) { font-family: monospace; padding-left: 0.3em; padding-right: 0.3em; text-align: center; } -#vwRenderer .logEntry > div.canDetails:hover > span:not(:nth-of-type(4)):not(:nth-of-type(8)) { - background: rgba(0, 0, 255, 0.1); - cursor: zoom-in; - } -#netInspector:not(.vExpanded) #vwRenderer .logEntry > div > span:nth-of-type(4) { - direction: rtl; +#netInspector:not(.vExpanded) #vwRenderer .logEntry > .fields > span:nth-of-type(4) { text-align: right; - unicode-bidi: plaintext; } -#vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) { +#vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(4) { text-overflow: ellipsis; } -.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) { +.vExpanded #vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(4) { overflow-y: auto; text-overflow: clip; white-space: pre-line; } -#vwRenderer .logEntry > div > span:nth-of-type(5) { +#vwRenderer .logEntry > .fields > span:nth-of-type(5) { text-align: center; } -/* visual for tabless network requests */ -#vwRenderer .logEntry > div > span:nth-of-type(5) { - position: relative; +#vwRenderer .logEntry > .fields > span:nth-of-type(3), +#vwRenderer .logEntry > .fields > span:nth-of-type(5), +#vwRenderer .logEntry > .fields > span:nth-of-type(7) { + white-space: nowrap !important; } -#vwRenderer .logEntry > div > span:nth-of-type(7) { - } -#vwRenderer #vwContent .logEntry > div > span:nth-of-type(7) { - } -#vwRenderer .logEntry > div > span:nth-of-type(8) { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) { position: relative; } -#vwRenderer #vwContent .logEntry > div > span:nth-of-type(8) { +#vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(8) { text-overflow: ellipsis; } -.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(8) { +.vExpanded #vwRenderer #vwContent .logEntry > .fields > span:nth-of-type(8) { overflow-y: auto; white-space: pre-line; } -#vwRenderer .logEntry > div > span:nth-of-type(8) b { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) b { font-weight: bold; } #vwRenderer .logEntry > div[data-status="1"] > span:nth-of-type(8) b, @@ -396,37 +388,65 @@ body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child { .netFilteringDialog > .panes > .details > div[data-status="2"] b { background-color: rgb(var(--popup-cell-allow-surface-rgb) / 100%); } -#vwRenderer .logEntry > div > span:nth-of-type(8) a { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) a { align-items: center; background-color: dimgray; + bottom: 0; color: white; display: none; - height: 100%; + height: min(100%, 1.5em); justify-content: center; - padding: 0 0.25em; + padding: 0.1em; opacity: 0.4; position: absolute; right: 0; text-decoration: none; - top: 0; - width: 2rem; + width: 1.5em; } -#netInspector.vExpanded #vwRenderer .logEntry > div > span:nth-of-type(8) a { +#netInspector.vExpanded #vwRenderer .logEntry > .fields > span:nth-of-type(8) a { bottom: 0px; height: unset; - padding: 0.25em; + padding: 0.2em; top: unset; } -#vwRenderer .logEntry > div > span:nth-of-type(8) a::after { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) a::after { content: '\2197'; } -#vwRenderer .logEntry > div.networkRealm > span:nth-of-type(8):hover a { +#vwRenderer .logEntry > .fields.networkRealm > span:nth-of-type(8):hover a { display: inline-flex; } -#vwRenderer .logEntry > div > span:nth-of-type(8) a:hover { +#vwRenderer .logEntry > .fields > span:nth-of-type(8) a:hover { opacity: 1; } +@keyframes unrollRow { + to { + box-shadow: 0 2px 3px 0 #444; + height: auto; + max-height: 200px; + z-index: 1; + } +} +@keyframes unrollRowCell { + to { + height: auto; + overflow-y: auto; + white-space: pre-wrap; + } +} +#netInspector:not(.vExpanded) #vwRenderer .logEntry:hover { + animation-delay: 0.8s; + animation-fill-mode: forwards; + animation-name: unrollRow; + animation-timing-function: step-start; + } +#netInspector:not(.vExpanded) #vwRenderer .logEntry:hover > .fields > span { + animation-delay: 0.8s; + animation-fill-mode: forwards; + animation-name: unrollRowCell; + animation-timing-function: step-start; + } + #vwRenderer #vwBottom { background-color: #00F; height: 0; @@ -448,6 +468,7 @@ body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child { max-width: 640px; min-width: min(100%, 640px); position: absolute; + z-index: 2; } #netInspector .entryTools:empty { display: none; @@ -492,6 +513,7 @@ body[dir="rtl"] .closeButton { bottom: 0; display: none; max-height: min(800px, calc(100vh - 2rem)); + max-height: min(800px, calc(100svh - 2rem)); min-width: 360px; overflow: hidden; position: fixed; @@ -947,10 +969,10 @@ body.dirty .netFilteringDialog > div.panes > .dynamic > .toolbar #saveRules { background-color: rgb(var(--primary-70) / 40%); } #loggerExportDialog .output { - font: smaller mono; - height: 60vh; + font-size: small; + height: 80vh; padding: 0.5em; - white-space: pre; + white-space: pre-wrap; } #loggerSettingsDialog { diff --git a/src/css/popup-fenix.css b/src/css/popup-fenix.css index 252e371..b845562 100644 --- a/src/css/popup-fenix.css +++ b/src/css/popup-fenix.css @@ -211,7 +211,7 @@ body.needSave #revertRules { font-size: 1.6em; } -#basicTools:not(.canPick) .needPick { +#basicTools .needPick:not(.canPick) { visibility: hidden; } @@ -300,6 +300,7 @@ body.needSave #revertRules { } :root.desktop body.vMin #firewall { max-height: 100vh; + max-height: 100svh; } #firewall > * { direction: ltr; diff --git a/src/css/whitelist.css b/src/css/whitelist.css index 715c964..3248a77 100644 --- a/src/css/whitelist.css +++ b/src/css/whitelist.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/dashboard.html b/src/dashboard.html index 859de58..b62813b 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -4,24 +4,28 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1"> <title data-i18n="dashboardName"></title> -<link href="css/themes/default.css" rel="stylesheet" type="text/css"> -<link href="css/common.css" rel="stylesheet" type="text/css"> -<link href="css/dashboard.css" rel="stylesheet" type="text/css"> +<link rel="stylesheet" href="css/themes/default.css"> +<link rel="stylesheet" href="css/fa-icons.css"> +<link rel="stylesheet" href="css/common.css"> +<link rel="stylesheet" href="css/dashboard.css"> <link rel="shortcut icon" type="image/png" href="img/icon_64.png"> </head> <body class="notReady"> <div id="dashboard-nav"> - <span class="logo"><img data-i18n-title="extName" src="img/ublock.svg"></span><!-- - --><button class="tabButton" type="button" data-pane="settings.html" data-i18n="settingsPageName" tabindex="0"></button><!-- - --><button class="tabButton" type="button" data-pane="3p-filters.html" data-i18n="3pPageName" tabindex="0"></button><!-- - --><button class="tabButton" type="button" data-pane="1p-filters.html" data-i18n="1pPageName" tabindex="0"></button><!-- - --><button class="tabButton" type="button" data-pane="dyna-rules.html" data-i18n="rulesPageName" tabindex="0"></button><!-- - --><button class="tabButton" type="button" data-pane="whitelist.html" data-i18n="whitelistPageName" tabindex="0"></button><!-- - --><button class="tabButton" type="button" data-pane="support.html" data-i18n="supportPageName" tabindex="0"></button><!-- - --><button class="tabButton" type="button" data-pane="about.html" data-i18n="aboutPageName" tabindex="0"></button><!-- - --><button class="tabButton" type="button" data-pane="no-dashboard.html"></button> + <span> + <span class="logo"><img data-i18n-title="extName" src="img/ublock.svg" alt="uBlock Origin"></span><!-- + --><button class="tabButton" type="button" data-pane="settings.html" data-i18n="settingsPageName" tabindex="0"></button><!-- + --><button class="tabButton" type="button" data-pane="3p-filters.html" data-i18n="3pPageName" tabindex="0"></button><!-- + --><button class="tabButton" type="button" data-pane="1p-filters.html" data-i18n="1pPageName" tabindex="0"></button><!-- + --><button class="tabButton" type="button" data-pane="dyna-rules.html" data-i18n="rulesPageName" tabindex="0"></button><!-- + --><button class="tabButton" type="button" data-pane="whitelist.html" data-i18n="whitelistPageName" tabindex="0"></button><!-- + --><button class="tabButton" type="button" data-pane="support.html" data-i18n="supportPageName" tabindex="0"></button><!-- + --><button class="tabButton" type="button" data-pane="about.html" data-i18n="aboutPageName" tabindex="0"></button><!-- + --><button class="tabButton" type="button" data-pane="no-dashboard.html"></button> + </span> + <a class="wikilink fa-icon" href="https://github.com/gorhill/uBlock/wiki/Dashboard:-My-filters">book</a> </div> <section id="unsavedWarning" class="notice"> <div> @@ -32,16 +36,19 @@ <div></div> </section> -<iframe id="iframe" src=""></iframe> +<iframe id="iframe" src="about:blank"></iframe> <script src="lib/hsluv/hsluv-0.1.0.min.js"></script> <script src="js/vapi.js"></script> <script src="js/vapi-common.js"></script> <script src="js/vapi-client.js"></script> + +<script src="js/fa-icons.js" type="module"></script> <script src="js/theme.js" type="module"></script> <script src="js/i18n.js" type="module"></script> <script src="js/dashboard.js" type="module"></script> +<script src="js/dashboard-common.js" type="module"></script> </body> </html> diff --git a/src/devtools.html b/src/devtools.html index af7b972..8f66827 100644 --- a/src/devtools.html +++ b/src/devtools.html @@ -29,6 +29,8 @@ <button id="snfe-todnr" type="button">SNFE: DNR<span class="hover"></span></button> <button id="snfe-benchmark" type="button" disabled>SNFE: Benchmark<span class="hover"></span></button> <button id="cfe-dump" type="button">CFE: Dump<span class="hover"></span></button> + <button id="cfe-benchmark" type="button" disabled>CFE: Benchmark<span class="hover"></span></button> + <button id="sfe-benchmark" type="button" disabled>SFE: Benchmark<span class="hover"></span></button> <button id="purge-all-caches" type="button" data-i18n-title="3pPurgeAll"><span data-i18n="3pPurgeAll">_</span><span class="hover"></span></button> </div> <div id="console" class="codeMirrorContainer"></div> diff --git a/src/dyna-rules.html b/src/dyna-rules.html index c9dd3bc..451f858 100644 --- a/src/dyna-rules.html +++ b/src/dyna-rules.html @@ -20,7 +20,7 @@ <div class="body"> <div id="cloudWidget" class="hide" data-cloud-entry="myRulesPane"></div> - <p class="vverbose"><span data-i18n="rulesHint"></span> <a class="fa-icon info" href="https://github.com/gorhill/uBlock/wiki/Dynamic-filtering:-rule-syntax" target="_blank">info-circle</a></p> + <p class="vverbose"><span data-i18n="rulesHint"></span></p> <div id="diff"> <div class="tools"> <div class="ruleActions"> @@ -40,7 +40,7 @@ <div id="ruleFilter"> <span><span class="fa-icon">filter</span> <input type="search" size="16"></span> <span data-i18n="rulesSort"></span> <select><option value="0" selected data-i18n="rulesSortByType"><option value="1" data-i18n="rulesSortBySource"><option value="2" data-i18n="rulesSortByDestination"></select> <span id="diffCollapse" class="fa-icon">double-angle-up</span> </div> -<div class="codeMirrorContainer codeMirrorMergeContainer"></div> +<div class="codeMirrorContainer codeMirrorMergeContainer cm-theme-override"></div> <div id="templates" style="display: none;"> <input class="hidden" id="importFilePicker" type="file" accept="text/plain"> diff --git a/src/img/flags-of-the-world/np.png b/src/img/flags-of-the-world/np.png Binary files differindex c47f80e..3ecd78d 100644 --- a/src/img/flags-of-the-world/np.png +++ b/src/img/flags-of-the-world/np.png diff --git a/src/img/fontawesome/fontawesome-defs.svg b/src/img/fontawesome/fontawesome-defs.svg index 75bc67f..0113e06 100644 --- a/src/img/fontawesome/fontawesome-defs.svg +++ b/src/img/fontawesome/fontawesome-defs.svg @@ -30,6 +30,7 @@ License - https://github.com/FortAwesome/Font-Awesome/tree/a8386aae19e200ddb0f68 <symbol id="arrow-right" viewBox="0 0 1472 1558"><path d="m 1472,779 q 0,54 -37,91 l -651,651 q -39,37 -91,37 -51,0 -90,-37 l -75,-75 q -38,-38 -38,-91 0,-53 38,-91 L 821,971 H 117 Q 65,971 32.5,933.5 0,896 0,843 V 715 Q 0,662 32.5,624.5 65,587 117,587 H 821 L 528,293 q -38,-36 -38,-90 0,-54 38,-90 l 75,-75 q 38,-38 90,-38 53,0 91,38 l 651,651 q 37,35 37,90 z"/></symbol> <symbol id="bar-chart" viewBox="0 0 2048 1536"><path d="m 640,768 0,512 -256,0 0,-512 256,0 z m 384,-512 0,1024 -256,0 0,-1024 256,0 z m 1024,1152 0,128 L 0,1536 0,0 l 128,0 0,1408 1920,0 z m -640,-896 0,768 -256,0 0,-768 256,0 z m 384,-384 0,1152 -256,0 0,-1152 256,0 z"/></symbol> <symbol id="bolt" viewBox="0 0 896 1664"><path d="m 885.08696,438 q 18,20 7,44 l -540,1157 q -13,25 -42,25 -4,0 -14,-2 -17,-5 -25.5,-19 -8.5,-14 -4.5,-30 l 197,-808 -406,101 q -4,1 -12,1 -18,0 -31,-11 Q -3.9130435,881 1.0869565,857 L 202.08696,32 q 4,-14 16,-23 12,-9 28,-9 l 328,0 q 19,0 32,12.5 13,12.5 13,29.5 0,8 -5,18 l -171,463 396,-98 q 8,-2 12,-2 19,0 34,15 z"/></symbol> + <symbol id="book" viewBox="0 0 1664 1536"><path d="m 1639.2625,350 c 25,36 32,83 18,129 l -275,906 c -25,85 -113,151 -199,151 H 260.26251 c -102,0 -211,-81 -248,-185 -16,-45 -16,-89 -2,-127 2,-20 6,-40 7,-64 1,-16 -8,-29 -6,-41 4,-24 25,-41 41,-68 30,-50 64,-131 75,-183 5,-19 -5,-41 0,-58 5,-19 24,-33 34,-51 27,-46 62,-135 67,-182 2,-21 -8,-44 -2,-60 7,-23 29,-33 44,-53 24,-33 64,-128 70,-181 2,-17 -8,-34 -5,-52 4,-19 28,-39 44,-62 42,-62 50,-199 177,-163 l -1,3 c 17,-4 34,-9 51,-9 h 761 c 47,0 89,21 114,56 26,36 32,83 18,130 l -274,906 c -47,154 -73,188 -200,188 H 156.26251 c -13,0 -29,3 -38,15 -8,12 -9,21 -1,43 20,58 89,70 144,70 h 923 c 37,0 80,-21 91,-57 l 300,-987 c 6,-19 6,-39 5,-57 23,9 44,23 59,43 z m -1064,2 c -6,18 4,32 22,32 h 608 c 17,0 36,-14 42,-32 l 21,-64 c 6,-18 -4,-32 -22,-32 H 638.26251 c -17,0 -36,14 -42,32 z m -83,256 c -6,18 4,32 22,32 h 608 c 17,0 36,-14 42,-32 l 21,-64 c 6,-18 -4,-32 -22,-32 H 555.26251 c -17,0 -36,14 -42,32 z"/></symbol> <symbol id="clipboard" viewBox="0 0 1792 1792"><path d="m 768,1664 896,0 0,-640 -416,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -384,0 0,1152 z m 256,-1440 0,-64 q 0,-13 -9.5,-22.5 Q 1005,128 992,128 l -704,0 q -13,0 -22.5,9.5 Q 256,147 256,160 l 0,64 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 704,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 256,672 299,0 -299,-299 0,299 z m 512,128 0,672 q 0,40 -28,68 -28,28 -68,28 l -960,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-160 -544,0 Q 56,1536 28,1508 0,1480 0,1440 L 0,96 Q 0,56 28,28 56,0 96,0 l 1088,0 q 40,0 68,28 28,28 28,68 l 0,328 q 21,13 36,28 l 408,408 q 28,28 48,76 20,48 20,88 z"/></symbol> <symbol id="clock-o" viewBox="0 0 1536 1536"><path d="m 896,416 v 448 q 0,14 -9,23 -9,9 -23,9 H 544 q -14,0 -23,-9 -9,-9 -9,-23 v -64 q 0,-14 9,-23 9,-9 23,-9 H 768 V 416 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 416,352 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z"/></symbol> <symbol id="cloud-download" viewBox="0 0 1920 1408"><path d="m 1280,800 q 0,-14 -9,-23 -9,-9 -23,-9 l -224,0 0,-352 q 0,-13 -9.5,-22.5 Q 1005,384 992,384 l -192,0 q -13,0 -22.5,9.5 Q 768,403 768,416 l 0,352 -224,0 q -13,0 -22.5,9.5 -9.5,9.5 -9.5,22.5 0,14 9,23 l 352,352 q 9,9 23,9 14,0 23,-9 l 351,-351 q 10,-12 10,-24 z m 640,224 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z"/></symbol> @@ -73,6 +74,7 @@ License - https://github.com/FortAwesome/Font-Awesome/tree/a8386aae19e200ddb0f68 <symbol id="unlink" viewBox="0 0 1664 1664"><path d="m 439,1271 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z m 169,41 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 v -320 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z M 384,1088 q 0,14 -9,23 -9,9 -23,9 H 32 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z m 1264,128 q 0,120 -85,203 l -147,146 q -83,83 -203,83 -121,0 -204,-85 L 675,1228 q -21,-21 -42,-56 l 239,-18 273,274 q 27,27 68,27.5 41,0.5 68,-26.5 l 147,-146 q 28,-28 28,-67 0,-40 -28,-68 l -274,-275 18,-239 q 35,21 56,42 l 336,336 q 84,86 84,204 z M 1031,492 792,510 519,236 q -28,-28 -68,-28 -39,0 -68,27 L 236,381 q -28,28 -28,67 0,40 28,68 l 274,274 -18,240 q -35,-21 -56,-42 L 100,652 Q 16,566 16,448 16,328 101,245 L 248,99 q 83,-83 203,-83 121,0 204,85 l 334,335 q 21,21 42,56 z m 633,84 q 0,14 -9,23 -9,9 -23,9 h -320 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z M 1120,32 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 V 32 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z m 407,151 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z"/></symbol> <symbol id="unlock-alt" viewBox="0 0 1152 1536"><path d="m 1056,768 q 40,0 68,28 28,28 28,68 v 576 q 0,40 -28,68 -28,28 -68,28 H 96 Q 56,1536 28,1508 0,1480 0,1440 V 864 q 0,-40 28,-68 28,-28 68,-28 h 32 V 448 Q 128,263 259.5,131.5 391,0 576,0 761,0 892.5,131.5 1024,263 1024,448 q 0,26 -19,45 -19,19 -45,19 h -64 q -26,0 -45,-19 -19,-19 -19,-45 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 v 320 z"/></symbol> <symbol id="upload_alt" viewBox="0 0 1664 1600"><path d="m 1280,1408 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 256,0 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 128,-224 v 320 q 0,40 -28,68 -28,28 -68,28 H 96 q -40,0 -68,-28 -28,-28 -28,-68 v -320 q 0,-40 28,-68 28,-28 68,-28 h 427 q 21,56 70.5,92 49.5,36 110.5,36 h 256 q 61,0 110.5,-36 49.5,-36 70.5,-92 h 427 q 40,0 68,28 28,28 28,68 z M 1339,536 q -17,40 -59,40 h -256 v 448 q 0,26 -19,45 -19,19 -45,19 H 704 q -26,0 -45,-19 -19,-19 -19,-45 V 576 H 384 q -42,0 -59,-40 -17,-39 14,-69 L 787,19 q 18,-19 45,-19 27,0 45,19 l 448,448 q 31,30 14,69 z"/></symbol> + <symbol id="volume-up" viewBox="0 0 1664 1422"><path d="m 768,167 v 1088 c 0,35 -29,64 -64,64 -17,0 -33,-7 -45,-19 L 326,967 H 64 C 29,967 0,938 0,903 V 519 C 0,484 29,455 64,455 H 326 L 659,122 c 12,-12 28,-19 45,-19 35,0 64,29 64,64 z m 384,544 c 0,100 -61,197 -155,235 -8,4 -17,5 -25,5 -35,0 -64,-28 -64,-64 0,-76 116,-55 116,-176 0,-121 -116,-100 -116,-176 0,-36 29,-64 64,-64 8,0 17,1 25,5 94,37 155,135 155,235 z m 256,0 c 0,203 -122,392 -310,471 -8,3 -17,5 -25,5 -36,0 -65,-29 -65,-64 0,-28 16,-47 39,-59 27,-14 52,-26 76,-44 99,-72 157,-187 157,-309 0,-122 -58,-237 -157,-309 -24,-18 -49,-30 -76,-44 -23,-12 -39,-31 -39,-59 0,-35 29,-64 64,-64 9,0 18,2 26,5 188,79 310,268 310,471 z m 256,0 c 0,307 -183,585 -465,706 -8,3 -17,5 -26,5 -35,0 -64,-29 -64,-64 0,-29 15,-45 39,-59 14,-8 30,-13 45,-21 28,-15 56,-32 82,-51 164,-121 261,-312 261,-516 0,-204 -97,-395 -261,-516 -26,-19 -54,-36 -82,-51 -15,-8 -31,-13 -45,-21 -24,-14 -39,-30 -39,-59 0,-35 29,-64 64,-64 9,0 18,2 26,5 282,121 465,399 465,706 z"/></symbol> <symbol id="zoom-in" viewBox="0 0 1664 1664"><path d="m 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 768 v 224 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 h -64 q -13,0 -22.5,-9.5 Q 640,1005 640,992 V 768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 H 640 V 416 q 0,-13 9.5,-22.5 Q 659,384 672,384 h 64 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 v 224 h 224 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z"/></symbol> <symbol id="zoom-out" viewBox="0 0 1664 1664"><path d="m 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 h 576 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z"/></symbol> </defs> diff --git a/src/js/1p-filters.js b/src/js/1p-filters.js index fc50b50..70ce256 100644 --- a/src/js/1p-filters.js +++ b/src/js/1p-filters.js @@ -21,12 +21,10 @@ /* global CodeMirror, uBlockDashboard */ -'use strict'; - -import { onBroadcast } from './broadcast.js'; +import './codemirror/ubo-static-filtering.js'; import { dom, qs$ } from './dom.js'; import { i18n$ } from './i18n.js'; -import './codemirror/ubo-static-filtering.js'; +import { onBroadcast } from './broadcast.js'; /******************************************************************************/ @@ -53,8 +51,6 @@ const cmEditor = new CodeMirror(qs$('#userFilters'), { uBlockDashboard.patchCodeMirrorEditor(cmEditor); -let cachedUserFilters = ''; - /******************************************************************************/ // Add auto-complete ability to the editor. Polling is used as the suggested @@ -91,9 +87,32 @@ vAPI.messaging.send('dashboard', { /******************************************************************************/ +let originalState = { + enabled: true, + trusted: false, + filters: '', +}; + +function getCurrentState() { + const enabled = qs$('#enableMyFilters input').checked; + return { + enabled, + trusted: enabled && qs$('#trustMyFilters input').checked, + filters: getEditorText(), + }; +} + +function rememberCurrentState() { + originalState = getCurrentState(); +} + +function currentStateChanged() { + return JSON.stringify(getCurrentState()) !== JSON.stringify(originalState); +} + function getEditorText() { const text = cmEditor.getValue().replace(/\s+$/, ''); - return text === '' ? text : text + '\n'; + return text === '' ? text : `${text}\n`; } function setEditorText(text) { @@ -102,12 +121,30 @@ function setEditorText(text) { /******************************************************************************/ -function userFiltersChanged(changed) { - if ( typeof changed !== 'boolean' ) { - changed = self.hasUnsavedData(); - } +function userFiltersChanged(details = {}) { + const changed = typeof details.changed === 'boolean' + ? details.changed + : self.hasUnsavedData(); qs$('#userFiltersApply').disabled = !changed; qs$('#userFiltersRevert').disabled = !changed; + const enabled = qs$('#enableMyFilters input').checked; + dom.attr('#trustMyFilters .input.checkbox', 'disabled', enabled ? null : ''); + const trustedbefore = cmEditor.getOption('trustedSource'); + const trustedAfter = enabled && qs$('#trustMyFilters input').checked; + if ( trustedAfter === trustedbefore ) { return; } + cmEditor.startOperation(); + cmEditor.setOption('trustedSource', trustedAfter); + const doc = cmEditor.getDoc(); + const history = doc.getHistory(); + const selections = doc.listSelections(); + doc.replaceRange(doc.getValue(), + { line: 0, ch: 0 }, + { line: doc.lineCount(), ch: 0 } + ); + doc.setSelections(selections); + doc.setHistory(history); + cmEditor.endOperation(); + cmEditor.focus(); } /******************************************************************************/ @@ -118,7 +155,7 @@ function userFiltersChanged(changed) { // background. function threeWayMerge(newContent) { - const prvContent = cachedUserFilters.trim().split(/\n/); + const prvContent = originalState.filters.trim().split(/\n/); const differ = new self.diff_match_patch(); const newChanges = differ.diff( prvContent, @@ -167,19 +204,22 @@ async function renderUserFilters(merge = false) { }); if ( details instanceof Object === false || details.error ) { return; } - cmEditor.setOption('trustedSource', details.trustedSource === true); + cmEditor.setOption('trustedSource', details.trusted); + + qs$('#enableMyFilters input').checked = details.enabled; + qs$('#trustMyFilters input').checked = details.trusted; const newContent = details.content.trim(); if ( merge && self.hasUnsavedData() ) { setEditorText(threeWayMerge(newContent)); - userFiltersChanged(true); + userFiltersChanged({ changed: true }); } else { setEditorText(newContent); - userFiltersChanged(false); + userFiltersChanged({ changed: false }); } - cachedUserFilters = newContent; + rememberCurrentState(); } /******************************************************************************/ @@ -224,7 +264,7 @@ function exportUserFiltersToFile() { .replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString()) .replace(/ +/g, '_'); vAPI.download({ - 'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(val + '\n'), + 'url': `data:text/plain;charset=utf-8,${encodeURIComponent(val)}`, 'filename': filename }); } @@ -232,21 +272,26 @@ function exportUserFiltersToFile() { /******************************************************************************/ async function applyChanges() { + const state = getCurrentState(); const details = await vAPI.messaging.send('dashboard', { what: 'writeUserFilters', - content: getEditorText(), + content: state.filters, + enabled: state.enabled, + trusted: state.trusted, }); if ( details instanceof Object === false || details.error ) { return; } - - cachedUserFilters = details.content.trim(); - userFiltersChanged(false); + rememberCurrentState(); + userFiltersChanged({ changed: false }); vAPI.messaging.send('dashboard', { what: 'reloadAllFilters', }); } function revertChanges() { - setEditorText(cachedUserFilters); + qs$('#enableMyFilters input').checked = originalState.enabled; + qs$('#trustMyFilters input').checked = originalState.trusted; + setEditorText(originalState.filters); + userFiltersChanged(); } /******************************************************************************/ @@ -268,8 +313,10 @@ self.cloud.onPull = setCloudData; /******************************************************************************/ +self.wikilink = 'https://github.com/gorhill/uBlock/wiki/Dashboard:-My-filters'; + self.hasUnsavedData = function() { - return getEditorText().trim() !== cachedUserFilters; + return currentStateChanged(); }; /******************************************************************************/ @@ -278,6 +325,8 @@ self.hasUnsavedData = function() { dom.on('#exportUserFiltersToFile', 'click', exportUserFiltersToFile); dom.on('#userFiltersApply', 'click', ( ) => { applyChanges(); }); dom.on('#userFiltersRevert', 'click', revertChanges); +dom.on('#enableMyFilters input', 'change', userFiltersChanged); +dom.on('#trustMyFilters input', 'change', userFiltersChanged); (async ( ) => { await renderUserFilters(); diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index c59365f..2d1a5df 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -19,11 +19,9 @@ Home: https://github.com/gorhill/uBlock */ -'use strict'; - -import { onBroadcast } from './broadcast.js'; import { dom, qs$, qsa$ } from './dom.js'; import { i18n, i18n$ } from './i18n.js'; +import { onBroadcast } from './broadcast.js'; /******************************************************************************/ @@ -32,6 +30,10 @@ const obsoleteTemplateString = i18n$('3pExternalListObsolete'); const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m; const recentlyUpdated = 1 * 60 * 60 * 1000; // 1 hour +// https://eslint.org/docs/latest/rules/no-prototype-builtins +const hasOwnProperty = (o, p) => + Object.prototype.hasOwnProperty.call(o, p); + let listsetDetails = {}; /******************************************************************************/ @@ -74,7 +76,9 @@ const renderNodeStats = (used, total) => { }; const i18nGroupName = name => { - return i18n$('3pGroup' + name.charAt(0).toUpperCase() + name.slice(1)); + const groupname = i18n$('3pGroup' + name.charAt(0).toUpperCase() + name.slice(1)); + if ( groupname !== '' ) { return groupname; } + return `${name.charAt(0).toLocaleUpperCase}${name.slice(1)}`; }; /******************************************************************************/ @@ -90,8 +94,9 @@ const renderFilterLists = ( ) => { const initializeListEntry = (listDetails, listEntry) => { const listkey = listEntry.dataset.key; + const groupkey = listDetails.group2 || listDetails.group; const listEntryPrevious = - qs$(`[data-key="${listDetails.group}"] [data-key="${listkey}"]`); + qs$(`[data-key="${groupkey}"] [data-key="${listkey}"]`); if ( listEntryPrevious !== null ) { if ( dom.cl.has(listEntryPrevious, 'checked') ) { dom.cl.add(listEntry, 'checked'); @@ -179,6 +184,9 @@ const renderFilterLists = ( ) => { if ( depth !== 0 ) { const reEmojis = /\p{Emoji}+/gu; treeEntries.sort((a ,b) => { + const ap = a[1].preferred === true; + const bp = b[1].preferred === true; + if ( ap !== bp ) { return ap ? -1 : 1; } const as = (a[1].title || a[0]).replace(reEmojis, ''); const bs = (b[1].title || b[0]).replace(reEmojis, ''); return as.localeCompare(bs); @@ -223,8 +231,11 @@ const renderFilterLists = ( ) => { 'privacy', 'malware', 'multipurpose', + 'cookies', + 'social', 'annoyances', 'regions', + 'unknown', 'custom' ]; for ( const key of groupKeys ) { @@ -234,17 +245,20 @@ const renderFilterLists = ( ) => { }; } for ( const [ listkey, listDetails ] of Object.entries(response.available) ) { - let groupKey = listDetails.group; - if ( groupKey === 'social' ) { - groupKey = 'annoyances'; + let groupkey = listDetails.group2 || listDetails.group; + if ( hasOwnProperty(listTree, groupkey) === false ) { + groupkey = 'unknown'; } - const groupDetails = listTree[groupKey]; + const groupDetails = listTree[groupkey]; if ( listDetails.parent !== undefined ) { let lists = groupDetails.lists; for ( const parent of listDetails.parent.split('|') ) { if ( lists[parent] === undefined ) { lists[parent] = { title: parent, lists: {} }; } + if ( listDetails.preferred === true ) { + lists[parent].preferred = true; + } lists = lists[parent].lists; } lists[listkey] = listDetails; @@ -253,6 +267,15 @@ const renderFilterLists = ( ) => { groupDetails.lists[listkey] = listDetails; } } + // https://github.com/uBlockOrigin/uBlock-issues/issues/3154#issuecomment-1975413427 + // Remove empty sections + for ( const groupkey of groupKeys ) { + const groupDetails = listTree[groupkey]; + if ( groupDetails === undefined ) { continue; } + if ( Object.keys(groupDetails.lists).length !== 0 ) { continue; } + delete listTree[groupkey]; + } + const listEntries = createListEntries('root', listTree); qs$('#lists .listEntries').replaceWith(listEntries); @@ -286,14 +309,14 @@ const renderFilterLists = ( ) => { /******************************************************************************/ const renderWidgets = ( ) => { + const updating = dom.cl.has(dom.body, 'updating'); + const hasObsolete = qs$('#lists .listEntry.checked.obsolete:not(.toRemove)') !== null; dom.cl.toggle('#buttonApply', 'disabled', filteringSettingsHash === hashFromCurrentFromSettings() ); - const updating = dom.cl.has(dom.body, 'updating'); dom.cl.toggle('#buttonUpdate', 'active', updating); dom.cl.toggle('#buttonUpdate', 'disabled', - updating === false && - qs$('#lists .listEntry.checked.obsolete:not(.toRemove)') === null + updating === false && hasObsolete === false ); }; @@ -530,6 +553,35 @@ dom.on('#lists', 'click', 'span.cache', onPurgeClicked); /******************************************************************************/ const selectFilterLists = async ( ) => { + // External filter lists to import + // Find stock list matching entries in lists to import + const toImport = (( ) => { + const textarea = qs$('#lists .listEntry[data-role="import"].expanded textarea'); + if ( textarea === null ) { return ''; } + const lists = listsetDetails.available; + const lines = textarea.value.split(/\s+\n|\s+/); + const after = []; + for ( const line of lines ) { + if ( /^https?:\/\//.test(line) === false ) { continue; } + for ( const [ listkey, list ] of Object.entries(lists) ) { + if ( list.content !== 'filters' ) { continue; } + if ( list.contentURL === undefined ) { continue; } + if ( list.contentURL.includes(line) === false ) { + after.push(line); + continue; + } + const groupkey = list.group2 || list.group; + const listEntry = qs$(`[data-key="${groupkey}"] [data-key="${listkey}"]`); + if ( listEntry === null ) { break; } + toggleFilterList(listEntry, true); + break; + } + } + dom.cl.remove(textarea.closest('.expandable'), 'expanded'); + textarea.value = ''; + return after.join('\n'); + })(); + // Cosmetic filtering switch let checked = qs$('#parseCosmeticFilters').checked; vAPI.messaging.send('dashboard', { @@ -552,7 +604,7 @@ const selectFilterLists = async ( ) => { const toRemove = []; for ( const liEntry of qsa$('#lists .listEntry[data-role="leaf"]') ) { const listkey = liEntry.dataset.key; - if ( listsetDetails.available.hasOwnProperty(listkey) === false ) { + if ( hasOwnProperty(listsetDetails.available, listkey) === false ) { continue; } const listDetails = listsetDetails.available[listkey]; @@ -569,14 +621,6 @@ const selectFilterLists = async ( ) => { } } - // External filter lists to import - const textarea = qs$('#lists .listEntry[data-role="import"].expanded textarea'); - const toImport = textarea !== null && textarea.value.trim() || ''; - if ( textarea !== null ) { - dom.cl.remove(textarea.closest('.expandable'), 'expanded'); - textarea.value = ''; - } - hashFromListsetDetails(); await vAPI.messaging.send('dashboard', { @@ -630,7 +674,7 @@ dom.on('#suspendUntilListsAreLoaded', 'change', userSettingCheckboxChanged); /******************************************************************************/ const searchFilterLists = ( ) => { - const pattern = dom.prop('.searchbar input', 'value') || ''; + const pattern = dom.prop('.searchfield input', 'value') || ''; dom.cl.toggle('#lists', 'searchMode', pattern !== ''); if ( pattern === '' ) { return; } const reflectSearchMatches = listEntry => { @@ -657,10 +701,11 @@ const searchFilterLists = ( ) => { if ( listDetails === undefined ) { continue; } let haystack = perListHaystack.get(listDetails); if ( haystack === undefined ) { + const groupkey = listDetails.group2 || listDetails.group || ''; haystack = [ listDetails.title, - listDetails.group || '', - i18nGroupName(listDetails.group || ''), + groupkey, + i18nGroupName(groupkey), listDetails.tags || '', toI18n(listDetails.tags || ''), ].join(' ').trim(); @@ -673,14 +718,13 @@ const searchFilterLists = ( ) => { const perListHaystack = new WeakMap(); -dom.on('.searchbar input', 'input', searchFilterLists); +dom.on('.searchfield input', 'input', searchFilterLists); /******************************************************************************/ const expandedListSet = new Set([ - 'uBlock filters', - 'AdGuard – Annoyances', - 'EasyList – Annoyances', + 'cookies', + 'social', ]); const listIsExpanded = which => { @@ -844,6 +888,8 @@ self.cloud.onPull = function fromCloudData(data, append) { /******************************************************************************/ +self.wikilink = 'https://github.com/gorhill/uBlock/wiki/Dashboard:-Filter-lists'; + self.hasUnsavedData = function() { return hashFromCurrentFromSettings() !== filteringSettingsHash; }; diff --git a/src/js/asset-viewer.js b/src/js/asset-viewer.js index eabe136..351531b 100644 --- a/src/js/asset-viewer.js +++ b/src/js/asset-viewer.js @@ -60,6 +60,7 @@ import './codemirror/ubo-static-filtering.js'; lineWrapping: true, matchBrackets: true, maxScanLines: 1, + maximizable: false, readOnly: true, styleActiveLine: { nonEmpty: true, diff --git a/src/js/assets.js b/src/js/assets.js index 69c2ef3..e1bc4e6 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -53,10 +53,13 @@ let remoteServerFriendly = false; const stringIsNotEmpty = s => typeof s === 'string' && s !== ''; const parseExpires = s => { - const matches = s.match(/(\d+)\s*([dhm]?)/i); + const matches = s.match(/(\d+)\s*([wdhm]?)/i); if ( matches === null ) { return; } let updateAfter = parseInt(matches[1], 10); - if ( matches[2] === 'h' ) { + if ( updateAfter === 0 ) { return; } + if ( matches[2] === 'w' ) { + updateAfter *= 7 * 24; + } else if ( matches[2] === 'h' ) { updateAfter = Math.max(updateAfter, 4) / 24; } else if ( matches[2] === 'm' ) { updateAfter = Math.max(updateAfter, 240) / 1440; @@ -428,7 +431,7 @@ assets.fetchFilterList = async function(mainlistURL) { continue; } if ( result instanceof Object === false ) { continue; } - const content = result.content; + const content = result.content.trimEnd() + '\n'; const slices = sfp.utils.preparser.splitter( content, vAPI.webextFlavor.env @@ -500,7 +503,7 @@ assets.fetchFilterList = async function(mainlistURL) { resourceTime, content: allParts.length === 1 ? allParts[0] - : allParts.join('') + '\n' + : allParts.join('') }; }; @@ -525,12 +528,12 @@ function getAssetSourceRegistry() { assetSourceRegistryPromise = cacheStorage.get( 'assetSourceRegistry' ).then(bin => { - if ( - bin instanceof Object && - bin.assetSourceRegistry instanceof Object - ) { - assetSourceRegistry = bin.assetSourceRegistry; - return assetSourceRegistry; + if ( bin instanceof Object ) { + if ( bin.assetSourceRegistry instanceof Object ) { + assetSourceRegistry = bin.assetSourceRegistry; + ubolog('Loaded assetSourceRegistry'); + return assetSourceRegistry; + } } return assets.fetchText( µb.assetsBootstrapLocation || µb.assetsJsonPath @@ -540,6 +543,7 @@ function getAssetSourceRegistry() { : assets.fetchText(µb.assetsJsonPath); }).then(details => { updateAssetSourceRegistry(details.content, true); + ubolog('Loaded assetSourceRegistry'); return assetSourceRegistry; }); }); @@ -670,49 +674,36 @@ let assetCacheRegistryPromise; let assetCacheRegistry = {}; function getAssetCacheRegistry() { - if ( assetCacheRegistryPromise === undefined ) { - assetCacheRegistryPromise = cacheStorage.get( - 'assetCacheRegistry' - ).then(bin => { - if ( - bin instanceof Object && - bin.assetCacheRegistry instanceof Object - ) { - if ( Object.keys(assetCacheRegistry).length === 0 ) { - assetCacheRegistry = bin.assetCacheRegistry; - } else { - console.error( - 'getAssetCacheRegistry(): assetCacheRegistry reassigned!' - ); - if ( - Object.keys(bin.assetCacheRegistry).sort().join() !== - Object.keys(assetCacheRegistry).sort().join() - ) { - console.error( - 'getAssetCacheRegistry(): assetCacheRegistry changes overwritten!' - ); - } - } - } - return assetCacheRegistry; - }); + if ( assetCacheRegistryPromise !== undefined ) { + return assetCacheRegistryPromise; } - + assetCacheRegistryPromise = cacheStorage.get( + 'assetCacheRegistry' + ).then(bin => { + if ( bin instanceof Object === false ) { return; } + if ( bin.assetCacheRegistry instanceof Object === false ) { return; } + if ( Object.keys(assetCacheRegistry).length !== 0 ) { + return console.error('getAssetCacheRegistry(): assetCacheRegistry reassigned!'); + } + ubolog('Loaded assetCacheRegistry'); + assetCacheRegistry = bin.assetCacheRegistry; + }).then(( ) => + assetCacheRegistry + ); return assetCacheRegistryPromise; } const saveAssetCacheRegistry = (( ) => { - const save = function() { + const save = ( ) => { timer.off(); - cacheStorage.set({ assetCacheRegistry }); + return cacheStorage.set({ assetCacheRegistry }); }; const timer = vAPI.defer.create(save); - return function(lazily) { - if ( lazily ) { - timer.offon({ sec: 30 }); - } else { - save(); + return (throttle = 0) => { + if ( throttle === 0 ) { + return save(); } + timer.offon({ sec: throttle }); }; })(); @@ -723,7 +714,9 @@ async function assetCacheRead(assetKey, updateReadTime = false) { const reportBack = function(content) { if ( content instanceof Blob ) { content = ''; } const details = { assetKey, content }; - if ( content === '' ) { details.error = 'ENOTFOUND'; } + if ( content === '' || content === undefined ) { + details.error = 'ENOTFOUND'; + } return details; }; @@ -739,55 +732,39 @@ async function assetCacheRead(assetKey, updateReadTime = false) { ) + ' ms'; } - if ( - bin instanceof Object === false || - bin.hasOwnProperty(internalKey) === false - ) { - return reportBack(''); - } + if ( bin instanceof Object === false ) { return reportBack(''); } + if ( bin.hasOwnProperty(internalKey) === false ) { return reportBack(''); } const entry = assetCacheRegistry[assetKey]; - if ( entry === undefined ) { - return reportBack(''); - } + if ( entry === undefined ) { return reportBack(''); } entry.readTime = Date.now(); if ( updateReadTime ) { - saveAssetCacheRegistry(true); + saveAssetCacheRegistry(23); } return reportBack(bin[internalKey]); } -async function assetCacheWrite(assetKey, details) { - let content = ''; - let options = {}; - if ( typeof details === 'string' ) { - content = details; - } else if ( details instanceof Object ) { - content = details.content || ''; - options = details; - } - - if ( content === '' ) { +async function assetCacheWrite(assetKey, content, options = {}) { + if ( content === '' || content === undefined ) { return assetCacheRemove(assetKey); } const cacheDict = await getAssetCacheRegistry(); - let entry = cacheDict[assetKey]; - if ( entry === undefined ) { - entry = cacheDict[assetKey] = {}; - } + const { resourceTime, url } = options; + const entry = cacheDict[assetKey] || {}; entry.writeTime = entry.readTime = Date.now(); - entry.resourceTime = options.resourceTime || 0; - if ( typeof options.url === 'string' ) { - entry.remoteURL = options.url; + entry.resourceTime = resourceTime || 0; + if ( typeof url === 'string' ) { + entry.remoteURL = url; } - cacheStorage.set({ - assetCacheRegistry, - [`cache/${assetKey}`]: content - }); + cacheDict[assetKey] = entry; + + await cacheStorage.set({ [`cache/${assetKey}`]: content }); + + saveAssetCacheRegistry(3); const result = { assetKey, content }; // https://github.com/uBlockOrigin/uBlock-issues/issues/248 @@ -797,21 +774,31 @@ async function assetCacheWrite(assetKey, details) { return result; } -async function assetCacheRemove(pattern) { +async function assetCacheRemove(pattern, options = {}) { const cacheDict = await getAssetCacheRegistry(); const removedEntries = []; const removedContent = []; for ( const assetKey in cacheDict ) { - if ( pattern instanceof RegExp && !pattern.test(assetKey) ) { - continue; - } - if ( typeof pattern === 'string' && assetKey !== pattern ) { - continue; + if ( pattern instanceof RegExp ) { + if ( pattern.test(assetKey) === false ) { continue; } + } else if ( typeof pattern === 'string' ) { + if ( assetKey !== pattern ) { continue; } } removedEntries.push(assetKey); - removedContent.push('cache/' + assetKey); + removedContent.push(`cache/${assetKey}`); delete cacheDict[assetKey]; } + if ( options.janitor && pattern instanceof RegExp ) { + const re = new RegExp( + pattern.source.replace(/^\^/, '^cache\\/'), + pattern.flags + ); + const keys = await cacheStorage.keys(re); + for ( const key of keys ) { + removedContent.push(key); + ubolog(`Removing stray ${key}`); + } + } if ( removedContent.length !== 0 ) { await Promise.all([ cacheStorage.remove(removedContent), @@ -851,7 +838,7 @@ async function assetCacheSetDetails(assetKey, details) { } } if ( modified ) { - saveAssetCacheRegistry(); + saveAssetCacheRegistry(3); } } @@ -977,8 +964,7 @@ assets.get = async function(assetKey, options = {}) { } if ( details.content === '' ) { continue; } if ( reIsExternalPath.test(contentURL) && options.dontCache !== true ) { - assetCacheWrite(assetKey, { - content: details.content, + assetCacheWrite(assetKey, details.content, { url: contentURL, silent: options.silent === true, }); @@ -1054,8 +1040,7 @@ async function getRemote(assetKey, options = {}) { } // Success - assetCacheWrite(assetKey, { - content: result.content, + assetCacheWrite(assetKey, result.content, { url: contentURL, resourceTime: result.resourceTime || 0, }); @@ -1098,6 +1083,17 @@ assets.put = async function(assetKey, content) { /******************************************************************************/ +assets.toCache = async function(assetKey, content) { + return assetCacheWrite(assetKey, content); +}; + +assets.fromCache = async function(assetKey) { + const details = await assetCacheRead(assetKey); + return details && details.content; +}; + +/******************************************************************************/ + assets.metadata = async function() { await Promise.all([ getAssetSourceRegistry(), @@ -1144,8 +1140,8 @@ assets.metadata = async function() { assets.purge = assetCacheMarkAsDirty; -assets.remove = function(pattern) { - return assetCacheRemove(pattern); +assets.remove = function(...args) { + return assetCacheRemove(...args); }; assets.rmrf = function() { @@ -1297,8 +1293,7 @@ async function diffUpdater() { 'Diff-Path', 'Diff-Expires', ]); - assetCacheWrite(data.assetKey, { - content: data.text, + assetCacheWrite(data.assetKey, data.text, { resourceTime: metadata.lastModified || 0, }); metadata.diffUpdated = true; @@ -1330,6 +1325,8 @@ async function diffUpdater() { terminate(); }; const worker = new Worker('js/diff-updater.js'); + }).catch(reason => { + ubolog(`Diff updater: ${reason}`); }); } diff --git a/src/js/background.js b/src/js/background.js index 578d8a6..edeac08 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -19,22 +19,18 @@ Home: https://github.com/gorhill/uBlock */ -/* globals browser */ - -'use strict'; - /******************************************************************************/ -import logger from './logger.js'; -import { FilteringContext } from './filtering-context.js'; -import { ubologSet } from './console.js'; - import { domainFromHostname, hostnameFromURI, originFromURI, } from './uri-utils.js'; +import { FilteringContext } from './filtering-context.js'; +import logger from './logger.js'; +import { ubologSet } from './console.js'; + /******************************************************************************/ // Not all platforms may have properly declared vAPI.webextFlavor. @@ -49,13 +45,14 @@ const hiddenSettingsDefault = { allowGenericProceduralFilters: false, assetFetchTimeout: 30, autoCommentFilterTemplate: '{{date}} {{origin}}', - autoUpdateAssetFetchPeriod: 15, - autoUpdateDelayAfterLaunch: 105, + autoUpdateAssetFetchPeriod: 5, + autoUpdateDelayAfterLaunch: 37, autoUpdatePeriod: 1, benchmarkDatasetURL: 'unset', blockingProfiles: '11111/#F00 11010/#C0F 11001/#00F 00001', - cacheStorageAPI: 'unset', cacheStorageCompression: true, + cacheStorageCompressionThreshold: 65536, + cacheStorageMultithread: 2, cacheControlForFirefox1376932: 'no-cache, no-store, must-revalidate', cloudStorageCompression: true, cnameIgnoreList: 'unset', @@ -78,10 +75,12 @@ const hiddenSettingsDefault = { modifyWebextFlavor: 'unset', popupFontSize: 'unset', popupPanelDisabledSections: 0, - popupPanelLockedSections: 0, popupPanelHeightMode: 0, + popupPanelLockedSections: 0, + popupPanelOrientation: 'unset', requestJournalProcessPeriod: 1000, - selfieAfter: 2, + requestStatsDisabled: false, + selfieDelayInSeconds: 53, strictBlockingBypassDuration: 120, toolbarWarningTimeout: 60, trustedListPrefixes: 'ublock-', @@ -93,7 +92,7 @@ const hiddenSettingsDefault = { if ( vAPI.webextFlavor.soup.has('devbuild') ) { hiddenSettingsDefault.consoleLogLevel = 'info'; - hiddenSettingsDefault.trustedListPrefixes += ' user-'; + hiddenSettingsDefault.cacheStorageAPI = 'unset'; ubologSet(true); } @@ -112,7 +111,7 @@ const userSettingsDefault = { externalLists: '', firewallPaneMinimized: true, hyperlinkAuditingDisabled: true, - ignoreGenericCosmeticFilters: vAPI.webextFlavor.soup.has('mobile'), + ignoreGenericCosmeticFilters: false, importedLists: [], largeMediaSize: 50, parseAllABPHideFilters: true, @@ -122,6 +121,7 @@ const userSettingsDefault = { showIconBadge: true, suspendUntilListsAreLoaded: vAPI.Net.canSuspend(), tooltipsDisabled: false, + userFiltersTrusted: false, webrtcIPAddressHidden: false, }; @@ -144,7 +144,7 @@ if ( vAPI.webextFlavor.soup.has('firefox') ) { } const µBlock = { // jshint ignore:line - wakeupReason: '', + alarmQueue: [], userSettingsDefault, userSettings: Object.assign({}, userSettingsDefault), @@ -168,26 +168,19 @@ const µBlock = { // jshint ignore:line netWhitelist: new Map(), netWhitelistModifyTime: 0, netWhitelistDefault: [ - 'about-scheme', 'chrome-extension-scheme', - 'chrome-scheme', - 'edge-scheme', 'moz-extension-scheme', - 'opera-scheme', - 'vivaldi-scheme', - 'wyciwyg-scheme', // Firefox's "What-You-Cache-Is-What-You-Get" ], - localSettings: { - blockedRequestCount: 0, - allowedRequestCount: 0, + requestStats: { + blockedCount: 0, + allowedCount: 0, }, - localSettingsLastModified: 0, // Read-only systemSettings: { compiledMagic: 57, // Increase when compiled format changes - selfieMagic: 57, // Increase when selfie format changes + selfieMagic: 58, // Increase when selfie format changes }, // https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501 @@ -311,7 +304,6 @@ const µBlock = { // jshint ignore:line } this.fromTabId(tabId); // Must be called AFTER tab context management this.realm = ''; - this.id = details.requestId; this.setMethod(details.method); this.setURL(details.url); this.aliasURL = details.aliasURL || undefined; @@ -373,8 +365,7 @@ const µBlock = { // jshint ignore:line toLogger() { const details = { - id: this.id, - tstamp: Date.now(), + tstamp: 0, realm: this.realm, method: this.getMethodName(), type: this.stype, diff --git a/src/js/base64-custom.js b/src/js/base64-custom.js index 34141b8..0d9a43f 100644 --- a/src/js/base64-custom.js +++ b/src/js/base64-custom.js @@ -46,105 +46,6 @@ const digitToVal = new Uint8Array(128); } } -// The sparse base64 codec is best for buffers which contains a lot of -// small u32 integer values. Those small u32 integer values are better -// represented with stringified integers, because small values can be -// represented with fewer bits than the usual base64 codec. For example, -// 0 become '0 ', i.e. 16 bits instead of 48 bits with official base64 -// codec. - -const sparseBase64 = { - magic: 'Base64_1', - - encode: function(arrbuf, arrlen) { - const inputLength = (arrlen + 3) >>> 2; - const inbuf = new Uint32Array(arrbuf, 0, inputLength); - const outputLength = this.magic.length + 7 + inputLength * 7; - const outbuf = new Uint8Array(outputLength); - // magic bytes - let j = 0; - for ( let i = 0; i < this.magic.length; i++ ) { - outbuf[j++] = this.magic.charCodeAt(i); - } - // array size - let v = inputLength; - do { - outbuf[j++] = valToDigit[v & 0b111111]; - v >>>= 6; - } while ( v !== 0 ); - outbuf[j++] = 0x20 /* ' ' */; - // array content - for ( let i = 0; i < inputLength; i++ ) { - v = inbuf[i]; - do { - outbuf[j++] = valToDigit[v & 0b111111]; - v >>>= 6; - } while ( v !== 0 ); - outbuf[j++] = 0x20 /* ' ' */; - } - if ( typeof TextDecoder === 'undefined' ) { - return JSON.stringify( - Array.from(new Uint32Array(outbuf.buffer, 0, j >>> 2)) - ); - } - const textDecoder = new TextDecoder(); - return textDecoder.decode(new Uint8Array(outbuf.buffer, 0, j)); - }, - - decode: function(instr, arrbuf) { - if ( instr.charCodeAt(0) === 0x5B /* '[' */ ) { - const inbuf = JSON.parse(instr); - if ( arrbuf instanceof ArrayBuffer === false ) { - return new Uint32Array(inbuf); - } - const outbuf = new Uint32Array(arrbuf); - outbuf.set(inbuf); - return outbuf; - } - if ( instr.startsWith(this.magic) === false ) { - throw new Error('Invalid µBlock.base64 encoding'); - } - const inputLength = instr.length; - const outputLength = this.decodeSize(instr) >> 2; - const outbuf = arrbuf instanceof ArrayBuffer === false - ? new Uint32Array(outputLength) - : new Uint32Array(arrbuf); - let i = instr.indexOf(' ', this.magic.length) + 1; - if ( i === -1 ) { - throw new Error('Invalid µBlock.base64 encoding'); - } - // array content - let j = 0; - for (;;) { - if ( j === outputLength || i >= inputLength ) { break; } - let v = 0, l = 0; - for (;;) { - const c = instr.charCodeAt(i++); - if ( c === 0x20 /* ' ' */ ) { break; } - v += digitToVal[c] << l; - l += 6; - } - outbuf[j++] = v; - } - if ( i < inputLength || j < outputLength ) { - throw new Error('Invalid µBlock.base64 encoding'); - } - return outbuf; - }, - - decodeSize: function(instr) { - if ( instr.startsWith(this.magic) === false ) { return 0; } - let v = 0, l = 0, i = this.magic.length; - for (;;) { - const c = instr.charCodeAt(i++); - if ( c === 0x20 /* ' ' */ ) { break; } - v += digitToVal[c] << l; - l += 6; - } - return v << 2; - }, -}; - // The dense base64 codec is best for typed buffers which values are // more random. For example, buffer contents as a result of compression // contain less repetitive values and thus the content is more @@ -154,7 +55,7 @@ const sparseBase64 = { // ArrayBuffer fails, the content of the resulting Uint8Array is // non-sensical. WASM-related? -const denseBase64 = { +export const denseBase64 = { magic: 'DenseBase64_1', encode: function(input) { @@ -242,5 +143,3 @@ const denseBase64 = { }; /******************************************************************************/ - -export { denseBase64, sparseBase64 }; diff --git a/src/js/benchmarks.js b/src/js/benchmarks.js index 8792f03..9fdc6ec 100644 --- a/src/js/benchmarks.js +++ b/src/js/benchmarks.js @@ -74,8 +74,8 @@ const loadBenchmarkDataset = (( ) => { datasetPromise = undefined; }); - return function() { - ttlTimer.offon({ min: 5 }); + return async function() { + ttlTimer.offon({ min: 2 }); if ( datasetPromise !== undefined ) { return datasetPromise; @@ -84,7 +84,7 @@ const loadBenchmarkDataset = (( ) => { const datasetURL = µb.hiddenSettings.benchmarkDatasetURL; if ( datasetURL === 'unset' ) { console.info(`No benchmark dataset available.`); - return Promise.resolve(); + return; } console.info(`Loading benchmark dataset...`); datasetPromise = io.fetchText(datasetURL).then(details => { @@ -136,7 +136,7 @@ const loadBenchmarkDataset = (( ) => { // action: 1=test -µb.benchmarkStaticNetFiltering = async function(options = {}) { +export async function benchmarkStaticNetFiltering(options = {}) { const { target, redirectEngine } = options; const requests = await loadBenchmarkDataset(); @@ -231,11 +231,11 @@ const loadBenchmarkDataset = (( ) => { const s = output.join('\n'); console.info(s); return s; -}; +} /******************************************************************************/ -µb.tokenHistograms = async function() { +export async function tokenHistogramsfunction() { const requests = await loadBenchmarkDataset(); if ( Array.isArray(requests) === false || requests.length === 0 ) { console.info('No requests found to benchmark'); @@ -272,11 +272,11 @@ const loadBenchmarkDataset = (( ) => { const tophits = Array.from(hitTokenMap).sort(customSort).slice(0, 100); console.info('Misses:', JSON.stringify(topmisses)); console.info('Hits:', JSON.stringify(tophits)); -}; +} /******************************************************************************/ -µb.benchmarkDynamicNetFiltering = async function() { +export async function benchmarkDynamicNetFiltering() { const requests = await loadBenchmarkDataset(); if ( Array.isArray(requests) === false || requests.length === 0 ) { console.info('No requests found to benchmark'); @@ -299,17 +299,19 @@ const loadBenchmarkDataset = (( ) => { const dur = t1 - t0; console.info(`Evaluated ${requests.length} requests in ${dur.toFixed(0)} ms`); console.info(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`); -}; +} /******************************************************************************/ -µb.benchmarkCosmeticFiltering = async function() { +export async function benchmarkCosmeticFiltering() { const requests = await loadBenchmarkDataset(); if ( Array.isArray(requests) === false || requests.length === 0 ) { console.info('No requests found to benchmark'); return; } - console.info('Benchmarking cosmeticFilteringEngine.retrieveSpecificSelectors()...'); + const output = [ + 'Benchmarking cosmeticFilteringEngine.retrieveSpecificSelectors()...', + ]; const details = { tabId: undefined, frameId: undefined, @@ -320,6 +322,7 @@ const loadBenchmarkDataset = (( ) => { const options = { noSpecificCosmeticFiltering: false, noGenericCosmeticFiltering: false, + dontInject: true, }; let count = 0; const t0 = performance.now(); @@ -334,25 +337,33 @@ const loadBenchmarkDataset = (( ) => { } const t1 = performance.now(); const dur = t1 - t0; - console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`); - console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`); -}; + output.push( + `Evaluated ${count} retrieval in ${dur.toFixed(0)} ms`, + `\tAverage: ${(dur / count).toFixed(3)} ms per document` + ); + const s = output.join('\n'); + console.info(s); + return s; +} /******************************************************************************/ -µb.benchmarkScriptletFiltering = async function() { +export async function benchmarkScriptletFiltering() { const requests = await loadBenchmarkDataset(); if ( Array.isArray(requests) === false || requests.length === 0 ) { console.info('No requests found to benchmark'); return; } - console.info('Benchmarking scriptletFilteringEngine.retrieve()...'); + const output = [ + 'Benchmarking scriptletFilteringEngine.retrieve()...', + ]; const details = { domain: '', entity: '', hostname: '', tabId: 0, url: '', + nocache: true, }; let count = 0; const t0 = performance.now(); @@ -368,13 +379,18 @@ const loadBenchmarkDataset = (( ) => { } const t1 = performance.now(); const dur = t1 - t0; - console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`); - console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`); -}; + output.push( + `Evaluated ${count} retrieval in ${dur.toFixed(0)} ms`, + `\tAverage: ${(dur / count).toFixed(3)} ms per document` + ); + const s = output.join('\n'); + console.info(s); + return s; +} /******************************************************************************/ -µb.benchmarkOnBeforeRequest = async function() { +export async function benchmarkOnBeforeRequest() { const requests = await loadBenchmarkDataset(); if ( Array.isArray(requests) === false || requests.length === 0 ) { console.info('No requests found to benchmark'); @@ -416,6 +432,6 @@ const loadBenchmarkDataset = (( ) => { console.info(`\tBlocked ${blockCount} requests`); console.info(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`); }); -}; +} /******************************************************************************/ diff --git a/src/js/biditrie.js b/src/js/biditrie.js index d0f64ee..1329316 100644 --- a/src/js/biditrie.js +++ b/src/js/biditrie.js @@ -576,34 +576,19 @@ class BidiTrieContainer { }; } - serialize(encoder) { - if ( encoder instanceof Object ) { - return encoder.encode( - this.buf32.buffer, - this.buf32[CHAR1_SLOT] - ); - } - return Array.from( - new Uint32Array( - this.buf32.buffer, - 0, - this.buf32[CHAR1_SLOT] + 3 >>> 2 - ) + toSelfie() { + return this.buf32.subarray( + 0, + this.buf32[CHAR1_SLOT] + 3 >>> 2 ); } - unserialize(selfie, decoder) { - const shouldDecode = typeof selfie === 'string'; - let byteLength = shouldDecode - ? decoder.decodeSize(selfie) - : selfie.length << 2; + fromSelfie(selfie) { + if ( selfie instanceof Uint32Array === false ) { return false; } + let byteLength = selfie.length << 2; if ( byteLength === 0 ) { return false; } this.reallocateBuf(byteLength); - if ( shouldDecode ) { - decoder.decode(selfie, this.buf8.buffer); - } else { - this.buf32.set(selfie); - } + this.buf32.set(selfie); return true; } diff --git a/src/js/broadcast.js b/src/js/broadcast.js index 0bef46c..61d647f 100644 --- a/src/js/broadcast.js +++ b/src/js/broadcast.js @@ -19,9 +19,7 @@ Home: https://github.com/gorhill/uBlock */ -/* globals browser */ - -'use strict'; +import webext from './webext.js'; /******************************************************************************/ @@ -47,7 +45,7 @@ export async function broadcastToAll(message) { }); const bcmessage = Object.assign({ broadcast: true }, message); for ( const tab of tabs ) { - browser.tabs.sendMessage(tab.id, bcmessage); + webext.tabs.sendMessage(tab.id, bcmessage).catch(( ) => { }); } } @@ -69,7 +67,19 @@ export function filteringBehaviorChanged(details = {}) { } filteringBehaviorChanged.throttle = vAPI.defer.create(( ) => { + const { history, max } = filteringBehaviorChanged; + const now = (Date.now() / 1000) | 0; + if ( history.length >= max ) { + if ( (now - history[0]) <= (10 * 60) ) { return; } + history.shift(); + } + history.push(now); vAPI.net.handlerBehaviorChanged(); }); +filteringBehaviorChanged.history = []; +filteringBehaviorChanged.max = Math.min( + browser.webRequest.MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES - 1, + 19 +); /******************************************************************************/ diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index ef056af..19f2dae 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -19,191 +19,439 @@ Home: https://github.com/gorhill/uBlock */ -/* global browser, IDBDatabase, indexedDB */ +/* global indexedDB */ 'use strict'; /******************************************************************************/ import lz4Codec from './lz4.js'; -import µb from './background.js'; import webext from './webext.js'; +import µb from './background.js'; +import { ubolog } from './console.js'; +import * as s14e from './s14e-serializer.js'; /******************************************************************************/ -// The code below has been originally manually imported from: -// Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134 -// Commit date: 29 October 2016 -// Commit author: https://github.com/nikrolls -// Commit message: "Implement cacheStorage using IndexedDB" - -// The original imported code has been subsequently modified as it was not -// compatible with Firefox. -// (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317) -// Furthermore, code to migrate from browser.storage.local to vAPI.storage -// has been added, for seamless migration of cache-related entries into -// indexedDB. - -// https://bugzilla.mozilla.org/show_bug.cgi?id=1371255 -// Firefox-specific: we use indexedDB because browser.storage.local() has -// poor performance in Firefox. -// https://github.com/uBlockOrigin/uBlock-issues/issues/328 -// Use IndexedDB for Chromium as well, to take advantage of LZ4 -// compression. -// https://github.com/uBlockOrigin/uBlock-issues/issues/399 -// Revert Chromium support of IndexedDB, use advanced setting to force -// IndexedDB. -// https://github.com/uBlockOrigin/uBlock-issues/issues/409 -// Allow forcing the use of webext storage on Firefox. - const STORAGE_NAME = 'uBlock0CacheStorage'; +const extensionStorage = webext.storage.local; + +const keysFromGetArg = arg => { + if ( arg === null || arg === undefined ) { return []; } + const type = typeof arg; + if ( type === 'string' ) { return [ arg ]; } + if ( Array.isArray(arg) ) { return arg; } + if ( type !== 'object' ) { return; } + return Object.keys(arg); +}; -// Default to webext storage. -const storageLocal = webext.storage.local; - -let storageReadyResolve; -const storageReadyPromise = new Promise(resolve => { - storageReadyResolve = resolve; -}); - -const cacheStorage = { - name: 'browser.storage.local', - get(...args) { - return storageReadyPromise.then(( ) => - storageLocal.get(...args).catch(reason => { - console.log(reason); - }) - ); - }, - set(...args) { - return storageReadyPromise.then(( ) => - storageLocal.set(...args).catch(reason => { - console.log(reason); - }) - ); - }, - remove(...args) { - return storageReadyPromise.then(( ) => - storageLocal.remove(...args).catch(reason => { - console.log(reason); - }) - ); - }, - clear(...args) { - return storageReadyPromise.then(( ) => - storageLocal.clear(...args).catch(reason => { - console.log(reason); - }) - ); - }, - select: function(selectedBackend) { - let actualBackend = selectedBackend; - if ( actualBackend === undefined || actualBackend === 'unset' ) { - actualBackend = vAPI.webextFlavor.soup.has('firefox') - ? 'indexedDB' - : 'browser.storage.local'; - } - if ( actualBackend === 'indexedDB' ) { - return selectIDB().then(success => { - if ( success || selectedBackend === 'indexedDB' ) { - clearWebext(); - storageReadyResolve(); - return 'indexedDB'; +let fastCache = 'indexedDB'; + +/******************************************************************************* + * + * Extension storage + * + * Always available. + * + * */ + +const cacheStorage = (( ) => { + + const exGet = (api, wanted, outbin) => { + return api.get(wanted).then(inbin => { + inbin = inbin || {}; + const found = Object.keys(inbin); + Object.assign(outbin, inbin); + if ( found.length === wanted.length ) { return; } + const missing = []; + for ( const key of wanted ) { + if ( outbin.hasOwnProperty(key) ) { continue; } + missing.push(key); + } + return missing; + }); + }; + + const compress = async (bin, key, data) => { + const µbhs = µb.hiddenSettings; + const after = await s14e.serializeAsync(data, { + compress: µbhs.cacheStorageCompression, + compressThreshold: µbhs.cacheStorageCompressionThreshold, + multithreaded: µbhs.cacheStorageMultithread, + }); + bin[key] = after; + }; + + const decompress = async (bin, key) => { + const data = bin[key]; + if ( s14e.isSerialized(data) === false ) { return; } + const µbhs = µb.hiddenSettings; + const isLarge = data.length >= µbhs.cacheStorageCompressionThreshold; + bin[key] = await s14e.deserializeAsync(data, { + multithreaded: isLarge && µbhs.cacheStorageMultithread || 1, + }); + }; + + const api = { + get(argbin) { + const outbin = {}; + return exGet( + cacheAPIs[fastCache], + keysFromGetArg(argbin), + outbin + ).then(wanted => { + if ( wanted === undefined ) { return; } + return exGet(extensionStorage, wanted, outbin); + }).then(wanted => { + if ( wanted === undefined ) { return; } + if ( argbin instanceof Object === false ) { return; } + if ( Array.isArray(argbin) ) { return; } + for ( const key of wanted ) { + if ( argbin.hasOwnProperty(key) === false ) { continue; } + outbin[key] = argbin[key]; + } + }).then(( ) => { + const promises = []; + for ( const key of Object.keys(outbin) ) { + promises.push(decompress(outbin, key)); } - clearIDB(); - storageReadyResolve(); - return 'browser.storage.local'; + return Promise.all(promises).then(( ) => outbin); + }).catch(reason => { + ubolog(reason); }); - } - if ( actualBackend === 'browser.storage.local' ) { - clearIDB(); - } - storageReadyResolve(); - return Promise.resolve('browser.storage.local'); - - }, - error: undefined -}; + }, + + async keys(regex) { + const results = await Promise.all([ + cacheAPIs[fastCache].keys(regex), + extensionStorage.get(null).catch(( ) => {}), + ]); + const keys = new Set(results[0]); + const bin = results[1] || {}; + for ( const key of Object.keys(bin) ) { + if ( regex && regex.test(key) === false ) { continue; } + keys.add(key); + } + return keys; + }, + + async set(rawbin) { + const keys = Object.keys(rawbin); + if ( keys.length === 0 ) { return; } + const serializedbin = {}; + const promises = []; + for ( const key of keys ) { + promises.push(compress(serializedbin, key, rawbin[key])); + } + await Promise.all(promises); + cacheAPIs[fastCache].set(rawbin, serializedbin); + return extensionStorage.set(serializedbin).catch(reason => { + ubolog(reason); + }); + }, -// Not all platforms support getBytesInUse -if ( storageLocal.getBytesInUse instanceof Function ) { - cacheStorage.getBytesInUse = function(...args) { - return storageLocal.getBytesInUse(...args).catch(reason => { - console.log(reason); - }); + remove(...args) { + cacheAPIs[fastCache].remove(...args); + return extensionStorage.remove(...args).catch(reason => { + ubolog(reason); + }); + }, + + clear(...args) { + cacheAPIs[fastCache].clear(...args); + return extensionStorage.clear(...args).catch(reason => { + ubolog(reason); + }); + }, + + select(api) { + if ( cacheAPIs.hasOwnProperty(api) === false ) { return fastCache; } + fastCache = api; + for ( const k of Object.keys(cacheAPIs) ) { + if ( k === api ) { continue; } + cacheAPIs[k]['clear'](); + } + return fastCache; + }, }; -} -// Reassign API entries to that of indexedDB-based ones -const selectIDB = async function() { - let db; - let dbPromise; + // Not all platforms support getBytesInUse + if ( extensionStorage.getBytesInUse instanceof Function ) { + api.getBytesInUse = function(...args) { + return extensionStorage.getBytesInUse(...args).catch(reason => { + ubolog(reason); + }); + }; + } - const noopfn = function () { + return api; +})(); + +/******************************************************************************* + * + * Cache API + * + * Purpose is to mirror cache-related items from extension storage, as its + * read/write operations are faster. May not be available/populated in + * private/incognito mode. + * + * */ + +const cacheAPI = (( ) => { + const caches = globalThis.caches; + let cacheStoragePromise; + + const getAPI = ( ) => { + if ( cacheStoragePromise !== undefined ) { return cacheStoragePromise; } + cacheStoragePromise = new Promise(resolve => { + if ( typeof caches !== 'object' || caches === null ) { + ubolog('CacheStorage API not available'); + resolve(null); + return; + } + resolve(caches.open(STORAGE_NAME)); + }).catch(reason => { + ubolog(reason); + return null; + }); + return cacheStoragePromise; }; - const disconnect = function() { - dbTimer.off(); - if ( db instanceof IDBDatabase ) { - db.close(); - db = undefined; + const urlPrefix = 'https://ublock0.invalid/'; + + const keyToURL = key => + `${urlPrefix}${encodeURIComponent(key)}`; + + const urlToKey = url => + decodeURIComponent(url.slice(urlPrefix.length)); + + // Cache API is subject to quota so we will use it only for what is key + // performance-wise + const shouldCache = bin => { + const out = {}; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/' ) ) { + if ( /^cache\/(compiled|selfie)\//.test(key) === false ) { continue; } + } + out[key] = bin[key]; } + if ( Object.keys(out).length !== 0 ) { return out; } }; - const dbTimer = vAPI.defer.create(( ) => { - disconnect(); - }); + const getOne = async key => { + const cache = await getAPI(); + if ( cache === null ) { return; } + return cache.match(keyToURL(key)).then(response => { + if ( response === undefined ) { return; } + return response.text(); + }).then(text => { + if ( text === undefined ) { return; } + return { key, text }; + }).catch(reason => { + ubolog(reason); + }); + }; - const keepAlive = function() { - dbTimer.offon(Math.max( - µb.hiddenSettings.autoUpdateAssetFetchPeriod * 2 * 1000, - 180000 - )); + const getAll = async ( ) => { + const cache = await getAPI(); + if ( cache === null ) { return; } + return cache.keys().then(requests => { + const promises = []; + for ( const request of requests ) { + promises.push(getOne(urlToKey(request.url))); + } + return Promise.all(promises); + }).then(responses => { + const bin = {}; + for ( const response of responses ) { + if ( response === undefined ) { continue; } + bin[response.key] = response.text; + } + return bin; + }).catch(reason => { + ubolog(reason); + }); }; - // https://github.com/gorhill/uBlock/issues/3156 - // I have observed that no event was fired in Tor Browser 7.0.7 + - // medium security level after the request to open the database was - // created. When this occurs, I have also observed that the `error` - // property was already set, so this means uBO can detect here whether - // the database can be opened successfully. A try-catch block is - // necessary when reading the `error` property because we are not - // allowed to read this property outside of event handlers in newer - // implementation of IDBRequest (my understanding). + const setOne = async (key, text) => { + if ( text === undefined ) { return removeOne(key); } + const blob = new Blob([ text ], { type: 'text/plain;charset=utf-8'}); + const cache = await getAPI(); + if ( cache === null ) { return; } + return cache + .put(keyToURL(key), new Response(blob)) + .catch(reason => { + ubolog(reason); + }); + }; - const getDb = function() { - keepAlive(); - if ( db !== undefined ) { - return Promise.resolve(db); - } - if ( dbPromise !== undefined ) { - return dbPromise; - } - dbPromise = new Promise(resolve => { - let req; - try { - req = indexedDB.open(STORAGE_NAME, 1); - if ( req.error ) { - console.log(req.error); - req = undefined; + const removeOne = async key => { + const cache = await getAPI(); + if ( cache === null ) { return; } + return cache.delete(keyToURL(key)).catch(reason => { + ubolog(reason); + }); + }; + + return { + async get(arg) { + const keys = keysFromGetArg(arg); + if ( keys === undefined ) { return; } + if ( keys.length === 0 ) { + return getAll(); + } + const bin = {}; + const toFetch = keys.slice(); + const hasDefault = typeof arg === 'object' && Array.isArray(arg) === false; + for ( let i = 0; i < toFetch.length; i++ ) { + const key = toFetch[i]; + if ( hasDefault && arg[key] !== undefined ) { + bin[key] = arg[key]; } - } catch(ex) { + toFetch[i] = getOne(key); } - if ( req === undefined ) { - db = null; - dbPromise = undefined; - return resolve(null); + const responses = await Promise.all(toFetch); + for ( const response of responses ) { + if ( response === undefined ) { continue; } + const { key, text } = response; + if ( typeof key !== 'string' ) { continue; } + if ( typeof text !== 'string' ) { continue; } + bin[key] = text; } - req.onupgradeneeded = function(ev) { - // https://github.com/uBlockOrigin/uBlock-issues/issues/2725 - // If context Firefox + incognito mode, fall back to - // browser.storage.local for cache storage purpose. - if ( - vAPI.webextFlavor.soup.has('firefox') && - browser.extension.inIncognitoContext === true - ) { - return req.onerror(); + if ( Object.keys(bin).length === 0 ) { return; } + return bin; + }, + + async keys(regex) { + const cache = await getAPI(); + if ( cache === null ) { return []; } + return cache.keys().then(requests => + requests.map(r => urlToKey(r.url)) + .filter(k => regex === undefined || regex.test(k)) + ).catch(( ) => []); + }, + + async set(rawbin, serializedbin) { + const bin = shouldCache(serializedbin); + if ( bin === undefined ) { return; } + const keys = Object.keys(bin); + const promises = []; + for ( const key of keys ) { + promises.push(setOne(key, bin[key])); + } + return Promise.all(promises); + }, + + remove(keys) { + const toRemove = []; + if ( typeof keys === 'string' ) { + toRemove.push(removeOne(keys)); + } else if ( Array.isArray(keys) ) { + for ( const key of keys ) { + toRemove.push(removeOne(key)); } + } + return Promise.all(toRemove); + }, + + async clear() { + if ( typeof caches !== 'object' || caches === null ) { return; } + return globalThis.caches.delete(STORAGE_NAME).catch(reason => { + ubolog(reason); + }); + }, + + shutdown() { + cacheStoragePromise = undefined; + return this.clear(); + }, + }; +})(); + +/******************************************************************************* + * + * In-memory storage + * + * */ + +const memoryStorage = (( ) => { + + const sessionStorage = vAPI.sessionStorage; + + // This should help speed up loading from suspended state in Firefox for + // Android. + // 20240228 Observation: Slows down loading from suspended state in + // Firefox desktop. Could be different in Firefox for Android. + const shouldCache = bin => { + const out = {}; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/compiled/') ) { continue; } + out[key] = bin[key]; + } + if ( Object.keys(out).length !== 0 ) { return out; } + }; + + return { + get(...args) { + return sessionStorage.get(...args).then(bin => { + return bin; + }).catch(reason => { + ubolog(reason); + }); + }, + + async keys(regex) { + const bin = await this.get(null); + const keys = []; + for ( const key of Object.keys(bin || {}) ) { + if ( regex && regex.test(key) === false ) { continue; } + keys.push(key); + } + return keys; + }, + + async set(rawbin, serializedbin) { + const bin = shouldCache(serializedbin); + if ( bin === undefined ) { return; } + return sessionStorage.set(bin).catch(reason => { + ubolog(reason); + }); + }, + + remove(...args) { + return sessionStorage.remove(...args).catch(reason => { + ubolog(reason); + }); + }, + + clear(...args) { + return sessionStorage.clear(...args).catch(reason => { + ubolog(reason); + }); + }, + + shutdown() { + return this.clear(); + }, + }; +})(); + +/******************************************************************************* + * + * IndexedDB + * + * Deprecated, exists only for the purpose of migrating from older versions. + * + * */ + +const idbStorage = (( ) => { + let dbPromise; + + const getDb = function() { + if ( dbPromise !== undefined ) { return dbPromise; } + dbPromise = new Promise(resolve => { + const req = indexedDB.open(STORAGE_NAME, 1); + req.onupgradeneeded = ev => { if ( ev.oldVersion === 1 ) { return; } try { const db = ev.target.result; @@ -212,35 +460,44 @@ const selectIDB = async function() { req.onerror(); } }; - req.onsuccess = function(ev) { + req.onsuccess = ev => { if ( resolve === undefined ) { return; } - req = undefined; - db = ev.target.result; - dbPromise = undefined; - resolve(db); + resolve(ev.target.result || null); resolve = undefined; }; - req.onerror = req.onblocked = function() { + req.onerror = req.onblocked = ( ) => { if ( resolve === undefined ) { return; } - req = undefined; - console.log(this.error); - db = null; - dbPromise = undefined; + ubolog(req.error); resolve(null); resolve = undefined; }; - vAPI.defer.once(5000).then(( ) => { + vAPI.defer.once(10000).then(( ) => { if ( resolve === undefined ) { return; } - db = null; - dbPromise = undefined; resolve(null); resolve = undefined; }); + }).catch(reason => { + ubolog(`idbStorage() / getDb() failed: ${reason}`); + return null; }); return dbPromise; }; - const fromBlob = function(data) { + // Cache API is subject to quota so we will use it only for what is key + // performance-wise + const shouldCache = bin => { + const out = {}; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/' ) ) { + if ( /^cache\/(compiled|selfie)\//.test(key) === false ) { continue; } + } + out[key] = bin[key]; + } + if ( Object.keys(out).length === 0 ) { return; } + return out; + }; + + const fromBlob = data => { if ( data instanceof Blob === false ) { return Promise.resolve(data); } @@ -253,277 +510,213 @@ const selectIDB = async function() { }); }; - const toBlob = function(data) { - const value = data instanceof Uint8Array - ? new Blob([ data ]) - : data; - return Promise.resolve(value); + const decompress = (key, value) => { + return lz4Codec.decode(value, fromBlob).then(value => { + return { key, value }; + }); }; - const compress = function(store, key, data) { - return lz4Codec.encode(data, toBlob).then(value => { - store.push({ key, value }); + const getAllEntries = async function() { + const db = await getDb(); + if ( db === null ) { return []; } + return new Promise(resolve => { + const entries = []; + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + resolve(Promise.all(entries)); + }; + const table = transaction.objectStore(STORAGE_NAME); + const req = table.openCursor(); + req.onsuccess = ev => { + const cursor = ev.target && ev.target.result; + if ( !cursor ) { return; } + const { key, value } = cursor.value; + if ( value instanceof Blob ) { + entries.push(decompress(key, value)); + } else { + entries.push({ key, value }); + } + cursor.continue(); + }; + }).catch(reason => { + ubolog(`idbStorage() / getAllEntries() failed: ${reason}`); + return []; }); }; - const decompress = function(store, key, data) { - return lz4Codec.decode(data, fromBlob).then(data => { - store[key] = data; + const getAllKeys = async function(regex) { + const db = await getDb(); + if ( db === null ) { return []; } + return new Promise(resolve => { + const keys = []; + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + resolve(keys); + }; + const table = transaction.objectStore(STORAGE_NAME); + const req = table.openCursor(); + req.onsuccess = ev => { + const cursor = ev.target && ev.target.result; + if ( !cursor ) { return; } + if ( regex && regex.test(cursor.key) === false ) { return; } + keys.push(cursor.key); + cursor.continue(); + }; + }).catch(reason => { + ubolog(`idbStorage() / getAllKeys() failed: ${reason}`); + return []; }); }; - const getFromDb = async function(keys, keyvalStore, callback) { - if ( typeof callback !== 'function' ) { return; } - if ( keys.length === 0 ) { return callback(keyvalStore); } - const promises = []; - const gotOne = function() { - if ( typeof this.result !== 'object' ) { return; } - const { key, value } = this.result; - keyvalStore[key] = value; - if ( value instanceof Blob === false ) { return; } - promises.push(decompress(keyvalStore, key, value)); - }; - try { - const db = await getDb(); - if ( !db ) { return callback(); } + const getEntries = async function(keys) { + const db = await getDb(); + if ( db === null ) { return []; } + return new Promise(resolve => { + const entries = []; + const gotOne = ev => { + const { result } = ev.target; + if ( typeof result !== 'object' ) { return; } + if ( result === null ) { return; } + const { key, value } = result; + if ( value instanceof Blob ) { + entries.push(decompress(key, value)); + } else { + entries.push({ key, value }); + } + }; const transaction = db.transaction(STORAGE_NAME, 'readonly'); transaction.oncomplete = transaction.onerror = - transaction.onabort = ( ) => { - Promise.all(promises).then(( ) => { - callback(keyvalStore); - }); + transaction.onabort = ( ) => { + resolve(Promise.all(entries)); }; const table = transaction.objectStore(STORAGE_NAME); for ( const key of keys ) { const req = table.get(key); req.onsuccess = gotOne; - req.onerror = noopfn; + req.onerror = ( ) => { }; } - } - catch(reason) { - console.info(`cacheStorage.getFromDb() failed: ${reason}`); - callback(); - } - }; - - const visitAllFromDb = async function(visitFn) { - const db = await getDb(); - if ( !db ) { return visitFn(); } - const transaction = db.transaction(STORAGE_NAME, 'readonly'); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = ( ) => visitFn(); - const table = transaction.objectStore(STORAGE_NAME); - const req = table.openCursor(); - req.onsuccess = function(ev) { - let cursor = ev.target && ev.target.result; - if ( !cursor ) { return; } - let entry = cursor.value; - visitFn(entry); - cursor.continue(); - }; - }; - - const getAllFromDb = function(callback) { - if ( typeof callback !== 'function' ) { return; } - const promises = []; - const keyvalStore = {}; - visitAllFromDb(entry => { - if ( entry === undefined ) { - Promise.all(promises).then(( ) => { - callback(keyvalStore); - }); - return; - } - const { key, value } = entry; - keyvalStore[key] = value; - if ( entry.value instanceof Blob === false ) { return; } - promises.push(decompress(keyvalStore, key, value)); }).catch(reason => { - console.info(`cacheStorage.getAllFromDb() failed: ${reason}`); - callback(); + ubolog(`idbStorage() / getEntries() failed: ${reason}`); + return []; }); }; - // https://github.com/uBlockOrigin/uBlock-issues/issues/141 - // Mind that IDBDatabase.transaction() and IDBObjectStore.put() - // can throw: - // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction - // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put - - const putToDb = async function(keyvalStore, callback) { - if ( typeof callback !== 'function' ) { - callback = noopfn; + const getAll = async ( ) => { + const entries = await getAllEntries(); + const outbin = {}; + for ( const { key, value } of entries ) { + outbin[key] = value; } - const keys = Object.keys(keyvalStore); - if ( keys.length === 0 ) { return callback(); } - const promises = [ getDb() ]; - const entries = []; - const dontCompress = - µb.hiddenSettings.cacheStorageCompression !== true; - for ( const key of keys ) { - const value = keyvalStore[key]; - const isString = typeof value === 'string'; - if ( isString === false || dontCompress ) { - entries.push({ key, value }); - continue; + return outbin; + }; + + const setEntries = async inbin => { + const keys = Object.keys(inbin); + if ( keys.length === 0 ) { return; } + const db = await getDb(); + if ( db === null ) { return; } + return new Promise(resolve => { + const entries = []; + for ( const key of keys ) { + entries.push({ key, value: inbin[key] }); } - promises.push(compress(entries, key, value)); - } - const finish = ( ) => { - if ( callback === undefined ) { return; } - let cb = callback; - callback = undefined; - cb(); - }; - try { - const results = await Promise.all(promises); - const db = results[0]; - if ( !db ) { return callback(); } - const transaction = db.transaction( - STORAGE_NAME, - 'readwrite' - ); + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); transaction.oncomplete = transaction.onerror = - transaction.onabort = finish; + transaction.onabort = ( ) => { + resolve(); + }; const table = transaction.objectStore(STORAGE_NAME); for ( const entry of entries ) { table.put(entry); } - } catch (ex) { - finish(); - } + }).catch(reason => { + ubolog(`idbStorage() / setEntries() failed: ${reason}`); + }); }; - const deleteFromDb = async function(input, callback) { - if ( typeof callback !== 'function' ) { - callback = noopfn; - } - const keys = Array.isArray(input) ? input.slice() : [ input ]; - if ( keys.length === 0 ) { return callback(); } - const finish = ( ) => { - if ( callback === undefined ) { return; } - let cb = callback; - callback = undefined; - cb(); - }; - try { - const db = await getDb(); - if ( !db ) { return callback(); } + const deleteEntries = async arg => { + const keys = Array.isArray(arg) ? arg.slice() : [ arg ]; + if ( keys.length === 0 ) { return; } + const db = await getDb(); + if ( db === null ) { return; } + return new Promise(resolve => { const transaction = db.transaction(STORAGE_NAME, 'readwrite'); transaction.oncomplete = transaction.onerror = - transaction.onabort = finish; + transaction.onabort = ( ) => { + resolve(); + }; const table = transaction.objectStore(STORAGE_NAME); for ( const key of keys ) { table.delete(key); } - } catch (ex) { - finish(); - } - }; - - const clearDb = async function(callback) { - if ( typeof callback !== 'function' ) { - callback = noopfn; - } - try { - const db = await getDb(); - if ( !db ) { return callback(); } - const transaction = db.transaction(STORAGE_NAME, 'readwrite'); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = ( ) => { - callback(); - }; - transaction.objectStore(STORAGE_NAME).clear(); - } - catch(reason) { - console.info(`cacheStorage.clearDb() failed: ${reason}`); - callback(); - } + }).catch(reason => { + ubolog(`idbStorage() / deleteEntries() failed: ${reason}`); + }); }; - await getDb(); - if ( !db ) { return false; } - - cacheStorage.name = 'indexedDB'; - cacheStorage.get = function get(keys) { - return storageReadyPromise.then(( ) => - new Promise(resolve => { - if ( keys === null ) { - return getAllFromDb(bin => resolve(bin)); - } - let toRead, output = {}; - if ( typeof keys === 'string' ) { - toRead = [ keys ]; - } else if ( Array.isArray(keys) ) { - toRead = keys; - } else /* if ( typeof keys === 'object' ) */ { - toRead = Object.keys(keys); - output = keys; + return { + async get(argbin) { + const keys = keysFromGetArg(argbin); + if ( keys === undefined ) { return; } + if ( keys.length === 0 ) { return getAll(); } + const entries = await getEntries(keys); + const outbin = {}; + for ( const { key, value } of entries ) { + outbin[key] = value; + } + if ( argbin instanceof Object && Array.isArray(argbin) === false ) { + for ( const key of keys ) { + if ( outbin.hasOwnProperty(key) ) { continue; } + outbin[key] = argbin[key]; } - getFromDb(toRead, output, bin => resolve(bin)); - }) - ); - }; - cacheStorage.set = function set(keys) { - return storageReadyPromise.then(( ) => - new Promise(resolve => { - putToDb(keys, details => resolve(details)); - }) - ); - }; - cacheStorage.remove = function remove(keys) { - return storageReadyPromise.then(( ) => - new Promise(resolve => { - deleteFromDb(keys, ( ) => resolve()); - }) - ); - }; - cacheStorage.clear = function clear() { - return storageReadyPromise.then(( ) => - new Promise(resolve => { - clearDb(( ) => resolve()); - }) - ); - }; - cacheStorage.getBytesInUse = function getBytesInUse() { - return Promise.resolve(0); + } + return outbin; + }, + + async set(rawbin) { + const bin = shouldCache(rawbin); + if ( bin === undefined ) { return; } + return setEntries(bin); + }, + + keys(...args) { + return getAllKeys(...args); + }, + + remove(...args) { + return deleteEntries(...args); + }, + + clear() { + return getDb().then(db => { + if ( db === null ) { return; } + db.close(); + indexedDB.deleteDatabase(STORAGE_NAME); + }).catch(reason => { + ubolog(`idbStorage.clear() failed: ${reason}`); + }); + }, + + async shutdown() { + await this.clear(); + dbPromise = undefined; + }, }; - return true; -}; +})(); -// https://github.com/uBlockOrigin/uBlock-issues/issues/328 -// Delete cache-related entries from webext storage. -const clearWebext = async function() { - let bin; - try { - bin = await webext.storage.local.get('assetCacheRegistry'); - } catch(ex) { - console.error(ex); - } - if ( bin instanceof Object === false ) { return; } - if ( bin.assetCacheRegistry instanceof Object === false ) { return; } - const toRemove = [ - 'assetCacheRegistry', - 'assetSourceRegistry', - ]; - for ( const key in bin.assetCacheRegistry ) { - if ( bin.assetCacheRegistry.hasOwnProperty(key) ) { - toRemove.push('cache/' + key); - } - } - webext.storage.local.remove(toRemove); -}; +/******************************************************************************/ -const clearIDB = function() { - try { - indexedDB.deleteDatabase(STORAGE_NAME); - } catch(ex) { - } +const cacheAPIs = { + 'indexedDB': idbStorage, + 'cacheAPI': cacheAPI, + 'browser.storage.session': memoryStorage, }; /******************************************************************************/ diff --git a/src/js/click2load.js b/src/js/click2load.js index 42b7525..b441d97 100644 --- a/src/js/click2load.js +++ b/src/js/click2load.js @@ -49,9 +49,8 @@ document.body.addEventListener('click', ev => { what: 'clickToLoad', frameURL, }).then(ok => { - if ( ok ) { - self.location.replace(frameURL); - } + if ( ok !== true ) { return; } + self.location.replace(frameURL); }); }); diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js index 477e9cc..7ee5b33 100644 --- a/src/js/codemirror/search.js +++ b/src/js/codemirror/search.js @@ -25,18 +25,25 @@ // Ctrl-G. // ===== -'use strict'; - import { dom, qs$ } from '../dom.js'; import { i18n$ } from '../i18n.js'; { const CodeMirror = self.CodeMirror; + CodeMirror.defineOption('maximizable', true, (cm, maximizable) => { + if ( typeof maximizable !== 'boolean' ) { return; } + const wrapper = cm.getWrapperElement(); + if ( wrapper === null ) { return; } + const container = wrapper.closest('.codeMirrorContainer'); + if ( container === null ) { return; } + container.dataset.maximizable = `${maximizable}`; + }); + const searchOverlay = function(query, caseInsensitive) { if ( typeof query === 'string' ) query = new RegExp( - query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), + query.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'), caseInsensitive ? 'gi' : 'g' ); else if ( !query.global ) @@ -89,8 +96,10 @@ import { i18n$ } from '../i18n.js'; state.queryTimer.offon(350); }; - const searchWidgetClickHandler = function(cm, ev) { - const tcl = ev.target.classList; + const searchWidgetClickHandler = (ev, cm) => { + if ( ev.button !== 0 ) { return; } + const target = ev.target; + const tcl = target.classList; if ( tcl.contains('cm-search-widget-up') ) { findNext(cm, -1); } else if ( tcl.contains('cm-search-widget-down') ) { @@ -99,11 +108,14 @@ import { i18n$ } from '../i18n.js'; findNextError(cm, -1); } else if ( tcl.contains('cm-linter-widget-down') ) { findNextError(cm, 1); + } else if ( tcl.contains('cm-maximize') ) { + const container = target.closest('.codeMirrorContainer'); + if ( container !== null ) { + container.classList.toggle('cm-maximized'); + } } - if ( ev.target.localName !== 'input' ) { - ev.preventDefault(); - } else { - ev.stopImmediatePropagation(); + if ( target.localName !== 'input' ) { + cm.focus(); } }; @@ -127,7 +139,9 @@ import { i18n$ } from '../i18n.js'; this.widget = widgetParent.children[0]; this.widget.addEventListener('keydown', searchWidgetKeydownHandler.bind(null, cm)); this.widget.addEventListener('input', searchWidgetInputHandler.bind(null, cm)); - this.widget.addEventListener('mousedown', searchWidgetClickHandler.bind(null, cm)); + this.widget.addEventListener('click', ev => { + searchWidgetClickHandler(ev, cm); + }); if ( typeof cm.addPanel === 'function' ) { this.panel = cm.addPanel(this.widget); } @@ -236,10 +250,7 @@ import { i18n$ } from '../i18n.js'; notation: 'compact', maximumSignificantDigits: 3 }); - if ( - intl.resolvedOptions instanceof Function && - intl.resolvedOptions().hasOwnProperty('notation') - ) { + if ( intl.resolvedOptions().notation ) { intlNumberFormat = intl; } } @@ -330,9 +341,6 @@ import { i18n$ } from '../i18n.js'; state.annotate.update(annotations); }); state.widget.setAttribute('data-query', state.queryText); - // Ensure the caret is visible - const input = state.widget.querySelector('.cm-search-widget-input input'); - input.selectionStart = input.selectionStart; }; const findNext = function(cm, dir, callback) { @@ -458,26 +466,30 @@ import { i18n$ } from '../i18n.js'; }; { - const searchWidgetTemplate = - '<div class="cm-search-widget-template" style="display:none;">' + - '<div class="cm-search-widget">' + - '<span class="cm-search-widget-input">' + - '<span class="fa-icon fa-icon-ro">search</span> ' + - '<input type="search" spellcheck="false"> ' + - '<span class="cm-search-widget-up cm-search-widget-button fa-icon">angle-up</span> ' + - '<span class="cm-search-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ' + - '<span class="cm-search-widget-count"></span>' + - '</span>' + - '<span class="cm-linter-widget" data-lint="0">' + - '<span class="cm-linter-widget-count"></span> ' + - '<span class="cm-linter-widget-up cm-search-widget-button fa-icon">angle-up</span> ' + - '<span class="cm-linter-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ' + - '</span>' + - '<span>' + - '<a class="fa-icon sourceURL" href>external-link</a>' + - '</span>' + - '</div>' + - '</div>'; + const searchWidgetTemplate = [ + '<div class="cm-search-widget-template" style="display:none;">', + '<div class="cm-search-widget">', + '<span class="cm-maximize"><svg viewBox="0 0 40 40"><path d="M4,16V4h12M24,4H36V16M4,24V36H16M36,24V36H24" /><path d="M14 2.5v12h-12M38 14h-12v-12M14 38v-12h-12M26 38v-12h12" /></svg></span> ', + '<span class="cm-search-widget-input">', + '<span class="searchfield">', + '<input type="search" spellcheck="false" placeholder="">', + '<span class="fa-icon">search</span>', + '</span> ', + '<span class="cm-search-widget-up cm-search-widget-button fa-icon">angle-up</span> ', + '<span class="cm-search-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ', + '<span class="cm-search-widget-count"></span>', + '</span>', + '<span class="cm-linter-widget" data-lint="0">', + '<span class="cm-linter-widget-count"></span> ', + '<span class="cm-linter-widget-up cm-search-widget-button fa-icon">angle-up</span> ', + '<span class="cm-linter-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ', + '</span>', + '<span>', + '<a class="fa-icon sourceURL" href>external-link</a>', + '</span>', + '</div>', + '</div>', + ].join('\n'); const domParser = new DOMParser(); const doc = domParser.parseFromString(searchWidgetTemplate, 'text/html'); const widgetTemplate = document.adoptNode(doc.body.firstElementChild); diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js index ac1b048..2aaf85b 100644 --- a/src/js/codemirror/ubo-static-filtering.js +++ b/src/js/codemirror/ubo-static-filtering.js @@ -21,8 +21,6 @@ /* global CodeMirror */ -'use strict'; - /******************************************************************************/ import * as sfp from '../static-filtering-parser.js'; @@ -39,10 +37,10 @@ let hintHelperRegistered = false; /******************************************************************************/ -CodeMirror.defineOption('trustedSource', false, (cm, state) => { - if ( typeof state !== 'boolean' ) { return; } +CodeMirror.defineOption('trustedSource', false, (cm, trusted) => { + if ( typeof trusted !== 'boolean' ) { return; } self.dispatchEvent(new CustomEvent('trustedSource', { - detail: state, + detail: { cm, trusted }, })); }); @@ -56,219 +54,232 @@ CodeMirror.defineOption('trustedScriptletTokens', undefined, (cm, tokens) => { /******************************************************************************/ -CodeMirror.defineMode('ubo-static-filtering', function() { - const astParser = new sfp.AstFilterParser({ - interactive: true, - nativeCssHas: vAPI.webextFlavor.env.includes('native_css_has'), - }); - const astWalker = astParser.getWalker(); - let currentWalkerNode = 0; - let lastNetOptionType = 0; - - const redirectTokenStyle = node => { - const rawToken = astParser.getNodeString(node || currentWalkerNode); +const uBOStaticFilteringMode = (( ) => { + const redirectTokenStyle = (mode, node) => { + const rawToken = mode.astParser.getNodeString(node || mode.currentWalkerNode); const { token } = sfp.parseRedirectValue(rawToken); return redirectNames.has(token) ? 'value' : 'value warning'; }; - const nodeHasError = node => { - return astParser.getNodeFlags( - node || currentWalkerNode, sfp.NODE_FLAG_ERROR + const nodeHasError = (mode, node) => { + return mode.astParser.getNodeFlags( + node || mode.currentWalkerNode, sfp.NODE_FLAG_ERROR ) !== 0; }; - const colorFromAstNode = function() { - if ( astParser.nodeIsEmptyString(currentWalkerNode) ) { return '+'; } - if ( nodeHasError() ) { return 'error'; } - const nodeType = astParser.getNodeType(currentWalkerNode); + const colorFromAstNode = mode => { + if ( mode.astParser.nodeIsEmptyString(mode.currentWalkerNode) ) { return '+'; } + if ( nodeHasError(mode) ) { return 'error'; } + const nodeType = mode.astParser.getNodeType(mode.currentWalkerNode); switch ( nodeType ) { - case sfp.NODE_TYPE_WHITESPACE: - return ''; - case sfp.NODE_TYPE_COMMENT: - if ( astWalker.canGoDown() ) { break; } - return 'comment'; - case sfp.NODE_TYPE_COMMENT_URL: - return 'comment link'; - case sfp.NODE_TYPE_IGNORE: - return 'comment'; - case sfp.NODE_TYPE_PREPARSE_DIRECTIVE: - case sfp.NODE_TYPE_PREPARSE_DIRECTIVE_VALUE: - return 'directive'; - case sfp.NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE: { - const raw = astParser.getNodeString(currentWalkerNode); - const state = sfp.utils.preparser.evaluateExpr(raw, preparseDirectiveEnv); - return state ? 'positive strong' : 'negative strong'; + case sfp.NODE_TYPE_WHITESPACE: + return ''; + case sfp.NODE_TYPE_COMMENT: + if ( mode.astWalker.canGoDown() ) { break; } + return 'comment'; + case sfp.NODE_TYPE_COMMENT_URL: + return 'comment link'; + case sfp.NODE_TYPE_IGNORE: + return 'comment'; + case sfp.NODE_TYPE_PREPARSE_DIRECTIVE: + case sfp.NODE_TYPE_PREPARSE_DIRECTIVE_VALUE: + return 'directive'; + case sfp.NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE: { + const raw = mode.astParser.getNodeString(mode.currentWalkerNode); + const state = sfp.utils.preparser.evaluateExpr(raw, preparseDirectiveEnv); + return state ? 'positive strong' : 'negative strong'; + } + case sfp.NODE_TYPE_EXT_OPTIONS_ANCHOR: + return mode.astParser.getFlags(sfp.AST_FLAG_IS_EXCEPTION) + ? 'tag strong' + : 'def strong'; + case sfp.NODE_TYPE_EXT_DECORATION: + return 'def'; + case sfp.NODE_TYPE_EXT_PATTERN_RAW: + if ( mode.astWalker.canGoDown() ) { break; } + return 'variable'; + case sfp.NODE_TYPE_EXT_PATTERN_COSMETIC: + case sfp.NODE_TYPE_EXT_PATTERN_HTML: + return 'variable'; + case sfp.NODE_TYPE_EXT_PATTERN_RESPONSEHEADER: + case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET: + if ( mode.astWalker.canGoDown() ) { break; } + return 'variable'; + case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN: { + const token = mode.astParser.getNodeString(mode.currentWalkerNode); + if ( scriptletNames.has(token) === false ) { + return 'warning'; } - case sfp.NODE_TYPE_EXT_OPTIONS_ANCHOR: - return astParser.getFlags(sfp.AST_FLAG_IS_EXCEPTION) - ? 'tag strong' - : 'def strong'; - case sfp.NODE_TYPE_EXT_DECORATION: - return 'def'; - case sfp.NODE_TYPE_EXT_PATTERN_RAW: - if ( astWalker.canGoDown() ) { break; } - return 'variable'; - case sfp.NODE_TYPE_EXT_PATTERN_COSMETIC: - case sfp.NODE_TYPE_EXT_PATTERN_HTML: - return 'variable'; - case sfp.NODE_TYPE_EXT_PATTERN_RESPONSEHEADER: - case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET: - if ( astWalker.canGoDown() ) { break; } - return 'variable'; - case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN: { - const token = astParser.getNodeString(currentWalkerNode); - if ( scriptletNames.has(token) === false ) { - return 'warning'; + return 'variable'; + } + case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG: + return 'variable'; + case sfp.NODE_TYPE_NET_EXCEPTION: + return 'tag strong'; + case sfp.NODE_TYPE_NET_PATTERN: + if ( mode.astWalker.canGoDown() ) { break; } + if ( mode.astParser.isRegexPattern() ) { + if ( mode.astParser.getNodeFlags(mode.currentWalkerNode, sfp.NODE_FLAG_PATTERN_UNTOKENIZABLE) !== 0 ) { + return 'variable warning'; } - return 'variable'; + return 'variable notice'; } - case sfp.NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG: - return 'variable'; - case sfp.NODE_TYPE_NET_EXCEPTION: - return 'tag strong'; - case sfp.NODE_TYPE_NET_PATTERN: - if ( astWalker.canGoDown() ) { break; } - if ( astParser.isRegexPattern() ) { - if ( astParser.getNodeFlags(currentWalkerNode, sfp.NODE_FLAG_PATTERN_UNTOKENIZABLE) !== 0 ) { - return 'variable warning'; - } - return 'variable notice'; - } - return 'variable'; - case sfp.NODE_TYPE_NET_PATTERN_PART: - return 'variable'; - case sfp.NODE_TYPE_NET_PATTERN_PART_SPECIAL: - return 'keyword strong'; - case sfp.NODE_TYPE_NET_PATTERN_PART_UNICODE: - return 'variable unicode'; - case sfp.NODE_TYPE_NET_PATTERN_LEFT_HNANCHOR: - case sfp.NODE_TYPE_NET_PATTERN_LEFT_ANCHOR: - case sfp.NODE_TYPE_NET_PATTERN_RIGHT_ANCHOR: - case sfp.NODE_TYPE_NET_OPTION_NAME_NOT: - return 'keyword strong'; - case sfp.NODE_TYPE_NET_OPTIONS_ANCHOR: - case sfp.NODE_TYPE_NET_OPTION_SEPARATOR: - lastNetOptionType = 0; - return 'def strong'; - case sfp.NODE_TYPE_NET_OPTION_NAME_UNKNOWN: - lastNetOptionType = 0; - return 'error'; - case sfp.NODE_TYPE_NET_OPTION_NAME_1P: - case sfp.NODE_TYPE_NET_OPTION_NAME_STRICT1P: - case sfp.NODE_TYPE_NET_OPTION_NAME_3P: - case sfp.NODE_TYPE_NET_OPTION_NAME_STRICT3P: - case sfp.NODE_TYPE_NET_OPTION_NAME_ALL: - case sfp.NODE_TYPE_NET_OPTION_NAME_BADFILTER: - case sfp.NODE_TYPE_NET_OPTION_NAME_CNAME: - case sfp.NODE_TYPE_NET_OPTION_NAME_CSP: - case sfp.NODE_TYPE_NET_OPTION_NAME_CSS: - case sfp.NODE_TYPE_NET_OPTION_NAME_DENYALLOW: - case sfp.NODE_TYPE_NET_OPTION_NAME_DOC: - case sfp.NODE_TYPE_NET_OPTION_NAME_EHIDE: - case sfp.NODE_TYPE_NET_OPTION_NAME_EMPTY: - case sfp.NODE_TYPE_NET_OPTION_NAME_FONT: - case sfp.NODE_TYPE_NET_OPTION_NAME_FRAME: - case sfp.NODE_TYPE_NET_OPTION_NAME_FROM: - case sfp.NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK: - case sfp.NODE_TYPE_NET_OPTION_NAME_GHIDE: - case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: - case sfp.NODE_TYPE_NET_OPTION_NAME_IMAGE: - case sfp.NODE_TYPE_NET_OPTION_NAME_IMPORTANT: - case sfp.NODE_TYPE_NET_OPTION_NAME_INLINEFONT: - case sfp.NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT: - case sfp.NODE_TYPE_NET_OPTION_NAME_MATCHCASE: - case sfp.NODE_TYPE_NET_OPTION_NAME_MEDIA: - case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD: - case sfp.NODE_TYPE_NET_OPTION_NAME_MP4: - case sfp.NODE_TYPE_NET_OPTION_NAME_NOOP: - case sfp.NODE_TYPE_NET_OPTION_NAME_OBJECT: - case sfp.NODE_TYPE_NET_OPTION_NAME_OTHER: - case sfp.NODE_TYPE_NET_OPTION_NAME_PING: - case sfp.NODE_TYPE_NET_OPTION_NAME_POPUNDER: - case sfp.NODE_TYPE_NET_OPTION_NAME_POPUP: + return 'variable'; + case sfp.NODE_TYPE_NET_PATTERN_PART: + return 'variable'; + case sfp.NODE_TYPE_NET_PATTERN_PART_SPECIAL: + return 'keyword strong'; + case sfp.NODE_TYPE_NET_PATTERN_PART_UNICODE: + return 'variable unicode'; + case sfp.NODE_TYPE_NET_PATTERN_LEFT_HNANCHOR: + case sfp.NODE_TYPE_NET_PATTERN_LEFT_ANCHOR: + case sfp.NODE_TYPE_NET_PATTERN_RIGHT_ANCHOR: + case sfp.NODE_TYPE_NET_OPTION_NAME_NOT: + return 'keyword strong'; + case sfp.NODE_TYPE_NET_OPTIONS_ANCHOR: + case sfp.NODE_TYPE_NET_OPTION_SEPARATOR: + mode.lastNetOptionType = 0; + return 'def strong'; + case sfp.NODE_TYPE_NET_OPTION_NAME_UNKNOWN: + mode.lastNetOptionType = 0; + return 'error'; + case sfp.NODE_TYPE_NET_OPTION_NAME_1P: + case sfp.NODE_TYPE_NET_OPTION_NAME_STRICT1P: + case sfp.NODE_TYPE_NET_OPTION_NAME_3P: + case sfp.NODE_TYPE_NET_OPTION_NAME_STRICT3P: + case sfp.NODE_TYPE_NET_OPTION_NAME_ALL: + case sfp.NODE_TYPE_NET_OPTION_NAME_BADFILTER: + case sfp.NODE_TYPE_NET_OPTION_NAME_CNAME: + case sfp.NODE_TYPE_NET_OPTION_NAME_CSP: + case sfp.NODE_TYPE_NET_OPTION_NAME_CSS: + case sfp.NODE_TYPE_NET_OPTION_NAME_DENYALLOW: + case sfp.NODE_TYPE_NET_OPTION_NAME_DOC: + case sfp.NODE_TYPE_NET_OPTION_NAME_EHIDE: + case sfp.NODE_TYPE_NET_OPTION_NAME_EMPTY: + case sfp.NODE_TYPE_NET_OPTION_NAME_FONT: + case sfp.NODE_TYPE_NET_OPTION_NAME_FRAME: + case sfp.NODE_TYPE_NET_OPTION_NAME_FROM: + case sfp.NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK: + case sfp.NODE_TYPE_NET_OPTION_NAME_GHIDE: + case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: + case sfp.NODE_TYPE_NET_OPTION_NAME_IMAGE: + case sfp.NODE_TYPE_NET_OPTION_NAME_IMPORTANT: + case sfp.NODE_TYPE_NET_OPTION_NAME_INLINEFONT: + case sfp.NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT: + case sfp.NODE_TYPE_NET_OPTION_NAME_MATCHCASE: + case sfp.NODE_TYPE_NET_OPTION_NAME_MEDIA: + case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD: + case sfp.NODE_TYPE_NET_OPTION_NAME_MP4: + case sfp.NODE_TYPE_NET_OPTION_NAME_NOOP: + case sfp.NODE_TYPE_NET_OPTION_NAME_OBJECT: + case sfp.NODE_TYPE_NET_OPTION_NAME_OTHER: + case sfp.NODE_TYPE_NET_OPTION_NAME_PING: + case sfp.NODE_TYPE_NET_OPTION_NAME_POPUNDER: + case sfp.NODE_TYPE_NET_OPTION_NAME_POPUP: + case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: + case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: + case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: + case sfp.NODE_TYPE_NET_OPTION_NAME_SCRIPT: + case sfp.NODE_TYPE_NET_OPTION_NAME_SHIDE: + case sfp.NODE_TYPE_NET_OPTION_NAME_TO: + case sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: + case sfp.NODE_TYPE_NET_OPTION_NAME_XHR: + case sfp.NODE_TYPE_NET_OPTION_NAME_WEBRTC: + case sfp.NODE_TYPE_NET_OPTION_NAME_WEBSOCKET: + mode.lastNetOptionType = nodeType; + return 'def'; + case sfp.NODE_TYPE_NET_OPTION_ASSIGN: + return 'def'; + case sfp.NODE_TYPE_NET_OPTION_VALUE: + if ( mode.astWalker.canGoDown() ) { break; } + switch ( mode.lastNetOptionType ) { case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: - case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: - case sfp.NODE_TYPE_NET_OPTION_NAME_SCRIPT: - case sfp.NODE_TYPE_NET_OPTION_NAME_SHIDE: - case sfp.NODE_TYPE_NET_OPTION_NAME_TO: - case sfp.NODE_TYPE_NET_OPTION_NAME_XHR: - case sfp.NODE_TYPE_NET_OPTION_NAME_WEBRTC: - case sfp.NODE_TYPE_NET_OPTION_NAME_WEBSOCKET: - lastNetOptionType = nodeType; - return 'def'; - case sfp.NODE_TYPE_NET_OPTION_ASSIGN: - return 'def'; - case sfp.NODE_TYPE_NET_OPTION_VALUE: - if ( astWalker.canGoDown() ) { break; } - switch ( lastNetOptionType ) { - case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT: - case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE: - return redirectTokenStyle(); - default: - break; - } - return 'value'; - case sfp.NODE_TYPE_OPTION_VALUE_NOT: - return 'keyword strong'; - case sfp.NODE_TYPE_OPTION_VALUE_DOMAIN: - return 'value'; - case sfp.NODE_TYPE_OPTION_VALUE_SEPARATOR: - return 'def'; + return redirectTokenStyle(mode); default: break; + } + return 'value'; + case sfp.NODE_TYPE_OPTION_VALUE_NOT: + return 'keyword strong'; + case sfp.NODE_TYPE_OPTION_VALUE_DOMAIN: + return 'value'; + case sfp.NODE_TYPE_OPTION_VALUE_SEPARATOR: + return 'def'; + default: + break; } return '+'; }; - self.addEventListener('trustedSource', ev => { - astParser.options.trustedSource = ev.detail; - }); - - self.addEventListener('trustedScriptletTokens', ev => { - astParser.options.trustedScriptletTokens = ev.detail; - }); + class ModeState { + constructor() { + this.astParser = new sfp.AstFilterParser({ + interactive: true, + nativeCssHas: vAPI.webextFlavor.env.includes('native_css_has'), + }); + this.astWalker = this.astParser.getWalker(); + this.currentWalkerNode = 0; + this.lastNetOptionType = 0; + self.addEventListener('trustedSource', ev => { + const { trusted } = ev.detail; + this.astParser.options.trustedSource = trusted; + }); + self.addEventListener('trustedScriptletTokens', ev => { + this.astParser.options.trustedScriptletTokens = ev.detail; + }); + } + } - return { - lineComment: '!', - token: function(stream) { + return { + state: null, + startState() { + if ( this.state === null ) { + this.state = new ModeState(); + } + return this.state; + }, + copyState(other) { + return other; + }, + token(stream, state) { if ( stream.sol() ) { - astParser.parse(stream.string); - if ( astParser.getFlags(sfp.AST_FLAG_UNSUPPORTED) !== 0 ) { + state.astParser.parse(stream.string); + if ( state.astParser.getFlags(sfp.AST_FLAG_UNSUPPORTED) !== 0 ) { stream.skipToEnd(); return 'error'; } - if ( astParser.getType() === sfp.AST_TYPE_NONE ) { + if ( state.astParser.getType() === sfp.AST_TYPE_NONE ) { stream.skipToEnd(); return 'comment'; } - currentWalkerNode = astWalker.reset(); - } else if ( nodeHasError() ) { - currentWalkerNode = astWalker.right(); + state.currentWalkerNode = state.astWalker.reset(); + } else if ( nodeHasError(state) ) { + state.currentWalkerNode = state.astWalker.right(); } else { - currentWalkerNode = astWalker.next(); + state.currentWalkerNode = state.astWalker.next(); } let style = ''; - while ( currentWalkerNode !== 0 ) { - style = colorFromAstNode(stream); + while ( state.currentWalkerNode !== 0 ) { + style = colorFromAstNode(state, stream); if ( style !== '+' ) { break; } - currentWalkerNode = astWalker.next(); + state.currentWalkerNode = state.astWalker.next(); } if ( style === '+' ) { stream.skipToEnd(); return null; } - stream.pos = astParser.getNodeStringEnd(currentWalkerNode); - if ( astParser.isNetworkFilter() ) { + stream.pos = state.astParser.getNodeStringEnd(state.currentWalkerNode); + if ( state.astParser.isNetworkFilter() ) { return style ? `line-cm-net ${style}` : 'line-cm-net'; } - if ( astParser.isExtendedFilter() ) { + if ( state.astParser.isExtendedFilter() ) { let flavor = ''; - if ( astParser.isCosmeticFilter() ) { + if ( state.astParser.isCosmeticFilter() ) { flavor = 'line-cm-ext-dom'; - } else if ( astParser.isScriptletFilter() ) { + } else if ( state.astParser.isScriptletFilter() ) { flavor = 'line-cm-ext-js'; - } else if ( astParser.isHtmlFilter() ) { + } else if ( state.astParser.isHtmlFilter() ) { flavor = 'line-cm-ext-html'; } if ( flavor !== '' ) { @@ -278,9 +289,11 @@ CodeMirror.defineMode('ubo-static-filtering', function() { style = style.trim(); return style !== '' ? style : null; }, - parser: astParser, + lineComment: '!', }; -}); +})(); + +CodeMirror.defineMode('ubo-static-filtering', ( ) => uBOStaticFilteringMode); /******************************************************************************/ @@ -327,7 +340,7 @@ function initHints() { }); const proceduralOperatorNames = new Map( Array.from(sfp.proceduralOperatorTokens) - .filter(item => (item[1] & 0b01) !== 0) + .filter(item => (item[1] & 0b01) !== 0) ); const excludedHints = new Set([ 'genericblock', @@ -562,7 +575,7 @@ function initHints() { const getExtScriptletHints = function(cursor, line) { const beg = cursor.ch; - const matchLeft = /#\+\js\(([^,]*)$/.exec(line.slice(0, beg)); + const matchLeft = /#\+js\(([^,]*)$/.exec(line.slice(0, beg)); const matchRight = /^([^,)]*)/.exec(line.slice(beg)); if ( matchLeft === null || matchRight === null ) { return; } const hints = []; @@ -709,38 +722,38 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { if ( astParser.hasError() ) { let msg = 'Invalid filter'; switch ( astParser.astError ) { - case sfp.AST_ERROR_UNSUPPORTED: - msg = `${msg}: Unsupported filter syntax`; - break; - case sfp.AST_ERROR_REGEX: - msg = `${msg}: Bad regular expression`; - break; - case sfp.AST_ERROR_PATTERN: - msg = `${msg}: Bad pattern`; - break; - case sfp.AST_ERROR_DOMAIN_NAME: - msg = `${msg}: Bad domain name`; - break; - case sfp.AST_ERROR_OPTION_BADVALUE: - msg = `${msg}: Bad value assigned to a valid option`; - break; - case sfp.AST_ERROR_OPTION_DUPLICATE: - msg = `${msg}: Duplicate filter option`; - break; - case sfp.AST_ERROR_OPTION_UNKNOWN: - msg = `${msg}: Unsupported filter option`; - break; - case sfp.AST_ERROR_IF_TOKEN_UNKNOWN: - msg = `${msg}: Unknown preparsing token`; - break; - case sfp.AST_ERROR_UNTRUSTED_SOURCE: - msg = `${msg}: Filter requires trusted source`; - break; - default: - if ( astParser.isCosmeticFilter() && astParser.result.error ) { - msg = `${msg}: ${astParser.result.error}`; - } - break; + case sfp.AST_ERROR_UNSUPPORTED: + msg = `${msg}: Unsupported filter syntax`; + break; + case sfp.AST_ERROR_REGEX: + msg = `${msg}: Bad regular expression`; + break; + case sfp.AST_ERROR_PATTERN: + msg = `${msg}: Bad pattern`; + break; + case sfp.AST_ERROR_DOMAIN_NAME: + msg = `${msg}: Bad domain name`; + break; + case sfp.AST_ERROR_OPTION_BADVALUE: + msg = `${msg}: Bad value assigned to a valid option`; + break; + case sfp.AST_ERROR_OPTION_DUPLICATE: + msg = `${msg}: Duplicate filter option`; + break; + case sfp.AST_ERROR_OPTION_UNKNOWN: + msg = `${msg}: Unsupported filter option`; + break; + case sfp.AST_ERROR_IF_TOKEN_UNKNOWN: + msg = `${msg}: Unknown preparsing token`; + break; + case sfp.AST_ERROR_UNTRUSTED_SOURCE: + msg = `${msg}: Filter requires trusted source`; + break; + default: + if ( astParser.isCosmeticFilter() && astParser.result.error ) { + msg = `${msg}: ${astParser.result.error}`; + } + break; } return { lint: 'error', msg }; } @@ -877,6 +890,11 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { ifendifSet.add(lineHandle); ifendifSetChanged = true; } + } else if ( marker.dataset.lint === 'error' ) { + if ( marker.dataset.error !== 'y' ) { + marker.dataset.error = 'y'; + errorCount += 1; + } } if ( typeof details.msg !== 'string' || details.msg === '' ) { return; } const msgElem = qs$(marker, '.msg'); @@ -1083,7 +1101,8 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { }; self.addEventListener('trustedSource', ev => { - astParser.options.trustedSource = ev.detail; + const { trusted } = ev.detail; + astParser.options.trustedSource = trusted; }); self.addEventListener('trustedScriptletTokens', ev => { diff --git a/src/js/commands.js b/src/js/commands.js index 8fd6341..2f29b23 100644 --- a/src/js/commands.js +++ b/src/js/commands.js @@ -136,8 +136,11 @@ vAPI.commands.onCommand.addListener(async command => { // Tab-specific commands const tab = await vAPI.tabs.getCurrent(); if ( tab instanceof Object === false ) { return; } + switch ( command ) { case 'launch-element-picker': + if ( µb.userFiltersAreEnabled() === false ) { break; } + /* fall through */ case 'launch-element-zapper': { µb.epickerArgs.mouse = false; µb.elementPickerExec( @@ -168,6 +171,13 @@ vAPI.commands.onCommand.addListener(async command => { hostname: hostnameFromURI(µb.normalizeTabURL(tab.id, tab.url)), }); break; + case 'toggle-javascript': + µb.toggleHostnameSwitch({ + name: 'no-scripting', + hostname: hostnameFromURI(µb.normalizeTabURL(tab.id, tab.url)), + }); + vAPI.tabs.reload(tab.id); + break; default: break; } diff --git a/src/js/contentscript-extra.js b/src/js/contentscript-extra.js index 45c5262..34b0ef0 100644 --- a/src/js/contentscript-extra.js +++ b/src/js/contentscript-extra.js @@ -30,6 +30,9 @@ if ( /******************************************************************************/ const nonVisualElements = { + head: true, + link: true, + meta: true, script: true, style: true, }; @@ -196,28 +199,27 @@ class PSelectorOthersTask extends PSelectorTask { const toKeep = new Set(this.targets); const toDiscard = new Set(); const body = document.body; + const head = document.head; let discard = null; for ( let keep of this.targets ) { - while ( keep !== null && keep !== body ) { + while ( keep !== null && keep !== body && keep !== head ) { toKeep.add(keep); toDiscard.delete(keep); discard = keep.previousElementSibling; while ( discard !== null ) { - if ( - nonVisualElements[discard.localName] !== true && - toKeep.has(discard) === false - ) { - toDiscard.add(discard); + if ( nonVisualElements[discard.localName] !== true ) { + if ( toKeep.has(discard) === false ) { + toDiscard.add(discard); + } } discard = discard.previousElementSibling; } discard = keep.nextElementSibling; while ( discard !== null ) { - if ( - nonVisualElements[discard.localName] !== true && - toKeep.has(discard) === false - ) { - toDiscard.add(discard); + if ( nonVisualElements[discard.localName] !== true ) { + if ( toKeep.has(discard) === false ) { + toDiscard.add(discard); + } } discard = discard.nextElementSibling; } @@ -240,6 +242,36 @@ class PSelectorOthersTask extends PSelectorTask { } } +class PSelectorShadowTask extends PSelectorTask { + constructor(task) { + super(); + this.selector = task[1]; + } + transpose(node, output) { + const root = this.openOrClosedShadowRoot(node); + if ( root === null ) { return; } + const nodes = root.querySelectorAll(this.selector); + output.push(...nodes); + } + get openOrClosedShadowRoot() { + if ( PSelectorShadowTask.openOrClosedShadowRoot !== undefined ) { + return PSelectorShadowTask.openOrClosedShadowRoot; + } + if ( typeof chrome === 'object' && chrome !== null ) { + if ( chrome.dom instanceof Object ) { + if ( typeof chrome.dom.openOrClosedShadowRoot === 'function' ) { + PSelectorShadowTask.openOrClosedShadowRoot = + chrome.dom.openOrClosedShadowRoot; + return PSelectorShadowTask.openOrClosedShadowRoot; + } + } + } + PSelectorShadowTask.openOrClosedShadowRoot = node => + node.openOrClosedShadowRoot || null; + return PSelectorShadowTask.openOrClosedShadowRoot; + } +} + // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 // Prepend `:scope ` if needed. class PSelectorSpathTask extends PSelectorTask { @@ -364,7 +396,6 @@ class PSelectorXpathTask extends PSelectorTask { class PSelector { constructor(o) { - this.raw = o.raw; this.selector = o.selector; this.tasks = []; const tasks = []; @@ -435,6 +466,7 @@ PSelector.prototype.operatorToTaskMap = new Map([ [ 'min-text-length', PSelectorMinTextLengthTask ], [ 'not', PSelectorIfNotTask ], [ 'others', PSelectorOthersTask ], + [ 'shadow', PSelectorShadowTask ], [ 'spath', PSelectorSpathTask ], [ 'upward', PSelectorUpwardTask ], [ 'watch-attr', PSelectorWatchAttrs ], diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 8f3a4cf..95dbdb6 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -462,28 +462,6 @@ vAPI.SafeAnimationFrame = class { /******************************************************************************/ /******************************************************************************/ -/******************************************************************************/ - -vAPI.injectScriptlet = function(doc, text) { - if ( !doc ) { return; } - let script, url; - try { - const blob = new self.Blob([ text ], { type: 'text/javascript; charset=utf-8' }); - url = self.URL.createObjectURL(blob); - script = doc.createElement('script'); - script.async = false; - script.src = url; - (doc.head || doc.documentElement || doc).appendChild(script); - } catch (ex) { - } - if ( url ) { - if ( script ) { script.remove(); } - self.URL.revokeObjectURL(url); - } -}; - -/******************************************************************************/ -/******************************************************************************/ /******************************************************************************* The DOM filterer is the heart of uBO's cosmetic filtering. @@ -1298,7 +1276,6 @@ vAPI.DOMFilterer = class { const { noSpecificCosmeticFiltering, noGenericCosmeticFiltering, - scriptletDetails, } = response; vAPI.noSpecificCosmeticFiltering = noSpecificCosmeticFiltering; @@ -1320,14 +1297,6 @@ vAPI.DOMFilterer = class { vAPI.userStylesheet.apply(); } - if ( scriptletDetails && typeof self.uBO_scriptletsInjected !== 'string' ) { - self.uBO_scriptletsInjected = scriptletDetails.filters; - if ( scriptletDetails.mainWorld ) { - vAPI.injectScriptlet(document, scriptletDetails.mainWorld); - vAPI.injectedScripts = scriptletDetails.mainWorld; - } - } - if ( vAPI.domSurveyor ) { if ( Array.isArray(cfeDetails.genericCosmeticHashes) ) { vAPI.domSurveyor.addHashes(cfeDetails.genericCosmeticHashes); diff --git a/src/js/contextmenu.js b/src/js/contextmenu.js index abf0582..788b62b 100644 --- a/src/js/contextmenu.js +++ b/src/js/contextmenu.js @@ -200,7 +200,11 @@ let currentBits = 0; const update = function(tabId = undefined) { let newBits = 0; - if ( µb.userSettings.contextMenuEnabled && tabId !== undefined ) { + if ( + µb.userSettings.contextMenuEnabled && + µb.userFiltersAreEnabled() && + tabId !== undefined + ) { const pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore && pageStore.getNetFilteringSwitch() ) { if ( pageStore.shouldApplySpecificCosmeticFilters(0) ) { diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index f4782bc..9ce1bf4 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -221,7 +221,7 @@ const reEscapeSequence = /\\([0-9A-Fa-f]+ |.)/g; // Generic filters can only be enforced once the main document is loaded. // Specific filers can be enforced before the main document is loaded. -const FilterContainer = function() { +const CosmeticFilteringEngine = function() { this.reSimpleHighGeneric = /^(?:[a-z]*\[[^\]]+\]|\S+)$/; this.selectorCache = new Map(); @@ -269,7 +269,7 @@ const FilterContainer = function() { // Reset all, thus reducing to a minimum memory footprint of the context. -FilterContainer.prototype.reset = function() { +CosmeticFilteringEngine.prototype.reset = function() { this.frozen = false; this.acceptedCount = 0; this.discardedCount = 0; @@ -292,12 +292,12 @@ FilterContainer.prototype.reset = function() { this.highlyGeneric.complex.str = ''; this.highlyGeneric.complex.mru.reset(); - this.selfieVersion = 1; + this.selfieVersion = 2; }; /******************************************************************************/ -FilterContainer.prototype.freeze = function() { +CosmeticFilteringEngine.prototype.freeze = function() { this.duplicateBuster.clear(); this.specificFilters.collectGarbage(); @@ -311,7 +311,7 @@ FilterContainer.prototype.freeze = function() { /******************************************************************************/ -FilterContainer.prototype.compile = function(parser, writer) { +CosmeticFilteringEngine.prototype.compile = function(parser, writer) { if ( parser.hasOptions() === false ) { this.compileGenericSelector(parser, writer); return true; @@ -337,7 +337,7 @@ FilterContainer.prototype.compile = function(parser, writer) { /******************************************************************************/ -FilterContainer.prototype.compileGenericSelector = function(parser, writer) { +CosmeticFilteringEngine.prototype.compileGenericSelector = function(parser, writer) { if ( parser.isException() ) { this.compileGenericUnhideSelector(parser, writer); } else { @@ -347,7 +347,7 @@ FilterContainer.prototype.compileGenericSelector = function(parser, writer) { /******************************************************************************/ -FilterContainer.prototype.compileGenericHideSelector = function( +CosmeticFilteringEngine.prototype.compileGenericHideSelector = function( parser, writer ) { @@ -403,7 +403,7 @@ FilterContainer.prototype.compileGenericHideSelector = function( /******************************************************************************/ -FilterContainer.prototype.compileGenericUnhideSelector = function( +CosmeticFilteringEngine.prototype.compileGenericUnhideSelector = function( parser, writer ) { @@ -432,7 +432,7 @@ FilterContainer.prototype.compileGenericUnhideSelector = function( /******************************************************************************/ -FilterContainer.prototype.compileSpecificSelector = function( +CosmeticFilteringEngine.prototype.compileSpecificSelector = function( parser, hostname, not, @@ -471,7 +471,7 @@ FilterContainer.prototype.compileSpecificSelector = function( /******************************************************************************/ -FilterContainer.prototype.fromCompiledContent = function(reader, options) { +CosmeticFilteringEngine.prototype.fromCompiledContent = function(reader, options) { if ( options.skipCosmetic ) { this.skipCompiledContent(reader, 'SPECIFIC'); this.skipCompiledContent(reader, 'GENERIC'); @@ -560,7 +560,7 @@ FilterContainer.prototype.fromCompiledContent = function(reader, options) { /******************************************************************************/ -FilterContainer.prototype.skipCompiledContent = function(reader, sectionId) { +CosmeticFilteringEngine.prototype.skipCompiledContent = function(reader, sectionId) { reader.select(`COSMETIC_FILTERS:${sectionId}`); while ( reader.next() ) { this.acceptedCount += 1; @@ -570,21 +570,23 @@ FilterContainer.prototype.skipCompiledContent = function(reader, sectionId) { /******************************************************************************/ -FilterContainer.prototype.toSelfie = function() { +CosmeticFilteringEngine.prototype.toSelfie = function() { return { version: this.selfieVersion, acceptedCount: this.acceptedCount, discardedCount: this.discardedCount, specificFilters: this.specificFilters.toSelfie(), - lowlyGeneric: Array.from(this.lowlyGeneric), - highSimpleGenericHideArray: Array.from(this.highlyGeneric.simple.dict), - highComplexGenericHideArray: Array.from(this.highlyGeneric.complex.dict), + lowlyGeneric: this.lowlyGeneric, + highSimpleGenericHideDict: this.highlyGeneric.simple.dict, + highSimpleGenericHideStr: this.highlyGeneric.simple.str, + highComplexGenericHideDict: this.highlyGeneric.complex.dict, + highComplexGenericHideStr: this.highlyGeneric.complex.str, }; }; /******************************************************************************/ -FilterContainer.prototype.fromSelfie = function(selfie) { +CosmeticFilteringEngine.prototype.fromSelfie = function(selfie) { if ( selfie.version !== this.selfieVersion ) { throw new Error( `cosmeticFilteringEngine: mismatched selfie version, ${selfie.version}, expected ${this.selfieVersion}` @@ -593,17 +595,17 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.acceptedCount = selfie.acceptedCount; this.discardedCount = selfie.discardedCount; this.specificFilters.fromSelfie(selfie.specificFilters); - this.lowlyGeneric = new Map(selfie.lowlyGeneric); - this.highlyGeneric.simple.dict = new Set(selfie.highSimpleGenericHideArray); - this.highlyGeneric.simple.str = selfie.highSimpleGenericHideArray.join(',\n'); - this.highlyGeneric.complex.dict = new Set(selfie.highComplexGenericHideArray); - this.highlyGeneric.complex.str = selfie.highComplexGenericHideArray.join(',\n'); + this.lowlyGeneric = selfie.lowlyGeneric; + this.highlyGeneric.simple.dict = selfie.highSimpleGenericHideDict; + this.highlyGeneric.simple.str = selfie.highSimpleGenericHideStr; + this.highlyGeneric.complex.dict = selfie.highComplexGenericHideDict; + this.highlyGeneric.complex.str = selfie.highComplexGenericHideStr; this.frozen = true; }; /******************************************************************************/ -FilterContainer.prototype.addToSelectorCache = function(details) { +CosmeticFilteringEngine.prototype.addToSelectorCache = function(details) { const hostname = details.hostname; if ( typeof hostname !== 'string' || hostname === '' ) { return; } const selectors = details.selectors; @@ -621,7 +623,7 @@ FilterContainer.prototype.addToSelectorCache = function(details) { /******************************************************************************/ -FilterContainer.prototype.removeFromSelectorCache = function( +CosmeticFilteringEngine.prototype.removeFromSelectorCache = function( targetHostname = '*', type = undefined ) { @@ -644,7 +646,7 @@ FilterContainer.prototype.removeFromSelectorCache = function( /******************************************************************************/ -FilterContainer.prototype.pruneSelectorCacheAsync = function() { +CosmeticFilteringEngine.prototype.pruneSelectorCacheAsync = function() { if ( this.selectorCache.size <= this.selectorCacheCountMax ) { return; } const cache = this.selectorCache; const hostnames = Array.from(cache.keys()) @@ -658,7 +660,7 @@ FilterContainer.prototype.pruneSelectorCacheAsync = function() { /******************************************************************************/ -FilterContainer.prototype.disableSurveyor = function(details) { +CosmeticFilteringEngine.prototype.disableSurveyor = function(details) { const hostname = details.hostname; if ( typeof hostname !== 'string' || hostname === '' ) { return; } const cacheEntry = this.selectorCache.get(hostname); @@ -668,7 +670,7 @@ FilterContainer.prototype.disableSurveyor = function(details) { /******************************************************************************/ -FilterContainer.prototype.cssRuleFromProcedural = function(pfilter) { +CosmeticFilteringEngine.prototype.cssRuleFromProcedural = function(pfilter) { if ( pfilter.cssable !== true ) { return; } const { tasks, action } = pfilter; let mq, selector; @@ -699,7 +701,7 @@ FilterContainer.prototype.cssRuleFromProcedural = function(pfilter) { /******************************************************************************/ -FilterContainer.prototype.retrieveGenericSelectors = function(request) { +CosmeticFilteringEngine.prototype.retrieveGenericSelectors = function(request) { if ( this.lowlyGeneric.size === 0 ) { return; } if ( Array.isArray(request.hashes) === false ) { return; } if ( request.hashes.length === 0 ) { return; } @@ -757,7 +759,7 @@ FilterContainer.prototype.retrieveGenericSelectors = function(request) { /******************************************************************************/ -FilterContainer.prototype.retrieveSpecificSelectors = function( +CosmeticFilteringEngine.prototype.retrieveSpecificSelectors = function( request, options ) { @@ -928,7 +930,7 @@ FilterContainer.prototype.retrieveSpecificSelectors = function( if ( injectedCSS.length !== 0 ) { out.injectedCSS = injectedCSS.join('\n\n'); details.code = out.injectedCSS; - if ( request.tabId !== undefined ) { + if ( request.tabId !== undefined && options.dontInject !== true ) { vAPI.tabs.insertCSS(request.tabId, details); } } @@ -938,7 +940,7 @@ FilterContainer.prototype.retrieveSpecificSelectors = function( const networkFilters = []; if ( cacheEntry.retrieveNet(networkFilters) ) { details.code = `${networkFilters.join('\n')}\n{display:none!important;}`; - if ( request.tabId !== undefined ) { + if ( request.tabId !== undefined && options.dontInject !== true ) { vAPI.tabs.insertCSS(request.tabId, details); } } @@ -949,13 +951,13 @@ FilterContainer.prototype.retrieveSpecificSelectors = function( /******************************************************************************/ -FilterContainer.prototype.getFilterCount = function() { +CosmeticFilteringEngine.prototype.getFilterCount = function() { return this.acceptedCount - this.discardedCount; }; /******************************************************************************/ -FilterContainer.prototype.dump = function() { +CosmeticFilteringEngine.prototype.dump = function() { const lowlyGenerics = []; for ( const selectors of this.lowlyGeneric.values() ) { lowlyGenerics.push(...selectors.split(',\n')); @@ -976,7 +978,7 @@ FilterContainer.prototype.dump = function() { /******************************************************************************/ -const cosmeticFilteringEngine = new FilterContainer(); +const cosmeticFilteringEngine = new CosmeticFilteringEngine(); export default cosmeticFilteringEngine; diff --git a/src/js/dashboard.js b/src/js/dashboard.js index e82ec28..3ba16f0 100644 --- a/src/js/dashboard.js +++ b/src/js/dashboard.js @@ -25,7 +25,7 @@ import { dom, qs$ } from './dom.js'; /******************************************************************************/ -const discardUnsavedData = function(synchronous = false) { +function discardUnsavedData(synchronous = false) { const paneFrame = qs$('#iframe'); const paneWindow = paneFrame.contentWindow; if ( @@ -66,9 +66,9 @@ const discardUnsavedData = function(synchronous = false) { dom.on(document, 'click', onClick, true); }); -}; +} -const loadDashboardPanel = function(pane, first) { +function loadDashboardPanel(pane, first) { const tabButton = qs$(`[data-pane="${pane}"]`); if ( tabButton === null || dom.cl.has(tabButton, 'selected') ) { return; } const loadPane = ( ) => { @@ -76,8 +76,12 @@ const loadDashboardPanel = function(pane, first) { dom.cl.remove('.tabButton.selected', 'selected'); dom.cl.add(tabButton, 'selected'); tabButton.scrollIntoView(); - qs$('#iframe').contentWindow.location.replace(pane); + const iframe = qs$('#iframe'); + iframe.contentWindow.location.replace(pane); if ( pane !== 'no-dashboard.html' ) { + iframe.addEventListener('load', ( ) => { + qs$('.wikilink').href = iframe.contentWindow.wikilink || ''; + }, { once: true }); vAPI.localStorage.setItem('dashboardLastVisitedPane', pane); } }; @@ -91,11 +95,11 @@ const loadDashboardPanel = function(pane, first) { if ( status === false ) { return; } loadPane(); }); -}; +} -const onTabClickHandler = function(ev) { +function onTabClickHandler(ev) { loadDashboardPanel(dom.attr(ev.target, 'data-pane')); -}; +} if ( self.location.hash.slice(1) === 'no-dashboard.html' ) { dom.cl.add(dom.body, 'noDashboard'); diff --git a/src/js/devtools.js b/src/js/devtools.js index 93b2697..0763b0b 100644 --- a/src/js/devtools.js +++ b/src/js/devtools.js @@ -187,6 +187,28 @@ vAPI.messaging.send('dashboard', { dom.attr(button, 'disabled', null); }); }); + dom.attr('#cfe-benchmark', 'disabled', null); + dom.on('#cfe-benchmark', 'click', ev => { + const button = ev.target; + dom.attr(button, 'disabled', ''); + vAPI.messaging.send('devTools', { + what: 'cfeBenchmark', + }).then(result => { + log(result); + dom.attr(button, 'disabled', null); + }); + }); + dom.attr('#sfe-benchmark', 'disabled', null); + dom.on('#sfe-benchmark', 'click', ev => { + const button = ev.target; + dom.attr(button, 'disabled', ''); + vAPI.messaging.send('devTools', { + what: 'sfeBenchmark', + }).then(result => { + log(result); + dom.attr(button, 'disabled', null); + }); + }); }); /******************************************************************************/ diff --git a/src/js/dom.js b/src/js/dom.js index 3d2f517..5c4d194 100644 --- a/src/js/dom.js +++ b/src/js/dom.js @@ -161,9 +161,9 @@ dom.cl = class { } } - static remove(target, name) { + static remove(target, ...names) { for ( const elem of normalizeTarget(target) ) { - elem.classList.remove(name); + elem.classList.remove(...names); } } diff --git a/src/js/dyna-rules.js b/src/js/dyna-rules.js index ea79742..69eef85 100644 --- a/src/js/dyna-rules.js +++ b/src/js/dyna-rules.js @@ -69,7 +69,6 @@ const thePanes = { let cleanEditToken = 0; let cleanEditText = ''; -let isCollapsed = false; /******************************************************************************/ @@ -104,7 +103,6 @@ let isCollapsed = false; qs$('.CodeMirror-merge-copybuttons-left'), { attributes: true, attributeFilter: [ 'title' ], subtree: true } ); - } /******************************************************************************/ @@ -142,21 +140,41 @@ const updateOverlay = (( ) => { stream.skipToEnd(); } }; - return function(filter) { - reFilter = typeof filter === 'string' && filter !== '' ? - new RegExp(filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi') : - undefined; + return function() { + const f = presentationState.filter; + reFilter = typeof f === 'string' && f !== '' + ? new RegExp(f.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi') + : undefined; return mode; }; })(); +const toggleOverlay = (( ) => { + let overlay = null; + + return function() { + if ( overlay !== null ) { + mergeView.leftOriginal().removeOverlay(overlay); + mergeView.editor().removeOverlay(overlay); + overlay = null; + } + if ( presentationState.filter !== '' ) { + overlay = updateOverlay(); + mergeView.leftOriginal().addOverlay(overlay); + mergeView.editor().addOverlay(overlay); + } + rulesToDoc(true); + savePresentationState(); + }; +})(); + /******************************************************************************/ // Incrementally update text in a CodeMirror editor for best user experience: // - Scroll position preserved // - Minimum amount of text updated -const rulesToDoc = function(clearHistory) { +function rulesToDoc(clearHistory) { const orig = thePanes.orig.doc; const edit = thePanes.edit.doc; orig.startOperation(); @@ -210,7 +228,7 @@ const rulesToDoc = function(clearHistory) { if ( mark.uboEllipsis !== true ) { continue; } mark.clear(); } - if ( isCollapsed ) { + if ( presentationState.isCollapsed ) { for ( let iline = 0, n = edit.lineCount(); iline < n; iline++ ) { if ( edit.getLine(iline) !== '...' ) { continue; } const mark = edit.markText( @@ -240,11 +258,11 @@ const rulesToDoc = function(clearHistory) { { line, ch: 0 }, (clientHeight - ldoc.defaultTextHeight()) / 2 ); -}; +} /******************************************************************************/ -const filterRules = function(key) { +function filterRules(key) { const filter = qs$('#ruleFilter input').value; const rules = thePanes[key].modified; if ( filter === '' ) { return rules; } @@ -254,11 +272,11 @@ const filterRules = function(key) { out.push(rule); } return out; -}; +} /******************************************************************************/ -const applyDiff = async function(permanent, toAdd, toRemove) { +async function applyDiff(permanent, toAdd, toRemove) { const details = await vAPI.messaging.send('dashboard', { what: 'modifyRuleset', permanent: permanent, @@ -268,7 +286,7 @@ const applyDiff = async function(permanent, toAdd, toRemove) { thePanes.orig.original = details.permanentRules; thePanes.edit.original = details.sessionRules; onPresentationChanged(); -}; +} /******************************************************************************/ @@ -327,14 +345,14 @@ function handleImportFilePicker() { /******************************************************************************/ -const startImportFilePicker = function() { +function startImportFilePicker() { const input = qs$('#importFilePicker'); // Reset to empty string, this will ensure an change event is properly // triggered if the user pick a file, even if it is the same as the last // one picked. input.value = ''; input.click(); -}; +} /******************************************************************************/ @@ -353,41 +371,25 @@ function exportUserRulesToFile() { /******************************************************************************/ -const onFilterChanged = (( ) => { +{ let timer; - let overlay = null; - let last = ''; - const process = function() { - timer = undefined; - if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; } - const filter = qs$('#ruleFilter input').value; - if ( filter === last ) { return; } - last = filter; - if ( overlay !== null ) { - mergeView.leftOriginal().removeOverlay(overlay); - mergeView.editor().removeOverlay(overlay); - overlay = null; - } - if ( filter !== '' ) { - overlay = updateOverlay(filter); - mergeView.leftOriginal().addOverlay(overlay); - mergeView.editor().addOverlay(overlay); - } - rulesToDoc(true); - }; - - return function() { + dom.on('#ruleFilter input', 'input', ( ) => { if ( timer !== undefined ) { self.cancelIdleCallback(timer); } - timer = self.requestIdleCallback(process, { timeout: 773 }); - }; -})(); + timer = self.requestIdleCallback(( ) => { + timer = undefined; + if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; } + const filter = qs$('#ruleFilter input').value; + if ( filter === presentationState.filter ) { return; } + presentationState.filter = filter; + toggleOverlay(); + }, { timeout: 773 }); + }); +} /******************************************************************************/ const onPresentationChanged = (( ) => { - let sortType = 1; - const reSwRule = /^([^/]+): ([^/ ]+) ([^ ]+)/; const reRule = /^([^ ]+) ([^/ ]+) ([^ ]+ [^ ]+)/; const reUrlRule = /^([^ ]+) ([^ ]+) ([^ ]+ [^ ]+)/; @@ -431,10 +433,10 @@ const onPresentationChanged = (( ) => { desHn = sortNormalizeHn(hostnameFromURI(match[2])); extra = match[3]; } - if ( sortType === 0 ) { + if ( presentationState.sortType === 0 ) { return { rule, token: `${type} ${srcHn} ${desHn} ${extra}` }; } - if ( sortType === 1 ) { + if ( presentationState.sortType === 1 ) { return { rule, token: `${srcHn} ${type} ${desHn} ${extra}` }; } return { rule, token: `${desHn} ${type} ${srcHn} ${extra}` }; @@ -452,7 +454,7 @@ const onPresentationChanged = (( ) => { }; const collapse = ( ) => { - if ( isCollapsed !== true ) { return; } + if ( presentationState.isCollapsed !== true ) { return; } const diffs = getDiffer().diff_main( thePanes.orig.modified.join('\n'), thePanes.edit.modified.join('\n') @@ -491,23 +493,31 @@ const onPresentationChanged = (( ) => { thePanes.edit.modified = rr; }; - return function(clearHistory) { + dom.on('#ruleFilter select', 'input', ev => { + presentationState.sortType = parseInt(ev.target.value, 10) || 0; + savePresentationState(); + onPresentationChanged(true); + }); + dom.on('#ruleFilter #diffCollapse', 'click', ev => { + presentationState.isCollapsed = dom.cl.toggle(ev.target, 'active'); + savePresentationState(); + onPresentationChanged(true); + }); + + return function onPresentationChanged(clearHistory) { const origPane = thePanes.orig; const editPane = thePanes.edit; origPane.modified = origPane.original.slice(); editPane.modified = editPane.original.slice(); - const select = qs$('#ruleFilter select'); - sortType = parseInt(select.value, 10); - if ( isNaN(sortType) ) { sortType = 1; } { const mode = origPane.doc.getMode(); - mode.sortType = sortType; + mode.sortType = presentationState.sortType; mode.setHostnameToDomainMap(hostnameToDomainMap); mode.setPSL(publicSuffixList); } { const mode = editPane.doc.getMode(); - mode.sortType = sortType; + mode.sortType = presentationState.sortType; mode.setHostnameToDomainMap(hostnameToDomainMap); mode.setPSL(publicSuffixList); } @@ -552,7 +562,7 @@ const onTextChanged = (( ) => { } }; - return function(now) { + return function onTextChanged(now) { if ( timer !== undefined ) { self.cancelIdleCallback(timer); } timer = now ? process() : self.requestIdleCallback(process, { timeout: 57 }); }; @@ -560,7 +570,7 @@ const onTextChanged = (( ) => { /******************************************************************************/ -const revertAllHandler = function() { +function revertAllHandler() { const toAdd = [], toRemove = []; const left = mergeView.leftOriginal(); const edit = mergeView.editor(); @@ -577,11 +587,11 @@ const revertAllHandler = function() { toRemove.push(removedLines.trim()); } applyDiff(false, toAdd.join('\n'), toRemove.join('\n')); -}; +} /******************************************************************************/ -const commitAllHandler = function() { +function commitAllHandler() { const toAdd = [], toRemove = []; const left = mergeView.leftOriginal(); const edit = mergeView.editor(); @@ -598,11 +608,11 @@ const commitAllHandler = function() { toRemove.push(removedLines.trim()); } applyDiff(true, toAdd.join('\n'), toRemove.join('\n')); -}; +} /******************************************************************************/ -const editSaveHandler = function() { +function editSaveHandler() { const editor = mergeView.editor(); const editText = editor.getValue().trim(); if ( editText === cleanEditText ) { @@ -619,7 +629,7 @@ const editSaveHandler = function() { } } applyDiff(false, toAdd.join(''), toRemove.join('')); -}; +} /******************************************************************************/ @@ -638,12 +648,43 @@ self.cloud.onPull = function(data, append) { /******************************************************************************/ +self.wikilink = 'https://github.com/gorhill/uBlock/wiki/Dashboard:-My-rules'; + self.hasUnsavedData = function() { return mergeView.editor().isClean(cleanEditToken) === false; }; /******************************************************************************/ +const presentationState = { + sortType: 0, + isCollapsed: false, + filter: '', +}; + +const savePresentationState = ( ) => { + vAPI.localStorage.setItem('dynaRulesPresentationState', presentationState); +}; + +vAPI.localStorage.getItemAsync('dynaRulesPresentationState').then(details => { + if ( details instanceof Object === false ) { return; } + if ( typeof details.sortType === 'number' ) { + presentationState.sortType = details.sortType; + qs$('#ruleFilter select').value = `${details.sortType}`; + } + if ( typeof details.isCollapsed === 'boolean' ) { + presentationState.isCollapsed = details.isCollapsed; + dom.cl.toggle('#ruleFilter #diffCollapse', 'active', details.isCollapsed); + } + if ( typeof details.filter === 'string' ) { + presentationState.filter = details.filter; + qs$('#ruleFilter input').value = details.filter; + toggleOverlay(); + } +}); + +/******************************************************************************/ + vAPI.messaging.send('dashboard', { what: 'getRules', }).then(details => { @@ -660,14 +701,6 @@ dom.on('#exportButton', 'click', exportUserRulesToFile); dom.on('#revertButton', 'click', revertAllHandler); dom.on('#commitButton', 'click', commitAllHandler); dom.on('#editSaveButton', 'click', editSaveHandler); -dom.on('#ruleFilter input', 'input', onFilterChanged); -dom.on('#ruleFilter select', 'input', ( ) => { - onPresentationChanged(true); -}); -dom.on('#ruleFilter #diffCollapse', 'click', ev => { - isCollapsed = dom.cl.toggle(ev.target, 'active'); - onPresentationChanged(true); -}); // https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs mergeView.editor().on('updateDiff', ( ) => { @@ -675,4 +708,3 @@ mergeView.editor().on('updateDiff', ( ) => { }); /******************************************************************************/ - diff --git a/src/js/epicker-ui.js b/src/js/epicker-ui.js index 49fc116..0c7ea1f 100644 --- a/src/js/epicker-ui.js +++ b/src/js/epicker-ui.js @@ -28,6 +28,7 @@ import './codemirror/ubo-static-filtering.js'; import { hostnameFromURI } from './uri-utils.js'; import punycode from '../lib/punycode.js'; import * as sfp from './static-filtering-parser.js'; +import { dom } from './dom.js'; /******************************************************************************/ /******************************************************************************/ @@ -46,7 +47,7 @@ const pickerRoot = document.documentElement; const dialog = $stor('aside'); let staticFilteringParser; -const svgRoot = $stor('svg'); +const svgRoot = $stor('svg#sea'); const svgOcean = svgRoot.children[0]; const svgIslands = svgRoot.children[1]; const NoPaths = 'M0 0'; @@ -594,8 +595,9 @@ const onStartMoving = (( ) => { let isTouch = false; let mx0 = 0, my0 = 0; let mx1 = 0, my1 = 0; - let r0 = 0, b0 = 0; - let rMax = 0, bMax = 0; + let pw = 0, ph = 0; + let dw = 0, dh = 0; + let cx0 = 0, cy0 = 0; let timer; const eatEvent = function(ev) { @@ -605,10 +607,22 @@ const onStartMoving = (( ) => { const move = ( ) => { timer = undefined; - const r1 = Math.min(Math.max(r0 - mx1 + mx0, 2), rMax); - const b1 = Math.min(Math.max(b0 - my1 + my0, 2), bMax); - dialog.style.setProperty('right', `${r1}px`); - dialog.style.setProperty('bottom', `${b1}px`); + const cx1 = cx0 + mx1 - mx0; + const cy1 = cy0 + my1 - my0; + if ( cx1 < pw / 2 ) { + dialog.style.setProperty('left', `${Math.max(cx1-dw/2,2)}px`); + dialog.style.removeProperty('right'); + } else { + dialog.style.removeProperty('left'); + dialog.style.setProperty('right', `${Math.max(pw-cx1-dw/2,2)}px`); + } + if ( cy1 < ph / 2 ) { + dialog.style.setProperty('top', `${Math.max(cy1-dh/2,2)}px`); + dialog.style.removeProperty('bottom'); + } else { + dialog.style.removeProperty('top'); + dialog.style.setProperty('bottom', `${Math.max(ph-cy1-dh/2,2)}px`); + } }; const moveAsync = ev => { @@ -635,7 +649,7 @@ const onStartMoving = (( ) => { eatEvent(ev); }; - return function(ev) { + return ev => { const target = dialog.querySelector('#move'); if ( ev.target !== target ) { return; } if ( dialog.classList.contains('moving') ) { return; } @@ -648,12 +662,13 @@ const onStartMoving = (( ) => { mx0 = ev.pageX; my0 = ev.pageY; } - const style = self.getComputedStyle(dialog); - r0 = parseInt(style.right, 10); - b0 = parseInt(style.bottom, 10); const rect = dialog.getBoundingClientRect(); - rMax = pickerRoot.clientWidth - 2 - rect.width ; - bMax = pickerRoot.clientHeight - 2 - rect.height; + dw = rect.width; + dh = rect.height; + cx0 = rect.x + dw / 2; + cy0 = rect.y + dh / 2; + pw = pickerRoot.clientWidth; + ph = pickerRoot.clientHeight; dialog.classList.add('moving'); if ( isTouch ) { self.addEventListener('touchmove', moveAsync, { capture: true }); @@ -787,14 +802,16 @@ const showDialog = function(details) { /******************************************************************************/ const pausePicker = function() { - pickerRoot.classList.add('paused'); + dom.cl.add(pickerRoot, 'paused'); + dom.cl.remove(pickerRoot, 'minimized'); svgListening(false); }; /******************************************************************************/ const unpausePicker = function() { - pickerRoot.classList.remove('paused', 'preview'); + dom.cl.remove(pickerRoot, 'paused', 'preview'); + dom.cl.add(pickerRoot, 'minimized'); pickerContentPort.postMessage({ what: 'togglePreview', state: false, @@ -806,7 +823,7 @@ const unpausePicker = function() { const startPicker = function() { self.addEventListener('keydown', onKeyPressed, true); - const svg = $stor('svg'); + const svg = $stor('svg#sea'); svg.addEventListener('click', onSvgClicked); svg.addEventListener('touchstart', onSvgTouch); svg.addEventListener('touchend', onSvgTouch); @@ -820,6 +837,14 @@ const startPicker = function() { $id('preview').addEventListener('click', onPreviewClicked); $id('create').addEventListener('click', onCreateClicked); $id('pick').addEventListener('click', onPickClicked); + $id('minimize').addEventListener('click', ( ) => { + if ( dom.cl.has(pickerRoot, 'paused') === false ) { + pausePicker(); + onCandidateChanged(); + } else { + dom.cl.toggle(pickerRoot, 'minimized'); + } + }); $id('quit').addEventListener('click', onQuitClicked); $id('move').addEventListener('mousedown', onStartMoving); $id('move').addEventListener('touchstart', onStartMoving); diff --git a/src/js/fa-icons.js b/src/js/fa-icons.js index 79968d0..5c249b9 100644 --- a/src/js/fa-icons.js +++ b/src/js/fa-icons.js @@ -32,6 +32,7 @@ export const faIconsInit = (( ) => { [ 'arrow-right', { viewBox: '0 0 1472 1558', path: 'm 1472,779 q 0,54 -37,91 l -651,651 q -39,37 -91,37 -51,0 -90,-37 l -75,-75 q -38,-38 -38,-91 0,-53 38,-91 L 821,971 H 117 Q 65,971 32.5,933.5 0,896 0,843 V 715 Q 0,662 32.5,624.5 65,587 117,587 H 821 L 528,293 q -38,-36 -38,-90 0,-54 38,-90 l 75,-75 q 38,-38 90,-38 53,0 91,38 l 651,651 q 37,35 37,90 z' } ], [ 'bar-chart', { viewBox: '0 0 2048 1536', path: 'm 640,768 0,512 -256,0 0,-512 256,0 z m 384,-512 0,1024 -256,0 0,-1024 256,0 z m 1024,1152 0,128 L 0,1536 0,0 l 128,0 0,1408 1920,0 z m -640,-896 0,768 -256,0 0,-768 256,0 z m 384,-384 0,1152 -256,0 0,-1152 256,0 z' } ], [ 'bolt', { viewBox: '0 0 896 1664', path: 'm 885.08696,438 q 18,20 7,44 l -540,1157 q -13,25 -42,25 -4,0 -14,-2 -17,-5 -25.5,-19 -8.5,-14 -4.5,-30 l 197,-808 -406,101 q -4,1 -12,1 -18,0 -31,-11 Q -3.9130435,881 1.0869565,857 L 202.08696,32 q 4,-14 16,-23 12,-9 28,-9 l 328,0 q 19,0 32,12.5 13,12.5 13,29.5 0,8 -5,18 l -171,463 396,-98 q 8,-2 12,-2 19,0 34,15 z' } ], + [ 'book', { viewBox: '0 0 1664 1536', path: 'm 1639.2625,350 c 25,36 32,83 18,129 l -275,906 c -25,85 -113,151 -199,151 H 260.26251 c -102,0 -211,-81 -248,-185 -16,-45 -16,-89 -2,-127 2,-20 6,-40 7,-64 1,-16 -8,-29 -6,-41 4,-24 25,-41 41,-68 30,-50 64,-131 75,-183 5,-19 -5,-41 0,-58 5,-19 24,-33 34,-51 27,-46 62,-135 67,-182 2,-21 -8,-44 -2,-60 7,-23 29,-33 44,-53 24,-33 64,-128 70,-181 2,-17 -8,-34 -5,-52 4,-19 28,-39 44,-62 42,-62 50,-199 177,-163 l -1,3 c 17,-4 34,-9 51,-9 h 761 c 47,0 89,21 114,56 26,36 32,83 18,130 l -274,906 c -47,154 -73,188 -200,188 H 156.26251 c -13,0 -29,3 -38,15 -8,12 -9,21 -1,43 20,58 89,70 144,70 h 923 c 37,0 80,-21 91,-57 l 300,-987 c 6,-19 6,-39 5,-57 23,9 44,23 59,43 z m -1064,2 c -6,18 4,32 22,32 h 608 c 17,0 36,-14 42,-32 l 21,-64 c 6,-18 -4,-32 -22,-32 H 638.26251 c -17,0 -36,14 -42,32 z m -83,256 c -6,18 4,32 22,32 h 608 c 17,0 36,-14 42,-32 l 21,-64 c 6,-18 -4,-32 -22,-32 H 555.26251 c -17,0 -36,14 -42,32 z' } ], [ 'clipboard', { viewBox: '0 0 1792 1792', path: 'm 768,1664 896,0 0,-640 -416,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -384,0 0,1152 z m 256,-1440 0,-64 q 0,-13 -9.5,-22.5 Q 1005,128 992,128 l -704,0 q -13,0 -22.5,9.5 Q 256,147 256,160 l 0,64 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 704,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 256,672 299,0 -299,-299 0,299 z m 512,128 0,672 q 0,40 -28,68 -28,28 -68,28 l -960,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-160 -544,0 Q 56,1536 28,1508 0,1480 0,1440 L 0,96 Q 0,56 28,28 56,0 96,0 l 1088,0 q 40,0 68,28 28,28 28,68 l 0,328 q 21,13 36,28 l 408,408 q 28,28 48,76 20,48 20,88 z' } ], [ 'clock-o', { viewBox: '0 0 1536 1536', path: 'm 896,416 v 448 q 0,14 -9,23 -9,9 -23,9 H 544 q -14,0 -23,-9 -9,-9 -9,-23 v -64 q 0,-14 9,-23 9,-9 23,-9 H 768 V 416 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 416,352 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z' } ], [ 'cloud-download', { viewBox: '0 0 1920 1408', path: 'm 1280,800 q 0,-14 -9,-23 -9,-9 -23,-9 l -224,0 0,-352 q 0,-13 -9.5,-22.5 Q 1005,384 992,384 l -192,0 q -13,0 -22.5,9.5 Q 768,403 768,416 l 0,352 -224,0 q -13,0 -22.5,9.5 -9.5,9.5 -9.5,22.5 0,14 9,23 l 352,352 q 9,9 23,9 14,0 23,-9 l 351,-351 q 10,-12 10,-24 z m 640,224 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z' } ], @@ -78,6 +79,7 @@ export const faIconsInit = (( ) => { [ 'unlink', { viewBox: '0 0 1664 1664', path: 'm 439,1271 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z m 169,41 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 v -320 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z M 384,1088 q 0,14 -9,23 -9,9 -23,9 H 32 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z m 1264,128 q 0,120 -85,203 l -147,146 q -83,83 -203,83 -121,0 -204,-85 L 675,1228 q -21,-21 -42,-56 l 239,-18 273,274 q 27,27 68,27.5 41,0.5 68,-26.5 l 147,-146 q 28,-28 28,-67 0,-40 -28,-68 l -274,-275 18,-239 q 35,21 56,42 l 336,336 q 84,86 84,204 z M 1031,492 792,510 519,236 q -28,-28 -68,-28 -39,0 -68,27 L 236,381 q -28,28 -28,67 0,40 28,68 l 274,274 -18,240 q -35,-21 -56,-42 L 100,652 Q 16,566 16,448 16,328 101,245 L 248,99 q 83,-83 203,-83 121,0 204,85 l 334,335 q 21,21 42,56 z m 633,84 q 0,14 -9,23 -9,9 -23,9 h -320 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z M 1120,32 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 V 32 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z m 407,151 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z' } ], [ 'unlock-alt', { viewBox: '0 0 1152 1536', path: 'm 1056,768 q 40,0 68,28 28,28 28,68 v 576 q 0,40 -28,68 -28,28 -68,28 H 96 Q 56,1536 28,1508 0,1480 0,1440 V 864 q 0,-40 28,-68 28,-28 68,-28 h 32 V 448 Q 128,263 259.5,131.5 391,0 576,0 761,0 892.5,131.5 1024,263 1024,448 q 0,26 -19,45 -19,19 -45,19 h -64 q -26,0 -45,-19 -19,-19 -19,-45 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 v 320 z' } ], [ 'upload-alt', { viewBox: '0 0 1664 1600', path: 'm 1280,1408 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 256,0 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 128,-224 v 320 q 0,40 -28,68 -28,28 -68,28 H 96 q -40,0 -68,-28 -28,-28 -28,-68 v -320 q 0,-40 28,-68 28,-28 68,-28 h 427 q 21,56 70.5,92 49.5,36 110.5,36 h 256 q 61,0 110.5,-36 49.5,-36 70.5,-92 h 427 q 40,0 68,28 28,28 28,68 z M 1339,536 q -17,40 -59,40 h -256 v 448 q 0,26 -19,45 -19,19 -45,19 H 704 q -26,0 -45,-19 -19,-19 -19,-45 V 576 H 384 q -42,0 -59,-40 -17,-39 14,-69 L 787,19 q 18,-19 45,-19 27,0 45,19 l 448,448 q 31,30 14,69 z' } ], + [ 'volume-up', { viewBox: '0 0 1664 1422', path: 'm 768,167 v 1088 c 0,35 -29,64 -64,64 -17,0 -33,-7 -45,-19 L 326,967 H 64 C 29,967 0,938 0,903 V 519 C 0,484 29,455 64,455 H 326 L 659,122 c 12,-12 28,-19 45,-19 35,0 64,29 64,64 z m 384,544 c 0,100 -61,197 -155,235 -8,4 -17,5 -25,5 -35,0 -64,-28 -64,-64 0,-76 116,-55 116,-176 0,-121 -116,-100 -116,-176 0,-36 29,-64 64,-64 8,0 17,1 25,5 94,37 155,135 155,235 z m 256,0 c 0,203 -122,392 -310,471 -8,3 -17,5 -25,5 -36,0 -65,-29 -65,-64 0,-28 16,-47 39,-59 27,-14 52,-26 76,-44 99,-72 157,-187 157,-309 0,-122 -58,-237 -157,-309 -24,-18 -49,-30 -76,-44 -23,-12 -39,-31 -39,-59 0,-35 29,-64 64,-64 9,0 18,2 26,5 188,79 310,268 310,471 z m 256,0 c 0,307 -183,585 -465,706 -8,3 -17,5 -26,5 -35,0 -64,-29 -64,-64 0,-29 15,-45 39,-59 14,-8 30,-13 45,-21 28,-15 56,-32 82,-51 164,-121 261,-312 261,-516 0,-204 -97,-395 -261,-516 -26,-19 -54,-36 -82,-51 -15,-8 -31,-13 -45,-21 -24,-14 -39,-30 -39,-59 0,-35 29,-64 64,-64 9,0 18,2 26,5 282,121 465,399 465,706 z' } ], [ 'zoom-in', { viewBox: '0 0 1664 1664', path: 'm 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 768 v 224 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 h -64 q -13,0 -22.5,-9.5 Q 640,1005 640,992 V 768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 H 640 V 416 q 0,-13 9.5,-22.5 Q 659,384 672,384 h 64 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 v 224 h 224 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ], [ 'zoom-out', { viewBox: '0 0 1664 1664', path: 'm 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 h 576 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ], // See /img/photon.svg diff --git a/src/js/filtering-context.js b/src/js/filtering-context.js index 5bc9aa1..6642050 100644 --- a/src/js/filtering-context.js +++ b/src/js/filtering-context.js @@ -135,7 +135,6 @@ export const FilteringContext = class { } this.tstamp = 0; this.realm = ''; - this.id = undefined; this.method = 0; this.itype = NO_TYPE; this.stype = undefined; @@ -175,7 +174,6 @@ export const FilteringContext = class { fromFilteringContext(other) { this.realm = other.realm; - this.id = other.id; this.type = other.type; this.method = other.method; this.url = other.url; diff --git a/src/js/hntrie.js b/src/js/hntrie.js index e8031a6..cc726db 100644 --- a/src/js/hntrie.js +++ b/src/js/hntrie.js @@ -445,28 +445,17 @@ class HNTrieContainer { }; } - serialize(encoder) { - if ( encoder instanceof Object ) { - return encoder.encode( - this.buf32.buffer, - this.buf32[CHAR1_SLOT] - ); - } - return Array.from( - new Uint32Array( - this.buf32.buffer, - 0, - this.buf32[CHAR1_SLOT] + 3 >>> 2 - ) + toSelfie() { + return this.buf32.subarray( + 0, + this.buf32[CHAR1_SLOT] + 3 >>> 2 ); } - unserialize(selfie, decoder) { + fromSelfie(selfie) { + if ( selfie instanceof Uint32Array === false ) { return false; } this.needle = ''; - const shouldDecode = typeof selfie === 'string'; - let byteLength = shouldDecode - ? decoder.decodeSize(selfie) - : selfie.length << 2; + let byteLength = selfie.length << 2; if ( byteLength === 0 ) { return false; } byteLength = roundToPageSize(byteLength); if ( this.wasmMemory !== null ) { @@ -477,14 +466,10 @@ class HNTrieContainer { this.buf = new Uint8Array(this.wasmMemory.buffer); this.buf32 = new Uint32Array(this.buf.buffer); } - } else if ( byteLength > this.buf.length ) { - this.buf = new Uint8Array(byteLength); - this.buf32 = new Uint32Array(this.buf.buffer); - } - if ( shouldDecode ) { - decoder.decode(selfie, this.buf.buffer); - } else { this.buf32.set(selfie); + } else { + this.buf32 = selfie; + this.buf = new Uint8Array(this.buf32.buffer); } // https://github.com/uBlockOrigin/uBlock-issues/issues/2925 this.buf[255] = 0; diff --git a/src/js/i18n.js b/src/js/i18n.js index 6302b35..18c7e14 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -29,11 +29,7 @@ const i18n = ? self.browser.i18n : self.chrome.i18n; -/******************************************************************************/ - -function i18n$(...args) { - return i18n.getMessage(...args); -} +const i18n$ = (...args) => i18n.getMessage(...args); /******************************************************************************/ @@ -295,21 +291,21 @@ if ( isBackgroundProcess !== true ) { const unicodeFlagToImageSrc = new Map([ [ '🇦🇱', 'al' ], [ '🇦🇷', 'ar' ], [ '🇦🇹', 'at' ], [ '🇧🇦', 'ba' ], - [ '🇧🇬', 'bg' ], [ '🇧🇷', 'br' ], [ '🇨🇦', 'ca' ], [ '🇨🇭', 'ch' ], - [ '🇨🇳', 'cn' ], [ '🇨🇴', 'co' ], [ '🇨🇾', 'cy' ], [ '🇨🇿', 'cz' ], - [ '🇩🇪', 'de' ], [ '🇩🇰', 'dk' ], [ '🇩🇿', 'dz' ], [ '🇪🇪', 'ee' ], - [ '🇪🇬', 'eg' ], [ '🇪🇸', 'es' ], [ '🇫🇮', 'fi' ], [ '🇫🇴', 'fo' ], - [ '🇫🇷', 'fr' ], [ '🇬🇷', 'gr' ], [ '🇭🇷', 'hr' ], [ '🇭🇺', 'hu' ], - [ '🇮🇩', 'id' ], [ '🇮🇱', 'il' ], [ '🇮🇳', 'in' ], [ '🇮🇷', 'ir' ], - [ '🇮🇸', 'is' ], [ '🇮🇹', 'it' ], [ '🇯🇵', 'jp' ], [ '🇰🇷', 'kr' ], - [ '🇰🇿', 'kz' ], [ '🇱🇰', 'lk' ], [ '🇱🇹', 'lt' ], [ '🇱🇻', 'lv' ], - [ '🇲🇦', 'ma' ], [ '🇲🇩', 'md' ], [ '🇲🇰', 'mk' ], [ '🇲🇽', 'mx' ], - [ '🇲🇾', 'my' ], [ '🇳🇱', 'nl' ], [ '🇳🇴', 'no' ], [ '🇳🇵', 'np' ], - [ '🇵🇱', 'pl' ], [ '🇵🇹', 'pt' ], [ '🇷🇴', 'ro' ], [ '🇷🇸', 'rs' ], - [ '🇷🇺', 'ru' ], [ '🇸🇦', 'sa' ], [ '🇸🇮', 'si' ], [ '🇸🇰', 'sk' ], - [ '🇸🇪', 'se' ], [ '🇸🇷', 'sr' ], [ '🇹🇭', 'th' ], [ '🇹🇯', 'tj' ], - [ '🇹🇼', 'tw' ], [ '🇹🇷', 'tr' ], [ '🇺🇦', 'ua' ], [ '🇺🇿', 'uz' ], - [ '🇻🇳', 'vn' ], [ '🇽🇰', 'xk' ], + [ '🇧🇪', 'be' ], [ '🇧🇬', 'bg' ], [ '🇧🇷', 'br' ], [ '🇨🇦', 'ca' ], + [ '🇨🇭', 'ch' ], [ '🇨🇳', 'cn' ], [ '🇨🇴', 'co' ], [ '🇨🇾', 'cy' ], + [ '🇨🇿', 'cz' ], [ '🇩🇪', 'de' ], [ '🇩🇰', 'dk' ], [ '🇩🇿', 'dz' ], + [ '🇪🇪', 'ee' ], [ '🇪🇬', 'eg' ], [ '🇪🇸', 'es' ], [ '🇫🇮', 'fi' ], + [ '🇫🇴', 'fo' ], [ '🇫🇷', 'fr' ], [ '🇬🇷', 'gr' ], [ '🇭🇷', 'hr' ], + [ '🇭🇺', 'hu' ], [ '🇮🇩', 'id' ], [ '🇮🇱', 'il' ], [ '🇮🇳', 'in' ], + [ '🇮🇷', 'ir' ], [ '🇮🇸', 'is' ], [ '🇮🇹', 'it' ], [ '🇯🇵', 'jp' ], + [ '🇰🇷', 'kr' ], [ '🇰🇿', 'kz' ], [ '🇱🇰', 'lk' ], [ '🇱🇹', 'lt' ], + [ '🇱🇻', 'lv' ], [ '🇲🇦', 'ma' ], [ '🇲🇩', 'md' ], [ '🇲🇰', 'mk' ], + [ '🇲🇽', 'mx' ], [ '🇲🇾', 'my' ], [ '🇳🇱', 'nl' ], [ '🇳🇴', 'no' ], + [ '🇳🇵', 'np' ], [ '🇵🇱', 'pl' ], [ '🇵🇹', 'pt' ], [ '🇷🇴', 'ro' ], + [ '🇷🇸', 'rs' ], [ '🇷🇺', 'ru' ], [ '🇸🇦', 'sa' ], [ '🇸🇮', 'si' ], + [ '🇸🇰', 'sk' ], [ '🇸🇪', 'se' ], [ '🇸🇷', 'sr' ], [ '🇹🇭', 'th' ], + [ '🇹🇯', 'tj' ], [ '🇹🇼', 'tw' ], [ '🇹🇷', 'tr' ], [ '🇺🇦', 'ua' ], + [ '🇺🇿', 'uz' ], [ '🇻🇳', 'vn' ], [ '🇽🇰', 'xk' ], ]); const reUnicodeFlags = new RegExp( Array.from(unicodeFlagToImageSrc).map(a => a[0]).join('|'), diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 177632e..b7aeb8e 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -21,6 +21,7 @@ 'use strict'; +import { broadcast } from './broadcast.js'; import { hostnameFromURI } from './uri-utils.js'; import { i18n, i18n$ } from './i18n.js'; import { dom, qs$, qsa$ } from './dom.js'; @@ -33,8 +34,9 @@ import { dom, qs$, qsa$ } from './dom.js'; const messaging = vAPI.messaging; const logger = self.logger = { ownerId: Date.now() }; const logDate = new Date(); -const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; +const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60; const loggerEntries = []; +let loggerEntryIdGenerator = 1; const COLUMN_TIMESTAMP = 0; const COLUMN_FILTER = 1; @@ -318,13 +320,11 @@ const LogEntry = function(details) { if ( details instanceof Object === false ) { return; } const receiver = LogEntry.prototype; for ( const prop in receiver ) { - if ( - details.hasOwnProperty(prop) && - details[prop] !== receiver[prop] - ) { - this[prop] = details[prop]; - } + if ( details.hasOwnProperty(prop) === false ) { continue; } + if ( details[prop] === receiver[prop] ) { continue; } + this[prop] = details[prop]; } + this.id = `${loggerEntryIdGenerator++}`; if ( details.aliasURL !== undefined ) { this.aliased = true; } @@ -345,7 +345,6 @@ LogEntry.prototype = { docHostname: '', domain: '', filter: undefined, - id: '', method: '', realm: '', tabDomain: '', @@ -368,7 +367,7 @@ const createLogSeparator = function(details, text) { separator.textContent = ''; const textContent = []; - logDate.setTime(separator.tstamp - logDateTimezoneOffset); + logDate.setTime((separator.tstamp - logDateTimezoneOffset) * 1000); textContent.push( // cell 0 padTo2(logDate.getUTCHours()) + ':' + @@ -377,7 +376,7 @@ const createLogSeparator = function(details, text) { // cell 1 text ); - separator.textContent = textContent.join('\t'); + separator.textContent = textContent.join('\x1F'); if ( details.voided ) { separator.voided = true; @@ -464,7 +463,7 @@ const parseLogEntry = function(details) { const textContent = []; // Cell 0 - logDate.setTime(details.tstamp - logDateTimezoneOffset); + logDate.setTime((details.tstamp - logDateTimezoneOffset) * 1000); textContent.push( padTo2(logDate.getUTCHours()) + ':' + padTo2(logDate.getUTCMinutes()) + ':' + @@ -474,7 +473,13 @@ const parseLogEntry = function(details) { // Cell 1 if ( details.realm === 'message' ) { textContent.push(details.text); - entry.textContent = textContent.join('\t'); + if ( details.type ) { + textContent.push(details.type); + } + if ( details.keywords ) { + textContent.push(...details.keywords); + } + entry.textContent = textContent.join('\x1F') + '\x1F'; return entry; } @@ -545,7 +550,7 @@ const parseLogEntry = function(details) { textContent.push(`aliasURL=${details.aliasURL}`); } - entry.textContent = textContent.join('\t'); + entry.textContent = textContent.join('\x1F'); return entry; }; @@ -744,7 +749,7 @@ const viewPort = (( ) => { vwEntry.logEntry = details; - const cells = details.textContent.split('\t'); + const cells = details.textContent.split('\x1F'); const div = dom.clone(vwLogEntryTemplate); const divcl = div.classList; let span; @@ -863,7 +868,7 @@ const viewPort = (( ) => { // Alias URL (CNAME, etc.) if ( cells.length > 8 ) { - const pos = details.textContent.lastIndexOf('\taliasURL='); + const pos = details.textContent.lastIndexOf('\x1FaliasURL='); if ( pos !== -1 ) { dom.attr(div, 'data-aliasid', details.id); } @@ -1336,9 +1341,7 @@ dom.on(document, 'keydown', ev => { if ( reSchemeOnly.test(value) ) { value = `|${value}`; } else { - if ( value.endsWith('/') ) { - value += '*'; - } else if ( /[/?]/.test(value) === false ) { + if ( /[/?]/.test(value) === false ) { value += '^'; } value = `||${value}`; @@ -1410,7 +1413,8 @@ dom.on(document, 'keydown', ev => { // Create static filter if ( target.id === 'createStaticFilter' ) { ev.stopPropagation(); - const value = staticFilterNode().value; + const value = staticFilterNode().value + .replace(/^((?:@@)?\/.+\/)(\$|$)/, '$1*$2'); // Avoid duplicates if ( createdStaticFilters.hasOwnProperty(value) ) { return; } createdStaticFilters[value] = true; @@ -1620,9 +1624,10 @@ dom.on(document, 'keydown', ev => { const aliasURLFromID = function(id) { if ( id === '' ) { return ''; } for ( const entry of loggerEntries ) { - if ( entry.id !== id || entry.aliased ) { continue; } - const fields = entry.textContent.split('\t'); - return fields[COLUMN_URL] || ''; + if ( entry.id !== id ) { continue; } + const match = /\baliasURL=([^\x1F]+)/.exec(entry.textContent); + if ( match === null ) { return ''; } + return match[1]; } return ''; }; @@ -2005,8 +2010,12 @@ dom.on(document, 'keydown', ev => { }; const toggleOn = async function(ev) { - targetRow = ev.target.closest('.canDetails'); - if ( targetRow === null ) { return; } + const clickedRow = ev.target.closest('.canDetails'); + if ( clickedRow === null ) { return; } + if ( clickedRow === targetRow ) { + return toggleOff(); + } + targetRow = clickedRow; ev.stopPropagation(); targetTabId = tabIdFromAttribute(targetRow); targetType = targetRow.children[COLUMN_TYPE].textContent.trim() || ''; @@ -2052,12 +2061,30 @@ dom.on(document, 'keydown', ev => { } }); - dom.on( - '#netInspector', - 'click', - '.canDetails > span:not(:nth-of-type(4)):not(:nth-of-type(8))', - ev => { toggleOn(ev); } - ); + // This is to detect text selection, in which case the click won't be + // interpreted as a request to open the details of the entry. + let selectionAtMouseDown; + let selectionAtTimer; + dom.on('#netInspector', 'mousedown', '.canDetails *', ev => { + if ( ev.button !== 0 ) { return; } + if ( selectionAtMouseDown !== undefined ) { return; } + selectionAtMouseDown = document.getSelection().toString(); + }); + + dom.on('#netInspector', 'click', '.canDetails *', ev => { + if ( ev.button !== 0 ) { return; } + if ( selectionAtTimer !== undefined ) { + clearTimeout(selectionAtTimer); + } + selectionAtTimer = setTimeout(( ) => { + selectionAtTimer = undefined; + const selectionAsOfNow = document.getSelection().toString(); + const selectionHasChanged = selectionAsOfNow !== selectionAtMouseDown; + selectionAtMouseDown = undefined; + if ( selectionHasChanged && selectionAsOfNow !== '' ) { return; } + toggleOn(ev); + }, 333); + }); dom.on( '#netInspector', @@ -2149,16 +2176,12 @@ const rowFilterer = (( ) => { filters = builtinFilters.concat(userFilters); }; - const filterOne = function(logEntry) { - if ( - logEntry.dead || - selectedTabId !== 0 && - ( - logEntry.tabId === undefined || - logEntry.tabId > 0 && logEntry.tabId !== selectedTabId - ) - ) { - return false; + const filterOne = logEntry => { + if ( logEntry.dead ) { return false; } + if ( selectedTabId !== 0 ) { + if ( logEntry.tabId !== undefined && logEntry.tabId > 0 ) { + if (logEntry.tabId !== selectedTabId ) { return false; } + } } if ( masterFilterSwitch === false || filters.length === 0 ) { @@ -2303,7 +2326,7 @@ const rowJanitor = (( ) => { ? opts.maxEntryCount : 0; const obsolete = typeof opts.maxAge === 'number' - ? Date.now() - opts.maxAge * 60000 + ? Date.now() / 1000 - opts.maxAge * 60 : 0; let i = rowIndex; @@ -2682,16 +2705,16 @@ const loggerStats = (( ) => { const text = entry.textContent; const fields = []; let i = 0; - let beg = text.indexOf('\t'); + let beg = text.indexOf('\x1F'); if ( beg === 0 ) { continue; } let timeField = text.slice(0, beg); if ( options.time === 'anonymous' ) { - timeField = '+' + Math.round((entry.tstamp - t0) / 1000).toString(); + timeField = '+' + Math.round(entry.tstamp - t0).toString(); } fields.push(timeField); beg += 1; while ( beg < text.length ) { - let end = text.indexOf('\t', beg); + let end = text.indexOf('\x1F', beg); if ( end === -1 ) { end = text.length; } fields.push(text.slice(beg, end)); beg = end + 1; @@ -3020,6 +3043,19 @@ dom.on('#pageSelector', 'change', pageSelectorChanged); dom.on('#netInspector .vCompactToggler', 'click', toggleVCompactView); dom.on('#pause', 'click', pauseNetInspector); +dom.on('#logLevel', 'click', ev => { + const level = dom.cl.toggle(ev.currentTarget, 'active') ? 2 : 1; + broadcast({ what: 'loggerLevelChanged', level }); +}); + +dom.on('#netInspector #vwContent', 'copy', ev => { + const selection = document.getSelection(); + const text = selection.toString(); + if ( /\x1F|\u200B/.test(text) === false ) { return; } + ev.clipboardData.setData('text/plain', text.replace(/\x1F|\u200B/g, '\t')); + ev.preventDefault(); +}); + // https://github.com/gorhill/uBlock/issues/507 // Ensure tab selector is in sync with URL hash pageSelectorFromURLHash(); diff --git a/src/js/logger.js b/src/js/logger.js index 5d1114f..766188e 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -23,7 +23,7 @@ /******************************************************************************/ -import { broadcastToAll } from './broadcast.js'; +import { broadcast, broadcastToAll } from './broadcast.js'; /******************************************************************************/ @@ -47,34 +47,38 @@ const janitorTimer = vAPI.defer.create(( ) => { broadcastToAll({ what: 'loggerDisabled' }); }); -const boxEntry = function(details) { - if ( details.tstamp === undefined ) { - details.tstamp = Date.now(); - } +const boxEntry = details => { + details.tstamp = Date.now() / 1000 | 0; return JSON.stringify(details); }; +const pushOne = box => { + if ( writePtr !== 0 && box === buffer[writePtr-1] ) { return; } + if ( writePtr === buffer.length ) { + buffer.push(box); + } else { + buffer[writePtr] = box; + } + writePtr += 1; +}; + const logger = { enabled: false, ownerId: undefined, - writeOne: function(details) { + writeOne(details) { if ( buffer === null ) { return; } - const box = boxEntry(details); - if ( writePtr === buffer.length ) { - buffer.push(box); - } else { - buffer[writePtr] = box; - } - writePtr += 1; + pushOne(boxEntry(details)); }, - readAll: function(ownerId) { + readAll(ownerId) { this.ownerId = ownerId; if ( buffer === null ) { this.enabled = true; buffer = []; janitorTimer.on(logBufferObsoleteAfter); + broadcast({ what: 'loggerEnabled' }); } const out = buffer.slice(0, writePtr); + buffer.fill('', 0, writePtr); writePtr = 0; lastReadTime = Date.now(); return out; diff --git a/src/js/messaging.js b/src/js/messaging.js index 52242b3..5f39af4 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -45,6 +45,7 @@ import { dnrRulesetFromRawLists } from './static-dnr-filtering.js'; import { i18n$ } from './i18n.js'; import { redirectEngine } from './redirect-engine.js'; import * as sfp from './static-filtering-parser.js'; +import * as s14e from './s14e-serializer.js'; import { permanentFirewall, @@ -63,8 +64,6 @@ import { isNetworkURI, } from './uri-utils.js'; -import './benchmarks.js'; - /******************************************************************************/ // https://github.com/uBlockOrigin/uBlock-issues/issues/710 @@ -364,11 +363,12 @@ const popupDataFromTabId = function(tabId, tabTitle) { colorBlindFriendly: µbus.colorBlindFriendly, cosmeticFilteringSwitch: false, firewallPaneMinimized: µbus.firewallPaneMinimized, - globalAllowedRequestCount: µb.localSettings.allowedRequestCount, - globalBlockedRequestCount: µb.localSettings.blockedRequestCount, + globalAllowedRequestCount: µb.requestStats.allowedCount, + globalBlockedRequestCount: µb.requestStats.blockedCount, fontSize: µbhs.popupFontSize, godMode: µbhs.filterAuthorMode, netFilteringSwitch: false, + userFiltersAreEnabled: µb.userFiltersAreEnabled(), rawURL: tabContext.rawURL, pageURL: tabContext.normalURL, pageHostname: rootHostname, @@ -378,6 +378,7 @@ const popupDataFromTabId = function(tabId, tabTitle) { popupPanelDisabledSections: µbhs.popupPanelDisabledSections, popupPanelLockedSections: µbhs.popupPanelLockedSections, popupPanelHeightMode: µbhs.popupPanelHeightMode, + popupPanelOrientation: µbhs.popupPanelOrientation, tabId, tabTitle, tooltipsDisabled: µbus.tooltipsDisabled, @@ -715,15 +716,15 @@ const retrieveContentScriptParameters = async function(sender, request) { // https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-748179731 // For non-network URIs, scriptlet injection is deferred to here. The // effective URL is available here in `request.url`. - if ( logger.enabled || request.needScriptlets ) { - const scriptletDetails = scriptletFilteringEngine.injectNow(request); + if ( logger.enabled ) { + const scriptletDetails = scriptletFilteringEngine.retrieve(request); if ( scriptletDetails !== undefined ) { scriptletFilteringEngine.toLogger(request, scriptletDetails); - if ( request.needScriptlets ) { - response.scriptletDetails = scriptletDetails; - } } } + if ( request.needScriptlets ) { + scriptletFilteringEngine.injectNow(request); + } // https://github.com/NanoMeow/QuickReports/issues/6#issuecomment-414516623 // Inject as early as possible to make the cosmetic logger code less @@ -795,6 +796,17 @@ const onMessage = function(request, sender, callback) { µb.maybeGoodPopup.url = request.url; break; + case 'messageToLogger': + if ( logger.enabled !== true ) { break; } + logger.writeOne({ + tabId: sender.tabId, + realm: 'message', + type: request.type || 'info', + keywords: [ 'scriptlet' ], + text: request.text, + }); + break; + case 'shouldRenderNoscriptTags': if ( pageStore === null ) { break; } const fctxt = µb.filteringContext.fromTabId(sender.tabId); @@ -913,21 +925,6 @@ const fromBase64 = function(encoded) { return Promise.resolve(u8array !== undefined ? u8array : encoded); }; -const toBase64 = function(data) { - const value = data instanceof Uint8Array - ? denseBase64.encode(data) - : data; - return Promise.resolve(value); -}; - -const compress = function(json) { - return lz4Codec.encode(json, toBase64); -}; - -const decompress = function(encoded) { - return lz4Codec.decode(encoded, fromBase64); -}; - const onMessage = function(request, sender, callback) { // Cloud storage support is optional. if ( µb.cloudStorageSupported !== true ) { @@ -949,15 +946,25 @@ const onMessage = function(request, sender, callback) { return; case 'cloudPull': - request.decode = decompress; + request.decode = encoded => { + if ( s14e.isSerialized(encoded) ) { + return s14e.deserializeAsync(encoded, { thread: true }); + } + // Legacy decoding: needs to be kept around for the foreseeable future. + return lz4Codec.decode(encoded, fromBase64); + }; return vAPI.cloud.pull(request).then(result => { callback(result); }); case 'cloudPush': - if ( µb.hiddenSettings.cloudStorageCompression ) { - request.encode = compress; - } + request.encode = data => { + const options = { + compress: µb.hiddenSettings.cloudStorageCompression, + thread: true, + }; + return s14e.serializeAsync(data, options); + }; return vAPI.cloud.push(request).then(result => { callback(result); }); @@ -1444,11 +1451,23 @@ const onMessage = function(request, sender, callback) { case 'readUserFilters': return µb.loadUserFilters().then(result => { - result.trustedSource = µb.isTrustedList(µb.userFiltersPath); + result.enabled = µb.selectedFilterLists.includes(µb.userFiltersPath); + result.trusted = µb.isTrustedList(µb.userFiltersPath); callback(result); }); case 'writeUserFilters': + if ( request.enabled ) { + µb.applyFilterListSelection({ + toSelect: [ µb.userFiltersPath ], + merge: true, + }); + } else { + µb.applyFilterListSelection({ + toRemove: [ µb.userFiltersPath ], + }); + } + µb.changeUserSettings('userFiltersTrusted', request.trusted || false); return µb.saveUserFilters(request.content).then(result => { callback(result); }); @@ -1839,8 +1858,26 @@ const onMessage = function(request, sender, callback) { return; case 'snfeBenchmark': - µb.benchmarkStaticNetFiltering({ redirectEngine }).then(result => { - callback(result); + import('/js/benchmarks.js').then(module => { + module.benchmarkStaticNetFiltering({ redirectEngine }).then(result => { + callback(result); + }); + }); + return; + + case 'cfeBenchmark': + import('/js/benchmarks.js').then(module => { + module.benchmarkCosmeticFiltering().then(result => { + callback(result); + }); + }); + return; + + case 'sfeBenchmark': + import('/js/benchmarks.js').then(module => { + module.benchmarkScriptletFiltering().then(result => { + callback(result); + }); }); return; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 907e747..227352d 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -19,17 +19,13 @@ Home: https://github.com/gorhill/uBlock */ -'use strict'; - /******************************************************************************/ -import contextMenu from './contextmenu.js'; -import logger from './logger.js'; -import staticNetFilteringEngine from './static-net-filtering.js'; -import µb from './background.js'; -import webext from './webext.js'; -import { orphanizeString } from './text-utils.js'; -import { redirectEngine } from './redirect-engine.js'; +import { + domainFromHostname, + hostnameFromURI, + isNetworkURI, +} from './uri-utils.js'; import { sessionFirewall, @@ -37,11 +33,13 @@ import { sessionURLFiltering, } from './filtering-engines.js'; -import { - domainFromHostname, - hostnameFromURI, - isNetworkURI, -} from './uri-utils.js'; +import contextMenu from './contextmenu.js'; +import logger from './logger.js'; +import { orphanizeString } from './text-utils.js'; +import { redirectEngine } from './redirect-engine.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import webext from './webext.js'; +import µb from './background.js'; /******************************************************************************* @@ -379,11 +377,13 @@ const PageStore = class { // If we are navigating from-to same site, remember whether large // media elements were temporarily allowed. - if ( - typeof this.allowLargeMediaElementsUntil !== 'number' || - tabContext.rootHostname !== this.tabHostname - ) { - this.allowLargeMediaElementsUntil = Date.now(); + const now = Date.now(); + if ( typeof this.allowLargeMediaElementsUntil !== 'number' ) { + this.allowLargeMediaElementsUntil = now; + } else if ( tabContext.rootHostname !== this.tabHostname ) { + if ( this.tabHostname.endsWith('about-scheme') === false ) { + this.allowLargeMediaElementsUntil = now; + } } this.tabHostname = tabContext.rootHostname; @@ -739,10 +739,8 @@ const PageStore = class { aggregateAllowed += 1; } } - if ( aggregateAllowed !== 0 || aggregateBlocked !== 0 ) { - µb.localSettings.blockedRequestCount += aggregateBlocked; - µb.localSettings.allowedRequestCount += aggregateAllowed; - µb.localSettingsLastModified = now; + if ( aggregateAllowed || aggregateBlocked ) { + µb.incrementRequestStats(aggregateBlocked, aggregateAllowed); } journal.length = 0; } diff --git a/src/js/popup-fenix.js b/src/js/popup-fenix.js index b44b923..9f2af08 100644 --- a/src/js/popup-fenix.js +++ b/src/js/popup-fenix.js @@ -70,6 +70,9 @@ let cachedPopupHash = ''; const reCyrillicNonAmbiguous = /[\u0400-\u042b\u042d-\u042f\u0431\u0432\u0434\u0436-\u043d\u0442\u0444\u0446-\u0449\u044b-\u0454\u0457\u0459-\u0460\u0462-\u0474\u0476-\u04ba\u04bc\u04be-\u04ce\u04d0-\u0500\u0502-\u051a\u051c\u051e-\u052f]/; const reCyrillicAmbiguous = /[\u042c\u0430\u0433\u0435\u043e\u043f\u0440\u0441\u0443\u0445\u044a\u0455\u0456\u0458\u0461\u0475\u04bb\u04bd\u04cf\u0501\u051b\u051d]/; +const hasOwnProperty = (o, p) => + Object.prototype.hasOwnProperty.call(o, p); + /******************************************************************************/ const cachePopupData = function(data) { @@ -88,7 +91,7 @@ const cachePopupData = function(data) { return popupData; } for ( const hostname in hostnameDict ) { - if ( hostnameDict.hasOwnProperty(hostname) === false ) { continue; } + if ( hasOwnProperty(hostnameDict, hostname) === false ) { continue; } let domain = hostnameDict[hostname].domain; let prefix = hostname.slice(0, 0 - domain.length - 1); // Prefix with space char for 1st-party hostnames: this ensure these @@ -160,7 +163,7 @@ const formatNumber = function(count) { }); if ( intl.resolvedOptions instanceof Function && - intl.resolvedOptions().hasOwnProperty('notation') + hasOwnProperty(intl.resolvedOptions(), 'notation') ) { intlNumberFormat = intl; } @@ -545,7 +548,7 @@ const renderPrivacyExposure = function() { if ( des === '*' || desHostnameDone.has(des) ) { continue; } const hnDetails = hostnameDict[des]; const { domain, counts } = hnDetails; - if ( allDomains.hasOwnProperty(domain) === false ) { + if ( hasOwnProperty(allDomains, domain) === false ) { allDomains[domain] = false; allDomainCount += 1; } @@ -614,11 +617,11 @@ const renderPopup = function() { } } - dom.cl.toggle( - '#basicTools', - 'canPick', - popupData.canElementPicker === true && isFiltering - ); + const canPick = popupData.canElementPicker && isFiltering; + + dom.cl.toggle('#gotoZap', 'canPick', canPick); + dom.cl.toggle('#gotoPick', 'canPick', canPick && popupData.userFiltersAreEnabled); + dom.cl.toggle('#gotoReport', 'canPick', canPick); let blocked, total; if ( popupData.pageCounts !== undefined ) { @@ -675,7 +678,7 @@ const renderPopup = function() { total ? Math.min(total, 99).toLocaleString() : '' ); - // Unprocesseed request(s) warning + // Unprocessed request(s) warning dom.cl.toggle(dom.root, 'warn', popupData.hasUnprocessedRequest === true); dom.cl.toggle(dom.html, 'colorBlind', popupData.colorBlindFriendly === true); @@ -802,7 +805,7 @@ let renderOnce = function() { dom.attr('#firewall [title][data-src]', 'title', null); } - // This must be done the firewall is populated + // This must be done when the firewall is populated if ( popupData.popupPanelHeightMode === 1 ) { dom.cl.add(dom.body, 'vMin'); } @@ -1462,6 +1465,33 @@ const getPopupData = async function(tabId, first = false) { } }; + const setOrientation = async ( ) => { + if ( dom.cl.has(dom.root, 'mobile') ) { + dom.cl.remove(dom.root, 'desktop'); + dom.cl.add(dom.root, 'portrait'); + return; + } + if ( selfURL.searchParams.get('portrait') !== null ) { + dom.cl.remove(dom.root, 'desktop'); + dom.cl.add(dom.root, 'portrait'); + return; + } + if ( popupData.popupPanelOrientation === 'landscape' ) { return; } + if ( popupData.popupPanelOrientation === 'portrait' ) { + dom.cl.remove(dom.root, 'desktop'); + dom.cl.add(dom.root, 'portrait'); + return; + } + if ( dom.cl.has(dom.root, 'desktop') === false ) { return; } + await nextFrames(8); + const main = qs$('#main'); + const firewall = qs$('#firewall'); + const minWidth = (main.offsetWidth + firewall.offsetWidth) / 1.1; + if ( window.innerWidth < minWidth ) { + dom.cl.add(dom.root, 'portrait'); + } + }; + // The purpose of the following code is to reset to a vertical layout // should the viewport not be enough wide to accommodate the horizontal // layout. @@ -1474,24 +1504,7 @@ const getPopupData = async function(tabId, first = false) { // Use a tolerance proportional to the sum of the width of the panes // when testing against viewport width. const checkViewport = async function() { - if ( - dom.cl.has(dom.root, 'mobile') || - selfURL.searchParams.get('portrait') - ) { - dom.cl.add(dom.root, 'portrait'); - dom.cl.remove(dom.root, 'desktop'); - } else if ( dom.cl.has(dom.root, 'desktop') ) { - await nextFrames(8); - const main = qs$('#main'); - const firewall = qs$('#firewall'); - const minWidth = (main.offsetWidth + firewall.offsetWidth) / 1.1; - if ( - selfURL.searchParams.get('portrait') || - window.innerWidth < minWidth - ) { - dom.cl.add(dom.root, 'portrait'); - } - } + await setOrientation(); if ( dom.cl.has(dom.root, 'portrait') ) { const panes = qs$('#panes'); const sticky = qs$('#sticky'); diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index 2f58066..1edb376 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -24,11 +24,7 @@ /******************************************************************************/ import redirectableResources from './redirect-resources.js'; - -import { - LineIterator, - orphanizeString, -} from './text-utils.js'; +import { LineIterator, orphanizeString } from './text-utils.js'; /******************************************************************************/ @@ -76,7 +72,7 @@ const warSecret = typeof vAPI === 'object' && vAPI !== null : ( ) => ''; const RESOURCES_SELFIE_VERSION = 7; -const RESOURCES_SELFIE_NAME = 'compiled/redirectEngine/resources'; +const RESOURCES_SELFIE_NAME = 'selfie/redirectEngine/resources'; /******************************************************************************/ /******************************************************************************/ @@ -448,33 +444,22 @@ class RedirectEngine { } selfieFromResources(storage) { - storage.put( - RESOURCES_SELFIE_NAME, - JSON.stringify({ - version: RESOURCES_SELFIE_VERSION, - aliases: Array.from(this.aliases), - resources: Array.from(this.resources), - }) - ); + return storage.toCache(RESOURCES_SELFIE_NAME, { + version: RESOURCES_SELFIE_VERSION, + aliases: this.aliases, + resources: this.resources, + }); } async resourcesFromSelfie(storage) { - const result = await storage.get(RESOURCES_SELFIE_NAME); - let selfie; - try { - selfie = JSON.parse(result.content); - } catch(ex) { - } - if ( - selfie instanceof Object === false || - selfie.version !== RESOURCES_SELFIE_VERSION || - Array.isArray(selfie.resources) === false - ) { - return false; - } - this.aliases = new Map(selfie.aliases); - this.resources = new Map(); - for ( const [ token, entry ] of selfie.resources ) { + const selfie = await storage.fromCache(RESOURCES_SELFIE_NAME); + if ( selfie instanceof Object === false ) { return false; } + if ( selfie.version !== RESOURCES_SELFIE_VERSION ) { return false; } + if ( selfie.aliases instanceof Map === false ) { return false; } + if ( selfie.resources instanceof Map === false ) { return false; } + this.aliases = selfie.aliases; + this.resources = selfie.resources; + for ( const [ token, entry ] of this.resources ) { this.resources.set(token, RedirectEntry.fromDetails(entry)); } return true; diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index c21ca4b..e7bf24e 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -62,7 +62,7 @@ const stopWorker = function() { }; const workerTTLTimer = vAPI.defer.create(stopWorker); -const workerTTL = { min: 5 }; +const workerTTL = { min: 1.5 }; const initWorker = function() { if ( worker === null ) { diff --git a/src/js/s14e-serializer.js b/src/js/s14e-serializer.js new file mode 100644 index 0000000..aae0ac9 --- /dev/null +++ b/src/js/s14e-serializer.js @@ -0,0 +1,1405 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2024-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************* + * + * Structured-Cloneable to Unicode-Only SERIALIZER + * + * Purpose: + * + * Serialize/deserialize arbitrary JS data to/from well-formed Unicode strings. + * + * The browser does not expose an API to serialize structured-cloneable types + * into a single string. JSON.stringify() does not support complex JavaScript + * objects, and does not support references to composite types. Unless the + * data to serialize is only JS strings, it is difficult to easily switch + * from one type of storage to another. + * + * Serializing to a well-formed Unicode string allows to store structured- + * cloneable data to any storage. Not all storages support storing binary data, + * but all storages support storing Unicode strings. + * + * Structured-cloneable types: + * https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types + * + * ----------------+------------------+------------------+---------------------- + * Data types | String | JSONable | structured-cloneable + * ================+============================================================ + * document.cookie | Yes | No | No + * ----------------+------------------+------------------+---------------------- + * localStorage | Yes | No | No + * ----------------+------------------+------------------+---------------------- + * IndexedDB | Yes | Yes | Yes + * ----------------+------------------+------------------+---------------------- + * browser.storage | Yes | Yes | No + * ----------------+------------------+------------------+---------------------- + * Cache API | Yes | No | No + * ----------------+------------------+------------------+---------------------- + * + * The above table shows that only JS strings can be persisted natively to all + * types of storage. The purpose of this library is to convert + * structure-cloneable data (which is a superset of JSONable data) into a + * single JS string. The resulting string is meant to be as small as possible. + * As a result, it is not human-readable, though it contains only printable + * ASCII characters -- and possibly Unicode characters beyond ASCII. + * + * The resulting JS string will not contain characters which require escaping + * should it be converted to a JSON value. However it may contain characters + * which require escaping should it be converted to a URI component. + * + * Characteristics: + * + * - Serializes/deserializes data to/from a single well-formed Unicode string + * - Strings do not require escaping, i.e. they are stored as-is + * - Supports multiple references to same object + * - Supports reference cycles + * - Supports synchronous and asynchronous API + * - Supports usage of Worker + * - Optionally supports LZ4 compression + * + * TODO: + * + * - Harden against unexpected conditions, such as corrupted string during + * deserialization. + * - Evaluate supporting checksum. + * + * */ + +const VERSION = 1; +const SEPARATORCHAR = ' '; +const SEPARATORCHARCODE = SEPARATORCHAR.charCodeAt(0); +const SENTINELCHAR = '!'; +const SENTINELCHARCODE = SENTINELCHAR.charCodeAt(0); +const MAGICPREFIX = `UOSC_${VERSION}${SEPARATORCHAR}`; +const MAGICLZ4PREFIX = `UOSC/lz4_${VERSION}${SEPARATORCHAR}`; +const FAILMARK = Number.MAX_SAFE_INTEGER; +// Avoid characters which require escaping when serialized to JSON: +const SAFECHARS = "&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"; +const NUMSAFECHARS = SAFECHARS.length; +const BITS_PER_SAFECHARS = Math.log2(NUMSAFECHARS); + +const { intToChar, intToCharCode, charCodeToInt } = (( ) => { + const intToChar = []; + const intToCharCode = []; + const charCodeToInt = []; + for ( let i = 0; i < NUMSAFECHARS; i++ ) { + intToChar[i] = SAFECHARS.charAt(i); + intToCharCode[i] = SAFECHARS.charCodeAt(i); + charCodeToInt[i] = 0; + } + for ( let i = NUMSAFECHARS; i < 128; i++ ) { + intToChar[i] = ''; + intToCharCode[i] = 0; + charCodeToInt[i] = 0; + } + for ( let i = 0; i < SAFECHARS.length; i++ ) { + charCodeToInt[SAFECHARS.charCodeAt(i)] = i; + } + return { intToChar, intToCharCode, charCodeToInt }; +})(); + +let iota = 1; +const I_STRING_SMALL = iota++; +const I_STRING_LARGE = iota++; +const I_ZERO = iota++; +const I_INTEGER_SMALL_POS = iota++; +const I_INTEGER_SMALL_NEG = iota++; +const I_INTEGER_LARGE_POS = iota++; +const I_INTEGER_LARGE_NEG = iota++; +const I_BOOL_FALSE = iota++; +const I_BOOL_TRUE = iota++; +const I_NULL = iota++; +const I_UNDEFINED = iota++; +const I_FLOAT = iota++; +const I_REGEXP = iota++; +const I_DATE = iota++; +const I_REFERENCE = iota++; +const I_OBJECT_SMALL = iota++; +const I_OBJECT_LARGE = iota++; +const I_ARRAY_SMALL = iota++; +const I_ARRAY_LARGE = iota++; +const I_SET_SMALL = iota++; +const I_SET_LARGE = iota++; +const I_MAP_SMALL = iota++; +const I_MAP_LARGE = iota++; +const I_ARRAYBUFFER = iota++; +const I_INT8ARRAY = iota++; +const I_UINT8ARRAY = iota++; +const I_UINT8CLAMPEDARRAY = iota++; +const I_INT16ARRAY = iota++; +const I_UINT16ARRAY = iota++; +const I_INT32ARRAY = iota++; +const I_UINT32ARRAY = iota++; +const I_FLOAT32ARRAY = iota++; +const I_FLOAT64ARRAY = iota++; +const I_DATAVIEW = iota++; + +const C_STRING_SMALL = intToChar[I_STRING_SMALL]; +const C_STRING_LARGE = intToChar[I_STRING_LARGE]; +const C_ZERO = intToChar[I_ZERO]; +const C_INTEGER_SMALL_POS = intToChar[I_INTEGER_SMALL_POS]; +const C_INTEGER_SMALL_NEG = intToChar[I_INTEGER_SMALL_NEG]; +const C_INTEGER_LARGE_POS = intToChar[I_INTEGER_LARGE_POS]; +const C_INTEGER_LARGE_NEG = intToChar[I_INTEGER_LARGE_NEG]; +const C_BOOL_FALSE = intToChar[I_BOOL_FALSE]; +const C_BOOL_TRUE = intToChar[I_BOOL_TRUE]; +const C_NULL = intToChar[I_NULL]; +const C_UNDEFINED = intToChar[I_UNDEFINED]; +const C_FLOAT = intToChar[I_FLOAT]; +const C_REGEXP = intToChar[I_REGEXP]; +const C_DATE = intToChar[I_DATE]; +const C_REFERENCE = intToChar[I_REFERENCE]; +const C_OBJECT_SMALL = intToChar[I_OBJECT_SMALL]; +const C_OBJECT_LARGE = intToChar[I_OBJECT_LARGE]; +const C_ARRAY_SMALL = intToChar[I_ARRAY_SMALL]; +const C_ARRAY_LARGE = intToChar[I_ARRAY_LARGE]; +const C_SET_SMALL = intToChar[I_SET_SMALL]; +const C_SET_LARGE = intToChar[I_SET_LARGE]; +const C_MAP_SMALL = intToChar[I_MAP_SMALL]; +const C_MAP_LARGE = intToChar[I_MAP_LARGE]; +const C_ARRAYBUFFER = intToChar[I_ARRAYBUFFER]; +const C_INT8ARRAY = intToChar[I_INT8ARRAY]; +const C_UINT8ARRAY = intToChar[I_UINT8ARRAY]; +const C_UINT8CLAMPEDARRAY = intToChar[I_UINT8CLAMPEDARRAY]; +const C_INT16ARRAY = intToChar[I_INT16ARRAY]; +const C_UINT16ARRAY = intToChar[I_UINT16ARRAY]; +const C_INT32ARRAY = intToChar[I_INT32ARRAY]; +const C_UINT32ARRAY = intToChar[I_UINT32ARRAY]; +const C_FLOAT32ARRAY = intToChar[I_FLOAT32ARRAY]; +const C_FLOAT64ARRAY = intToChar[I_FLOAT64ARRAY]; +const C_DATAVIEW = intToChar[I_DATAVIEW]; + +// Just reuse already defined constants, we just need distinct values +const I_STRING = I_STRING_SMALL; +const I_NUMBER = I_FLOAT; +const I_BOOL = I_BOOL_FALSE; +const I_OBJECT = I_OBJECT_SMALL; +const I_ARRAY = I_ARRAY_SMALL; +const I_SET = I_SET_SMALL; +const I_MAP = I_MAP_SMALL; + +const typeToSerializedInt = { + 'string': I_STRING, + 'number': I_NUMBER, + 'boolean': I_BOOL, + 'object': I_OBJECT, +}; + +const xtypeToSerializedInt = { + '[object RegExp]': I_REGEXP, + '[object Date]': I_DATE, + '[object Array]': I_ARRAY, + '[object Set]': I_SET, + '[object Map]': I_MAP, + '[object ArrayBuffer]': I_ARRAYBUFFER, + '[object Int8Array]': I_INT8ARRAY, + '[object Uint8Array]': I_UINT8ARRAY, + '[object Uint8ClampedArray]': I_UINT8CLAMPEDARRAY, + '[object Int16Array]': I_INT16ARRAY, + '[object Uint16Array]': I_UINT16ARRAY, + '[object Int32Array]': I_INT32ARRAY, + '[object Uint32Array]': I_UINT32ARRAY, + '[object Float32Array]': I_FLOAT32ARRAY, + '[object Float64Array]': I_FLOAT64ARRAY, + '[object DataView]': I_DATAVIEW, +}; + +const xtypeToSerializedChar = { + '[object Int8Array]': C_INT8ARRAY, + '[object Uint8Array]': C_UINT8ARRAY, + '[object Uint8ClampedArray]': C_UINT8CLAMPEDARRAY, + '[object Int16Array]': C_INT16ARRAY, + '[object Uint16Array]': C_UINT16ARRAY, + '[object Int32Array]': C_INT32ARRAY, + '[object Uint32Array]': C_UINT32ARRAY, + '[object Float32Array]': C_FLOAT32ARRAY, + '[object Float64Array]': C_FLOAT64ARRAY, +}; + +const toArrayBufferViewConstructor = { + [`${I_INT8ARRAY}`]: Int8Array, + [`${I_UINT8ARRAY}`]: Uint8Array, + [`${I_UINT8CLAMPEDARRAY}`]: Uint8ClampedArray, + [`${I_INT16ARRAY}`]: Int16Array, + [`${I_UINT16ARRAY}`]: Uint16Array, + [`${I_INT32ARRAY}`]: Int32Array, + [`${I_UINT32ARRAY}`]: Uint32Array, + [`${I_FLOAT32ARRAY}`]: Float32Array, + [`${I_FLOAT64ARRAY}`]: Float64Array, + [`${I_DATAVIEW}`]: DataView, +}; + +/******************************************************************************/ + +const textDecoder = new TextDecoder(); +const textEncoder = new TextEncoder(); +const isInteger = Number.isInteger; + +const writeRefs = new Map(); +const writeBuffer = []; + +const readRefs = new Map(); +let readStr = ''; +let readPtr = 0; +let readEnd = 0; + +let refCounter = 1; + +let uint8Input = null; + +const uint8InputFromAsciiStr = s => { + if ( uint8Input === null || uint8Input.length < s.length ) { + uint8Input = new Uint8Array(s.length + 0x03FF & ~0x03FF); + } + textEncoder.encodeInto(s, uint8Input); + return uint8Input; +}; + +const isInstanceOf = (o, s) => { + return typeof o === 'object' && o !== null && ( + s === 'Object' || Object.prototype.toString.call(o) === `[object ${s}]` + ); +}; + +const shouldCompress = (s, options) => + options.compress === true && ( + options.compressThreshold === undefined || + options.compressThreshold <= s.length + ); + +/******************************************************************************* + * + * A large Uint is always a positive integer (can be zero), assumed to be + * large, i.e. > NUMSAFECHARS -- but not necessarily. The serialized value has + * always at least one digit, and is always followed by a separator. + * + * */ + +const strFromLargeUint = i => { + let r = 0, s = ''; + for (;;) { + r = i % NUMSAFECHARS; + s += intToChar[r]; + i -= r; + if ( i === 0 ) { break; } + i /= NUMSAFECHARS; + } + return s + SEPARATORCHAR; +}; + +const deserializeLargeUint = ( ) => { + let c = readStr.charCodeAt(readPtr++); + let n = charCodeToInt[c]; + let m = 1; + while ( (c = readStr.charCodeAt(readPtr++)) !== SEPARATORCHARCODE ) { + m *= NUMSAFECHARS; + n += m * charCodeToInt[c]; + } + return n; +}; + +/******************************************************************************* + * + * Methods specific to ArrayBuffer objects to serialize optimally according to + * the content of the buffer. + * + * In sparse mode, number of output bytes per input int32 (4-byte) value: + * [v === zero]: 1 byte (separator) + * [v !== zero]: n digits + 1 byte (separator) + * + * */ + +const sparseValueLen = v => v !== 0 + ? (Math.log2(v) / BITS_PER_SAFECHARS | 0) + 2 + : 1; + +const analyzeArrayBuffer = arrbuf => { + const byteLength = arrbuf.byteLength; + const uint32len = byteLength >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uint32len); + let notzeroCount = 0; + for ( let i = uint32len-1; i >= 0; i-- ) { + if ( uint32arr[i] === 0 ) { continue; } + notzeroCount = i + 1; + break; + } + const end = notzeroCount + 1 <= uint32len ? notzeroCount << 2 : byteLength; + const endUint32 = end >>> 2; + const remUint8 = end & 0b11; + const denseSize = endUint32 * 5 + (remUint8 ? remUint8 + 1 : 0); + let sparseSize = 0; + for ( let i = 0; i < endUint32; i++ ) { + sparseSize += sparseValueLen(uint32arr[i]); + if ( sparseSize > denseSize ) { + return { end, dense: true, denseSize }; + } + } + if ( remUint8 !== 0 ) { + sparseSize += 1; // sentinel + const uint8arr = new Uint8Array(arrbuf, endUint32 << 2); + for ( let i = 0; i < remUint8; i++ ) { + sparseSize += sparseValueLen(uint8arr[i]); + } + } + return { end, dense: false, sparseSize }; +}; + +const denseArrayBufferToStr = (arrbuf, details) => { + const end = details.end; + const m = end % 4; + const n = end - m; + const uin32len = n >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uin32len); + const output = new Uint8Array(details.denseSize); + let j = 0, v = 0; + for ( let i = 0; i < uin32len; i++ ) { + v = uint32arr[i]; + output[j+0] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+1] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+2] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+3] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+4] = intToCharCode[v]; + j += 5; + } + if ( m !== 0 ) { + const uint8arr = new Uint8Array(arrbuf, n); + v = uint8arr[0]; + if ( m > 1 ) { + v += uint8arr[1] << 8; + if ( m > 2 ) { + v += uint8arr[2] << 16; + } + } + output[j+0] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+1] = intToCharCode[v % NUMSAFECHARS]; + if ( m > 1 ) { + v = v / NUMSAFECHARS | 0; + output[j+2] = intToCharCode[v % NUMSAFECHARS]; + if ( m > 2 ) { + v = v / NUMSAFECHARS | 0; + output[j+3] = intToCharCode[v % NUMSAFECHARS]; + } + } + } + return textDecoder.decode(output); +}; + +const BASE88_POW1 = NUMSAFECHARS; +const BASE88_POW2 = NUMSAFECHARS * BASE88_POW1; +const BASE88_POW3 = NUMSAFECHARS * BASE88_POW2; +const BASE88_POW4 = NUMSAFECHARS * BASE88_POW3; + +const denseArrayBufferFromStr = (denseStr, arrbuf) => { + const input = uint8InputFromAsciiStr(denseStr); + const end = denseStr.length; + const m = end % 5; + const n = end - m; + const uin32len = n / 5 * 4 >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uin32len); + let j = 0, v = 0; + for ( let i = 0; i < n; i += 5 ) { + v = charCodeToInt[input[i+0]]; + v += charCodeToInt[input[i+1]] * BASE88_POW1; + v += charCodeToInt[input[i+2]] * BASE88_POW2; + v += charCodeToInt[input[i+3]] * BASE88_POW3; + v += charCodeToInt[input[i+4]] * BASE88_POW4; + uint32arr[j++] = v; + } + if ( m === 0 ) { return; } + v = charCodeToInt[input[n+0]] + + charCodeToInt[input[n+1]] * BASE88_POW1; + if ( m > 2 ) { + v += charCodeToInt[input[n+2]] * BASE88_POW2; + if ( m > 3 ) { + v += charCodeToInt[input[n+3]] * BASE88_POW3; + } + } + const uint8arr = new Uint8Array(arrbuf, j << 2); + uint8arr[0] = v & 255; + if ( v !== 0 ) { + v >>>= 8; + uint8arr[1] = v & 255; + if ( v !== 0 ) { + v >>>= 8; + uint8arr[2] = v & 255; + } + } +}; + +const sparseArrayBufferToStr = (arrbuf, details) => { + const end = details.end; + const uint8out = new Uint8Array(details.sparseSize); + const uint32len = end >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uint32len); + let j = 0, n = 0, r = 0; + for ( let i = 0; i < uint32len; i++ ) { + n = uint32arr[i]; + if ( n !== 0 ) { + for (;;) { + r = n % NUMSAFECHARS; + uint8out[j++] = intToCharCode[r]; + n -= r; + if ( n === 0 ) { break; } + n /= NUMSAFECHARS; + } + } + uint8out[j++] = SEPARATORCHARCODE; + } + const uint8rem = end & 0b11; + if ( uint8rem !== 0 ) { + uint8out[j++] = SENTINELCHARCODE; + const uint8arr = new Uint8Array(arrbuf, end - uint8rem, uint8rem); + for ( let i = 0; i < uint8rem; i++ ) { + n = uint8arr[i]; + if ( n !== 0 ) { + for (;;) { + r = n % NUMSAFECHARS; + uint8out[j++] = intToCharCode[r]; + n -= r; + if ( n === 0 ) { break; } + n /= NUMSAFECHARS; + } + } + uint8out[j++] = SEPARATORCHARCODE; + } + } + return textDecoder.decode(uint8out); +}; + +const sparseArrayBufferFromStr = (sparseStr, arrbuf) => { + const sparseLen = sparseStr.length; + const input = uint8InputFromAsciiStr(sparseStr); + const end = arrbuf.byteLength; + const uint32len = end >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uint32len); + let i = 0, j = 0, c = 0, n = 0, m = 0; + for ( ; j < sparseLen; i++ ) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { continue; } + if ( c === SENTINELCHARCODE ) { break; } + n = charCodeToInt[c]; + m = 1; + for (;;) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { break; } + m *= NUMSAFECHARS; + n += m * charCodeToInt[c]; + } + uint32arr[i] = n; + } + if ( c === SENTINELCHARCODE ) { + i <<= 2; + const uint8arr = new Uint8Array(arrbuf, i); + for ( ; j < sparseLen; i++ ) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { continue; } + n = charCodeToInt[c]; + m = 1; + for (;;) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { break; } + m *= NUMSAFECHARS; + n += m * charCodeToInt[c]; + } + uint8arr[i] = n; + } + } +}; + +/******************************************************************************/ + +const _serialize = data => { + // Primitive types + if ( data === 0 ) { + writeBuffer.push(C_ZERO); + return; + } + if ( data === null ) { + writeBuffer.push(C_NULL); + return; + } + if ( data === undefined ) { + writeBuffer.push(C_UNDEFINED); + return; + } + // Type name + switch ( typeToSerializedInt[typeof data] ) { + case I_STRING: { + const length = data.length; + if ( length < NUMSAFECHARS ) { + writeBuffer.push(C_STRING_SMALL + intToChar[length], data); + } else { + writeBuffer.push(C_STRING_LARGE + strFromLargeUint(length), data); + } + return; + } + case I_NUMBER: + if ( isInteger(data) ) { + if ( data >= NUMSAFECHARS ) { + writeBuffer.push(C_INTEGER_LARGE_POS + strFromLargeUint(data)); + } else if ( data > 0 ) { + writeBuffer.push(C_INTEGER_SMALL_POS + intToChar[data]); + } else if ( data > -NUMSAFECHARS ) { + writeBuffer.push(C_INTEGER_SMALL_NEG + intToChar[-data]); + } else { + writeBuffer.push(C_INTEGER_LARGE_NEG + strFromLargeUint(-data)); + } + } else { + const s = `${data}`; + writeBuffer.push(C_FLOAT + strFromLargeUint(s.length) + s); + } + return; + case I_BOOL: + writeBuffer.push(data ? C_BOOL_TRUE : C_BOOL_FALSE); + return; + case I_OBJECT: + break; + default: + return; + } + const xtypeName = Object.prototype.toString.call(data); + const xtypeInt = xtypeToSerializedInt[xtypeName]; + if ( xtypeInt === I_REGEXP ) { + writeBuffer.push(C_REGEXP); + _serialize(data.source); + _serialize(data.flags); + return; + } + if ( xtypeInt === I_DATE ) { + writeBuffer.push(C_DATE + _serialize(data.getTime())); + return; + } + // Reference to composite types + const ref = writeRefs.get(data); + if ( ref !== undefined ) { + writeBuffer.push(C_REFERENCE + strFromLargeUint(ref)); + return; + } + // Remember reference + writeRefs.set(data, refCounter++); + // Extended type name + switch ( xtypeInt ) { + case I_ARRAY: { + const size = data.length; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_ARRAY_SMALL + intToChar[size]); + } else { + writeBuffer.push(C_ARRAY_LARGE + strFromLargeUint(size)); + } + for ( const v of data ) { + _serialize(v); + } + return; + } + case I_SET: { + const size = data.size; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_SET_SMALL + intToChar[size]); + } else { + writeBuffer.push(C_SET_LARGE + strFromLargeUint(size)); + } + for ( const v of data ) { + _serialize(v); + } + return; + } + case I_MAP: { + const size = data.size; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_MAP_SMALL + intToChar[size]); + } else { + writeBuffer.push(C_MAP_LARGE + strFromLargeUint(size)); + } + for ( const [ k, v ] of data ) { + _serialize(k); + _serialize(v); + } + return; + } + case I_ARRAYBUFFER: { + const byteLength = data.byteLength; + writeBuffer.push(C_ARRAYBUFFER + strFromLargeUint(byteLength)); + _serialize(data.maxByteLength); + const arrbuffDetails = analyzeArrayBuffer(data); + _serialize(arrbuffDetails.dense); + const str = arrbuffDetails.dense + ? denseArrayBufferToStr(data, arrbuffDetails) + : sparseArrayBufferToStr(data, arrbuffDetails); + _serialize(str); + //console.log(`arrbuf size=${byteLength} content size=${arrbuffDetails.end} dense=${arrbuffDetails.dense} array size=${arrbuffDetails.dense ? arrbuffDetails.denseSize : arrbuffDetails.sparseSize} serialized size=${str.length}`); + return; + } + case I_INT8ARRAY: + case I_UINT8ARRAY: + case I_UINT8CLAMPEDARRAY: + case I_INT16ARRAY: + case I_UINT16ARRAY: + case I_INT32ARRAY: + case I_UINT32ARRAY: + case I_FLOAT32ARRAY: + case I_FLOAT64ARRAY: + writeBuffer.push( + xtypeToSerializedChar[xtypeName], + strFromLargeUint(data.byteOffset), + strFromLargeUint(data.length) + ); + _serialize(data.buffer); + return; + case I_DATAVIEW: + writeBuffer.push(C_DATAVIEW, strFromLargeUint(data.byteOffset), strFromLargeUint(data.byteLength)); + _serialize(data.buffer); + return; + default: { + const keys = Object.keys(data); + const size = keys.length; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_OBJECT_SMALL + intToChar[size]); + } else { + writeBuffer.push(C_OBJECT_LARGE + strFromLargeUint(size)); + } + for ( const key of keys ) { + _serialize(key); + _serialize(data[key]); + } + break; + } + } +}; + +/******************************************************************************/ + +const _deserialize = ( ) => { + if ( readPtr >= readEnd ) { return; } + const type = charCodeToInt[readStr.charCodeAt(readPtr++)]; + switch ( type ) { + // Primitive types + case I_STRING_SMALL: + case I_STRING_LARGE: { + const size = type === I_STRING_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + const beg = readPtr; + readPtr += size; + return readStr.slice(beg, readPtr); + } + case I_ZERO: + return 0; + case I_INTEGER_SMALL_POS: + return charCodeToInt[readStr.charCodeAt(readPtr++)]; + case I_INTEGER_SMALL_NEG: + return -charCodeToInt[readStr.charCodeAt(readPtr++)]; + case I_INTEGER_LARGE_POS: + return deserializeLargeUint(); + case I_INTEGER_LARGE_NEG: + return -deserializeLargeUint(); + case I_BOOL_FALSE: + return false; + case I_BOOL_TRUE: + return true; + case I_NULL: + return null; + case I_UNDEFINED: + return; + case I_FLOAT: { + const size = deserializeLargeUint(); + const beg = readPtr; + readPtr += size; + return parseFloat(readStr.slice(beg, readPtr)); + } + case I_REGEXP: { + const source = _deserialize(); + const flags = _deserialize(); + return new RegExp(source, flags); + } + case I_DATE: { + const time = _deserialize(); + return new Date(time); + } + case I_REFERENCE: { + const ref = deserializeLargeUint(); + return readRefs.get(ref); + } + case I_OBJECT_SMALL: + case I_OBJECT_LARGE: { + const entries = []; + const size = type === I_OBJECT_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + const k = _deserialize(); + const v = _deserialize(); + entries.push([ k, v ]); + } + const out = Object.fromEntries(entries); + readRefs.set(refCounter++, out); + return out; + } + case I_ARRAY_SMALL: + case I_ARRAY_LARGE: { + const out = []; + const size = type === I_ARRAY_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + out.push(_deserialize()); + } + readRefs.set(refCounter++, out); + return out; + } + case I_SET_SMALL: + case I_SET_LARGE: { + const entries = []; + const size = type === I_SET_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + entries.push(_deserialize()); + } + const out = new Set(entries); + readRefs.set(refCounter++, out); + return out; + } + case I_MAP_SMALL: + case I_MAP_LARGE: { + const entries = []; + const size = type === I_MAP_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + const k = _deserialize(); + const v = _deserialize(); + entries.push([ k, v ]); + } + const out = new Map(entries); + readRefs.set(refCounter++, out); + return out; + } + case I_ARRAYBUFFER: { + const byteLength = deserializeLargeUint(); + const maxByteLength = _deserialize(); + let options; + if ( maxByteLength !== 0 && maxByteLength !== byteLength ) { + options = { maxByteLength }; + } + const arrbuf = new ArrayBuffer(byteLength, options); + const dense = _deserialize(); + const str = _deserialize(); + if ( dense ) { + denseArrayBufferFromStr(str, arrbuf); + } else { + sparseArrayBufferFromStr(str, arrbuf); + } + readRefs.set(refCounter++, arrbuf); + return arrbuf; + } + case I_INT8ARRAY: + case I_UINT8ARRAY: + case I_UINT8CLAMPEDARRAY: + case I_INT16ARRAY: + case I_UINT16ARRAY: + case I_INT32ARRAY: + case I_UINT32ARRAY: + case I_FLOAT32ARRAY: + case I_FLOAT64ARRAY: + case I_DATAVIEW: { + const byteOffset = deserializeLargeUint(); + const length = deserializeLargeUint(); + const arrayBuffer = _deserialize(); + const ctor = toArrayBufferViewConstructor[`${type}`]; + const out = new ctor(arrayBuffer, byteOffset, length); + readRefs.set(refCounter++, out); + return out; + } + default: + break; + } + readPtr = FAILMARK; +}; + +/******************************************************************************* + * + * LZ4 block compression/decompression + * + * Imported from: + * https://github.com/gorhill/lz4-wasm/blob/8995cdef7b/dist/lz4-block-codec-js.js + * + * Customized to avoid external dependencies as I entertain the idea of + * spinning off the serializer as a standalone utility for all to use. + * + * */ + +class LZ4BlockJS { + constructor() { + this.hashTable = undefined; + this.outputBuffer = undefined; + } + reset() { + this.hashTable = undefined; + this.outputBuffer = undefined; + } + growOutputBuffer(size) { + if ( this.outputBuffer !== undefined ) { + if ( this.outputBuffer.byteLength >= size ) { return; } + } + this.outputBuffer = new ArrayBuffer(size + 0xFFFF & 0x7FFF0000); + } + encodeBound(size) { + return size > 0x7E000000 ? 0 : size + (size / 255 | 0) + 16; + } + encodeBlock(iBuf, oOffset) { + const iLen = iBuf.byteLength; + if ( iLen >= 0x7E000000 ) { throw new RangeError(); } + // "The last match must start at least 12 bytes before end of block" + const lastMatchPos = iLen - 12; + // "The last 5 bytes are always literals" + const lastLiteralPos = iLen - 5; + if ( this.hashTable === undefined ) { + this.hashTable = new Int32Array(65536); + } + this.hashTable.fill(-65536); + if ( isInstanceOf(iBuf, 'ArrayBuffer') ) { + iBuf = new Uint8Array(iBuf); + } + const oLen = oOffset + this.encodeBound(iLen); + this.growOutputBuffer(oLen); + const oBuf = new Uint8Array(this.outputBuffer, 0, oLen); + let iPos = 0; + let oPos = oOffset; + let anchorPos = 0; + // sequence-finding loop + for (;;) { + let refPos; + let mOffset; + let sequence = iBuf[iPos] << 8 | iBuf[iPos+1] << 16 | iBuf[iPos+2] << 24; + // match-finding loop + while ( iPos <= lastMatchPos ) { + sequence = sequence >>> 8 | iBuf[iPos+3] << 24; + const hash = (sequence * 0x9E37 & 0xFFFF) + (sequence * 0x79B1 >>> 16) & 0xFFFF; + refPos = this.hashTable[hash]; + this.hashTable[hash] = iPos; + mOffset = iPos - refPos; + if ( + mOffset < 65536 && + iBuf[refPos+0] === ((sequence ) & 0xFF) && + iBuf[refPos+1] === ((sequence >>> 8) & 0xFF) && + iBuf[refPos+2] === ((sequence >>> 16) & 0xFF) && + iBuf[refPos+3] === ((sequence >>> 24) & 0xFF) + ) { + break; + } + iPos += 1; + } + // no match found + if ( iPos > lastMatchPos ) { break; } + // match found + let lLen = iPos - anchorPos; + let mLen = iPos; + iPos += 4; refPos += 4; + while ( iPos < lastLiteralPos && iBuf[iPos] === iBuf[refPos] ) { + iPos += 1; refPos += 1; + } + mLen = iPos - mLen; + const token = mLen < 19 ? mLen - 4 : 15; + // write token, length of literals if needed + if ( lLen >= 15 ) { + oBuf[oPos++] = 0xF0 | token; + let l = lLen - 15; + while ( l >= 255 ) { + oBuf[oPos++] = 255; + l -= 255; + } + oBuf[oPos++] = l; + } else { + oBuf[oPos++] = (lLen << 4) | token; + } + // write literals + while ( lLen-- ) { + oBuf[oPos++] = iBuf[anchorPos++]; + } + if ( mLen === 0 ) { break; } + // write offset of match + oBuf[oPos+0] = mOffset; + oBuf[oPos+1] = mOffset >>> 8; + oPos += 2; + // write length of match if needed + if ( mLen >= 19 ) { + let l = mLen - 19; + while ( l >= 255 ) { + oBuf[oPos++] = 255; + l -= 255; + } + oBuf[oPos++] = l; + } + anchorPos = iPos; + } + // last sequence is literals only + let lLen = iLen - anchorPos; + if ( lLen >= 15 ) { + oBuf[oPos++] = 0xF0; + let l = lLen - 15; + while ( l >= 255 ) { + oBuf[oPos++] = 255; + l -= 255; + } + oBuf[oPos++] = l; + } else { + oBuf[oPos++] = lLen << 4; + } + while ( lLen-- ) { + oBuf[oPos++] = iBuf[anchorPos++]; + } + return new Uint8Array(oBuf.buffer, 0, oPos); + } + decodeBlock(iBuf, iOffset, oLen) { + const iLen = iBuf.byteLength; + this.growOutputBuffer(oLen); + const oBuf = new Uint8Array(this.outputBuffer, 0, oLen); + let iPos = iOffset, oPos = 0; + while ( iPos < iLen ) { + const token = iBuf[iPos++]; + // literals + let clen = token >>> 4; + // length of literals + if ( clen !== 0 ) { + if ( clen === 15 ) { + let l; + for (;;) { + l = iBuf[iPos++]; + if ( l !== 255 ) { break; } + clen += 255; + } + clen += l; + } + // copy literals + const end = iPos + clen; + while ( iPos < end ) { + oBuf[oPos++] = iBuf[iPos++]; + } + if ( iPos === iLen ) { break; } + } + // match + const mOffset = iBuf[iPos+0] | (iBuf[iPos+1] << 8); + if ( mOffset === 0 || mOffset > oPos ) { return; } + iPos += 2; + // length of match + clen = (token & 0x0F) + 4; + if ( clen === 19 ) { + let l; + for (;;) { + l = iBuf[iPos++]; + if ( l !== 255 ) { break; } + clen += 255; + } + clen += l; + } + // copy match + const end = oPos + clen; + let mPos = oPos - mOffset; + while ( oPos < end ) { + oBuf[oPos++] = oBuf[mPos++]; + } + } + return oBuf; + } + encode(input, outputOffset) { + if ( isInstanceOf(input, 'ArrayBuffer') ) { + input = new Uint8Array(input); + } else if ( isInstanceOf(input, 'Uint8Array') === false ) { + throw new TypeError(); + } + return this.encodeBlock(input, outputOffset); + } + decode(input, inputOffset, outputSize) { + if ( isInstanceOf(input, 'ArrayBuffer') ) { + input = new Uint8Array(input); + } else if ( isInstanceOf(input, 'Uint8Array') === false ) { + throw new TypeError(); + } + return this.decodeBlock(input, inputOffset, outputSize); + } +} + +/******************************************************************************* + * + * Synchronous APIs + * + * */ + +export const serialize = (data, options = {}) => { + refCounter = 1; + _serialize(data); + writeBuffer.unshift(MAGICPREFIX); + const s = writeBuffer.join(''); + writeRefs.clear(); + writeBuffer.length = 0; + if ( shouldCompress(s, options) === false ) { return s; } + const lz4Util = new LZ4BlockJS(); + const uint8ArrayBefore = textEncoder.encode(s); + const uint8ArrayAfter = lz4Util.encode(uint8ArrayBefore, 0); + const lz4 = { + size: uint8ArrayBefore.length, + data: new Uint8Array(uint8ArrayAfter), + }; + refCounter = 1; + _serialize(lz4); + writeBuffer.unshift(MAGICLZ4PREFIX); + const t = writeBuffer.join(''); + writeRefs.clear(); + writeBuffer.length = 0; + const ratio = t.length / s.length; + return ratio <= 0.85 ? t : s; +}; + +export const deserialize = s => { + if ( s.startsWith(MAGICLZ4PREFIX) ) { + refCounter = 1; + readStr = s; + readEnd = s.length; + readPtr = MAGICLZ4PREFIX.length; + const lz4 = _deserialize(); + readRefs.clear(); + readStr = ''; + const lz4Util = new LZ4BlockJS(); + const uint8ArrayAfter = lz4Util.decode(lz4.data, 0, lz4.size); + s = textDecoder.decode(new Uint8Array(uint8ArrayAfter)); + } + if ( s.startsWith(MAGICPREFIX) === false ) { return; } + refCounter = 1; + readStr = s; + readEnd = s.length; + readPtr = MAGICPREFIX.length; + const data = _deserialize(); + readRefs.clear(); + readStr = ''; + uint8Input = null; + if ( readPtr === FAILMARK ) { return; } + return data; +}; + +export const isSerialized = s => + typeof s === 'string' && + (s.startsWith(MAGICLZ4PREFIX) || s.startsWith(MAGICPREFIX)); + +export const isCompressed = s => + typeof s === 'string' && s.startsWith(MAGICLZ4PREFIX); + +/******************************************************************************* + * + * Configuration + * + * */ + +const defaultConfig = { + threadTTL: 3000, +}; + +const validateConfig = { + threadTTL: val => val > 0, +}; + +const currentConfig = Object.assign({}, defaultConfig); + +export const getConfig = ( ) => Object.assign({}, currentConfig); + +export const setConfig = config => { + for ( const key in Object.keys(config) ) { + if ( defaultConfig.hasOwnProperty(key) === false ) { continue; } + const val = config[key]; + if ( typeof val !== typeof defaultConfig[key] ) { continue; } + if ( (validateConfig[key])(val) === false ) { continue; } + currentConfig[key] = val; + } +}; + +/******************************************************************************* + * + * Asynchronous APIs + * + * Being asynchronous allows to support workers and future features such as + * checksums. + * + * */ + +const THREAD_AREYOUREADY = 1; +const THREAD_IAMREADY = 2; +const THREAD_SERIALIZE = 3; +const THREAD_DESERIALIZE = 4; + +class MainThread { + constructor() { + this.name = 'main'; + this.jobs = []; + this.workload = 0; + this.timer = undefined; + this.busy = 2; + } + + process() { + if ( this.jobs.length === 0 ) { return; } + const job = this.jobs.shift(); + this.workload -= job.size; + const result = job.what === THREAD_SERIALIZE + ? serialize(job.data, job.options) + : deserialize(job.data); + job.resolve(result); + this.processAsync(); + if ( this.jobs.length === 0 ) { + this.busy = 2; + } else if ( this.busy > 2 ) { + this.busy -= 1; + } + } + + processAsync() { + if ( this.timer !== undefined ) { return; } + if ( this.jobs.length === 0 ) { return; } + this.timer = globalThis.requestIdleCallback(deadline => { + this.timer = undefined; + globalThis.queueMicrotask(( ) => { + this.process(); + }); + if ( deadline.timeRemaining() === 0 ) { + this.busy += 1; + } + }, { timeout: 5 }); + } + + serialize(data, options) { + return new Promise(resolve => { + this.workload += 1; + this.jobs.push({ what: THREAD_SERIALIZE, data, options, size: 1, resolve }); + this.processAsync(); + }); + } + + deserialize(data, options) { + return new Promise(resolve => { + const size = data.length; + this.workload += size; + this.jobs.push({ what: THREAD_DESERIALIZE, data, options, size, resolve }); + this.processAsync(); + }); + } + + get queueSize() { + return this.jobs.length; + } + + get workSize() { + return this.workload * this.busy; + } +} + +class Thread { + constructor(gcer) { + this.name = 'worker'; + this.jobs = new Map(); + this.jobIdGenerator = 1; + this.workload = 0; + this.workerAccessTime = 0; + this.workerTimer = undefined; + this.gcer = gcer; + this.workerPromise = new Promise(resolve => { + let worker = null; + try { + worker = new Worker('js/s14e-serializer.js', { type: 'module' }); + worker.onmessage = ev => { + const msg = ev.data; + if ( isInstanceOf(msg, 'Object') === false ) { return; } + if ( msg.what === THREAD_IAMREADY ) { + worker.onmessage = ev => { this.onmessage(ev); }; + worker.onerror = null; + resolve(worker); + } + }; + worker.onerror = ( ) => { + worker.onmessage = worker.onerror = null; + resolve(null); + }; + worker.postMessage({ + what: THREAD_AREYOUREADY, + config: currentConfig, + }); + } catch(ex) { + console.info(ex); + worker.onmessage = worker.onerror = null; + resolve(null); + } + }); + } + + countdownWorker() { + if ( this.workerTimer !== undefined ) { return; } + this.workerTimer = setTimeout(async ( ) => { + this.workerTimer = undefined; + if ( this.jobs.size !== 0 ) { return; } + const idleTime = Date.now() - this.workerAccessTime; + if ( idleTime < currentConfig.threadTTL ) { + return this.countdownWorker(); + } + const worker = await this.workerPromise; + if ( this.jobs.size !== 0 ) { return; } + this.gcer(this); + if ( worker === null ) { return; } + worker.onmessage = worker.onerror = null; + worker.terminate(); + }, currentConfig.threadTTL); + } + + onmessage(ev) { + this.ondone(ev.data); + } + + ondone(job) { + const resolve = this.jobs.get(job.id); + if ( resolve === undefined ) { return; } + this.jobs.delete(job.id); + resolve(job.result); + this.workload -= job.size; + if ( this.jobs.size !== 0 ) { return; } + this.countdownWorker(); + } + + async serialize(data, options) { + return new Promise(resolve => { + const id = this.jobIdGenerator++; + this.workload += 1; + this.jobs.set(id, resolve); + return this.workerPromise.then(worker => { + this.workerAccessTime = Date.now(); + if ( worker === null ) { + this.ondone({ id, result: serialize(data, options), size: 1 }); + } else { + worker.postMessage({ what: THREAD_SERIALIZE, id, data, options, size: 1 }); + } + }); + }); + } + + async deserialize(data, options) { + return new Promise(resolve => { + const id = this.jobIdGenerator++; + const size = data.length; + this.workload += size; + this.jobs.set(id, resolve); + return this.workerPromise.then(worker => { + this.workerAccessTime = Date.now(); + if ( worker === null ) { + this.ondone({ id, result: deserialize(data, options), size }); + } else { + worker.postMessage({ what: THREAD_DESERIALIZE, id, data, options, size }); + } + }); + }); + } + + get queueSize() { + return this.jobs.size; + } + + get workSize() { + return this.workload; + } +} + +const threads = { + pool: [ new MainThread() ], + thread(maxPoolSize) { + const poolSize = this.pool.length; + if ( poolSize !== 0 && poolSize >= maxPoolSize ) { + if ( poolSize === 1 ) { return this.pool[0]; } + return this.pool.reduce((a, b) => { + //console.log(`${a.name}: q=${a.queueSize} w=${a.workSize} ${b.name}: q=${b.queueSize} w=${b.workSize}`); + if ( b.queueSize === 0 ) { return b; } + if ( a.queueSize === 0 ) { return a; } + return b.workSize < a.workSize ? b : a; + }); + } + const thread = new Thread(thread => { + const pos = this.pool.indexOf(thread); + if ( pos === -1 ) { return; } + this.pool.splice(pos, 1); + }); + this.pool.push(thread); + return thread; + }, +}; + +export async function serializeAsync(data, options = {}) { + const maxThreadCount = options.multithreaded || 0; + if ( maxThreadCount === 0 ) { + return serialize(data, options); + } + const thread = threads.thread(maxThreadCount); + //console.log(`serializeAsync: thread=${thread.name} workload=${thread.workSize}`); + const result = await thread.serialize(data, options); + if ( result !== undefined ) { return result; } + return serialize(data, options); +} + +export async function deserializeAsync(data, options = {}) { + if ( isSerialized(data) === false ) { return data; } + const maxThreadCount = options.multithreaded || 0; + if ( maxThreadCount === 0 ) { + return deserialize(data, options); + } + const thread = threads.thread(maxThreadCount); + //console.log(`deserializeAsync: thread=${thread.name} data=${data.length} workload=${thread.workSize}`); + const result = await thread.deserialize(data, options); + if ( result !== undefined ) { return result; } + return deserialize(data, options); +} + +/******************************************************************************* + * + * Worker-only code + * + * */ + +if ( isInstanceOf(globalThis, 'DedicatedWorkerGlobalScope') ) { + globalThis.onmessage = ev => { + const msg = ev.data; + switch ( msg.what ) { + case THREAD_AREYOUREADY: + setConfig(msg.config); + globalThis.postMessage({ what: THREAD_IAMREADY }); + break; + case THREAD_SERIALIZE: + const result = serialize(msg.data, msg.options); + globalThis.postMessage({ id: msg.id, size: msg.size, result }); + break; + case THREAD_DESERIALIZE: { + const result = deserialize(msg.data); + globalThis.postMessage({ id: msg.id, size: msg.size, result }); + break; + } + } + }; +} + +/******************************************************************************/ diff --git a/src/js/scriptlet-filtering-core.js b/src/js/scriptlet-filtering-core.js index 125eb87..907844f 100644 --- a/src/js/scriptlet-filtering-core.js +++ b/src/js/scriptlet-filtering-core.js @@ -98,10 +98,18 @@ const patchScriptlet = (content, arglist) => { ); }; +const requote = s => { + if ( /^(["'`]).+\1$|,/.test(s) === false ) { return s; } + if ( s.includes("'") === false ) { return `'${s}'`; } + if ( s.includes('"') === false ) { return `"${s}"`; } + if ( s.includes('`') === false ) { return `\`${s}\``; } + return `'${s.replace(/'/g, "\\'")}'`; +}; + const decompile = json => { - const args = JSON.parse(json).map(s => s.replace(/,/g, '\\,')); + const args = JSON.parse(json); if ( args.length === 0 ) { return '+js()'; } - return `+js(${args.join(', ')})`; + return `+js(${args.map(s => requote(s)).join(', ')})`; }; /******************************************************************************/ @@ -192,7 +200,7 @@ export class ScriptletFilteringEngine { } fromSelfie(selfie) { - if ( selfie instanceof Object === false ) { return false; } + if ( typeof selfie !== 'object' || selfie === null ) { return false; } if ( selfie.version !== VERSION ) { return false; } this.scriptletDB.fromSelfie(selfie); return true; @@ -251,16 +259,10 @@ export class ScriptletFilteringEngine { $mainWorldMap.clear(); $isolatedWorldMap.clear(); - if ( scriptletDetails.mainWorld === '' ) { - if ( scriptletDetails.isolatedWorld === '' ) { - return { filters: scriptletDetails.filters }; - } - } - - const scriptletGlobals = options.scriptletGlobals || []; + const scriptletGlobals = options.scriptletGlobals || {}; if ( options.debug ) { - scriptletGlobals.push([ 'canDebug', true ]); + scriptletGlobals.canDebug = true; } return { @@ -271,7 +273,7 @@ export class ScriptletFilteringEngine { options.debugScriptlets ? 'debugger;' : ';', '', // For use by scriptlets to share local data among themselves - `const scriptletGlobals = new Map(${JSON.stringify(scriptletGlobals, null, 2)});`, + `const scriptletGlobals = ${JSON.stringify(scriptletGlobals, null, 4)}`, '', scriptletDetails.mainWorld, '', @@ -285,7 +287,7 @@ export class ScriptletFilteringEngine { options.debugScriptlets ? 'debugger;' : ';', '', // For use by scriptlets to share local data among themselves - `const scriptletGlobals = new Map(${JSON.stringify(scriptletGlobals, null, 2)});`, + `const scriptletGlobals = ${JSON.stringify(scriptletGlobals, null, 4)}`, '', scriptletDetails.isolatedWorld, '', diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 10da19f..7da840d 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -44,13 +44,6 @@ import { const contentScriptRegisterer = new (class { constructor() { this.hostnameToDetails = new Map(); - if ( browser.contentScripts === undefined ) { return; } - onBroadcast(msg => { - if ( msg.what !== 'filteringBehaviorChanged' ) { return; } - if ( msg.direction > 0 ) { return; } - if ( msg.hostname ) { return this.flush(msg.hostname); } - this.reset(); - }); } register(hostname, code) { if ( browser.contentScripts === undefined ) { return false; } @@ -78,6 +71,7 @@ const contentScriptRegisterer = new (class { return false; } unregister(hostname) { + if ( hostname === '' ) { return; } if ( this.hostnameToDetails.size === 0 ) { return; } const details = this.hostnameToDetails.get(hostname); if ( details === undefined ) { return; } @@ -85,6 +79,7 @@ const contentScriptRegisterer = new (class { this.unregisterHandle(details.handle); } flush(hostname) { + if ( hostname === '' ) { return; } if ( hostname === '*' ) { return this.reset(); } for ( const hn of this.hostnameToDetails.keys() ) { if ( hn.endsWith(hostname) === false ) { continue; } @@ -128,16 +123,15 @@ const mainWorldInjector = (( ) => { 'json-slot', ');', ]; + const jsonSlot = parts.indexOf('json-slot'); return { - parts, - jsonSlot: parts.indexOf('json-slot'), - assemble: function(hostname, scriptlets, filters) { - this.parts[this.jsonSlot] = JSON.stringify({ + assemble: function(hostname, details) { + parts[jsonSlot] = JSON.stringify({ hostname, - scriptlets, - filters, + scriptlets: details.mainWorld, + filters: details.filters, }); - return this.parts.join(''); + return parts.join(''); }, }; })(); @@ -160,24 +154,61 @@ const isolatedWorldInjector = (( ) => { 'json-slot', ');', ]; + const jsonSlot = parts.indexOf('json-slot'); return { - parts, - jsonSlot: parts.indexOf('json-slot'), - assemble: function(hostname, scriptlets) { - this.parts[this.jsonSlot] = JSON.stringify({ hostname }); - const code = this.parts.join(''); + assemble(hostname, details) { + parts[jsonSlot] = JSON.stringify({ hostname }); + const code = parts.join(''); // Manually substitute noop function with scriptlet wrapper // function, so as to not suffer instances of special // replacement characters `$`,`\` when using String.replace() // with scriptlet code. const match = /function\(\)\{\}/.exec(code); return code.slice(0, match.index) + - scriptlets + + details.isolatedWorld + code.slice(match.index + match[0].length); }, }; })(); +const onScriptletMessageInjector = (( ) => { + const parts = [ + '(', + function(name) { + if ( self.uBO_bcSecret ) { return; } + const bcSecret = new self.BroadcastChannel(name); + bcSecret.onmessage = ev => { + const msg = ev.data; + switch ( typeof msg ) { + case 'string': + if ( msg !== 'areyouready?' ) { break; } + bcSecret.postMessage('iamready!'); + break; + case 'object': + if ( self.vAPI && self.vAPI.messaging ) { + self.vAPI.messaging.send('contentscript', msg); + } else { + console.log(`[uBO][${msg.type}]${msg.text}`); + } + break; + } + }; + bcSecret.postMessage('iamready!'); + self.uBO_bcSecret = bcSecret; + }.toString(), + ')(', + 'bcSecret-slot', + ');', + ]; + const bcSecretSlot = parts.indexOf('bcSecret-slot'); + return { + assemble(details) { + parts[bcSecretSlot] = JSON.stringify(details.bcSecret); + return parts.join('\n'); + }, + }; +})(); + /******************************************************************************/ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { @@ -187,10 +218,44 @@ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { this.warSecret = undefined; this.scriptletCache = new MRUCache(32); this.isDevBuild = undefined; - onBroadcast(msg => { - if ( msg.what !== 'hiddenSettingsChanged' ) { return; } - this.scriptletCache.reset(); - this.isDevBuild = undefined; + this.logLevel = 1; + this.bc = onBroadcast(msg => { + switch ( msg.what ) { + case 'filteringBehaviorChanged': { + const direction = msg.direction || 0; + if ( direction > 0 ) { return; } + if ( direction >= 0 && msg.hostname ) { + return contentScriptRegisterer.flush(msg.hostname); + } + contentScriptRegisterer.reset(); + break; + } + case 'hiddenSettingsChanged': + this.isDevBuild = undefined; + /* fall through */ + case 'loggerEnabled': + case 'loggerDisabled': + this.clearCache(); + break; + case 'loggerLevelChanged': + this.logLevel = msg.level; + vAPI.tabs.query({ + discarded: false, + url: [ 'http://*/*', 'https://*/*' ], + }).then(tabs => { + for ( const tab of tabs ) { + const { status } = tab; + if ( status !== 'loading' && status !== 'complete' ) { continue; } + vAPI.tabs.executeScript(tab.id, { + allFrames: true, + file: `/js/scriptlets/scriptlet-loglevel-${this.logLevel}.js`, + matchAboutBlank: true, + }); + } + }); + this.clearCache(); + break; + } }); } @@ -208,6 +273,11 @@ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { contentScriptRegisterer.reset(); } + clearCache() { + this.scriptletCache.reset(); + contentScriptRegisterer.reset(); + } + retrieve(request) { const { hostname } = request; @@ -238,58 +308,85 @@ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { this.warSecret = vAPI.warSecret.long(); } + const bcSecret = vAPI.generateSecret(3); + const options = { - scriptletGlobals: [ - [ 'warOrigin', this.warOrigin ], - [ 'warSecret', this.warSecret ], - ], + scriptletGlobals: { + warOrigin: this.warOrigin, + warSecret: this.warSecret, + }, debug: this.isDevBuild, debugScriptlets: µb.hiddenSettings.debugScriptlets, }; + if ( logger.enabled ) { + options.scriptletGlobals.bcSecret = bcSecret; + options.scriptletGlobals.logLevel = this.logLevel; + } scriptletDetails = super.retrieve(request, options); - this.scriptletCache.add(hostname, scriptletDetails || null); + if ( scriptletDetails === undefined ) { + if ( request.nocache !== true ) { + this.scriptletCache.add(hostname, null); + } + return; + } + + const contentScript = []; + if ( scriptletDetails.mainWorld ) { + contentScript.push(mainWorldInjector.assemble(hostname, scriptletDetails)); + } + if ( scriptletDetails.isolatedWorld ) { + contentScript.push(isolatedWorldInjector.assemble(hostname, scriptletDetails)); + } + + const cachedScriptletDetails = { + bcSecret, + code: contentScript.join('\n\n'), + filters: scriptletDetails.filters, + }; - return scriptletDetails; + if ( request.nocache !== true ) { + this.scriptletCache.add(hostname, cachedScriptletDetails); + } + + return cachedScriptletDetails; } injectNow(details) { if ( typeof details.frameId !== 'number' ) { return; } - const request = { + const hostname = hostnameFromURI(details.url); + const domain = domainFromHostname(hostname); + + const scriptletDetails = this.retrieve({ tabId: details.tabId, frameId: details.frameId, url: details.url, - hostname: hostnameFromURI(details.url), - domain: undefined, - entity: undefined - }; - - request.domain = domainFromHostname(request.hostname); - request.entity = entityFromDomain(request.domain); - - const scriptletDetails = this.retrieve(request); + hostname, + domain, + entity: entityFromDomain(domain), + }); if ( scriptletDetails === undefined ) { - contentScriptRegisterer.unregister(request.hostname); + contentScriptRegisterer.unregister(hostname); return; } - - const contentScript = []; - if ( µb.hiddenSettings.debugScriptletInjector ) { - contentScript.push('debugger'); + if ( Boolean(scriptletDetails.code) === false ) { + return scriptletDetails; } - const { mainWorld = '', isolatedWorld = '', filters } = scriptletDetails; - if ( mainWorld !== '' ) { - contentScript.push(mainWorldInjector.assemble(request.hostname, mainWorld, filters)); + + const contentScript = [ scriptletDetails.code ]; + if ( logger.enabled ) { + contentScript.unshift( + onScriptletMessageInjector.assemble(scriptletDetails) + ); } - if ( isolatedWorld !== '' ) { - contentScript.push(isolatedWorldInjector.assemble(request.hostname, isolatedWorld)); + if ( µb.hiddenSettings.debugScriptletInjector ) { + contentScript.unshift('debugger'); } - const code = contentScript.join('\n\n'); - const isAlreadyInjected = contentScriptRegisterer.register(request.hostname, code); + const isAlreadyInjected = contentScriptRegisterer.register(hostname, code); if ( isAlreadyInjected !== true ) { vAPI.tabs.executeScript(details.tabId, { code, @@ -298,7 +395,6 @@ export class ScriptletFilteringEngineEx extends ScriptletFilteringEngine { runAt: 'document_start', }); } - return scriptletDetails; } diff --git a/src/js/scriptlets/epicker.js b/src/js/scriptlets/epicker.js index 80489e8..41b0b76 100644 --- a/src/js/scriptlets/epicker.js +++ b/src/js/scriptlets/epicker.js @@ -619,6 +619,21 @@ const filterToDOMInterface = (( ) => { const reCaret = '(?:[^%.0-9a-z_-]|$)'; const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/; + const matchElemToRegex = (elem, re) => { + const srcProp = netFilter1stSources[elem.localName]; + let src = elem[srcProp]; + if ( src instanceof SVGAnimatedString ) { + src = src.baseVal; + } + if ( typeof src === 'string' && /^https?:\/\//.test(src) ) { + if ( re.test(src) ) { return srcProp; } + } + src = elem.currentSrc; + if ( typeof src === 'string' && /^https?:\/\//.test(src) ) { + if ( re.test(src) ) { return srcProp; } + } + }; + // Net filters: we need to lookup manually -- translating into a foolproof // CSS selector is just not possible. // @@ -672,28 +687,21 @@ const filterToDOMInterface = (( ) => { // Lookup by tag names. // https://github.com/uBlockOrigin/uBlock-issues/issues/2260 // Maybe get to the actual URL indirectly. + // + // https://github.com/uBlockOrigin/uBlock-issues/issues/3142 + // Don't try to match against non-network URIs. const elems = document.querySelectorAll( Object.keys(netFilter1stSources).join() ); for ( const elem of elems ) { - const srcProp = netFilter1stSources[elem.localName]; - let src = elem[srcProp]; - if ( src instanceof SVGAnimatedString ) { - src = src.baseVal; - } - if ( - typeof src === 'string' && - reFilter.test(src) || - typeof elem.currentSrc === 'string' && - reFilter.test(elem.currentSrc) - ) { - out.push({ - elem, - src: srcProp, - opt: filterTypes[elem.localName], - style: vAPI.hideStyle, - }); - } + const srcProp = matchElemToRegex(elem, reFilter); + if ( srcProp === undefined ) { continue; } + out.push({ + elem, + src: srcProp, + opt: filterTypes[elem.localName], + style: vAPI.hideStyle, + }); } // Find matching background image in current set of candidate elements. @@ -1247,6 +1255,7 @@ const pickerCSSStyle = [ 'display: block', 'filter: none', 'height: 100vh', + ' height: 100svh', 'left: 0', 'margin: 0', 'max-height: none', diff --git a/src/js/scriptlets/scriptlet-loglevel-1.js b/src/js/scriptlets/scriptlet-loglevel-1.js new file mode 100644 index 0000000..bc5f4bb --- /dev/null +++ b/src/js/scriptlets/scriptlet-loglevel-1.js @@ -0,0 +1,49 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2024-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + if ( self.uBO_bcSecret instanceof self.BroadcastChannel === false ) { return; } + self.uBO_bcSecret.postMessage('setScriptletLogLevelToOne'); +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/js/scriptlets/scriptlet-loglevel-2.js b/src/js/scriptlets/scriptlet-loglevel-2.js new file mode 100644 index 0000000..d8afefd --- /dev/null +++ b/src/js/scriptlets/scriptlet-loglevel-2.js @@ -0,0 +1,49 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2024-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + if ( self.uBO_bcSecret instanceof self.BroadcastChannel === false ) { return; } + self.uBO_bcSecret.postMessage('setScriptletLogLevelToTwo'); +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/js/scriptlets/should-inject-contentscript.js b/src/js/scriptlets/should-inject-contentscript.js index b9a2658..94d0cd3 100644 --- a/src/js/scriptlets/should-inject-contentscript.js +++ b/src/js/scriptlets/should-inject-contentscript.js @@ -29,7 +29,7 @@ (( ) => { try { - let status = vAPI.uBO !== true; + const status = vAPI.uBO !== true; if ( status === false && vAPI.bootstrap ) { self.requestIdleCallback(( ) => vAPI && vAPI.bootstrap()); } diff --git a/src/js/settings.js b/src/js/settings.js index deb033f..fc0ea68 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -27,7 +27,7 @@ import { setAccentColor, setTheme } from './theme.js'; /******************************************************************************/ -const handleImportFilePicker = function() { +function handleImportFilePicker() { const file = this.files[0]; if ( file === undefined || file.name === '' ) { return; } @@ -88,22 +88,22 @@ const handleImportFilePicker = function() { }; fr.readAsText(file); -}; +} /******************************************************************************/ -const startImportFilePicker = function() { +function startImportFilePicker() { const input = qs$('#restoreFilePicker'); // Reset to empty string, this will ensure an change event is properly // triggered if the user pick a file, even if it is the same as the last // one picked. input.value = ''; input.click(); -}; +} /******************************************************************************/ -const exportToFile = async function() { +async function exportToFile() { const response = await vAPI.messaging.send('dashboard', { what: 'backupUserData', }); @@ -119,11 +119,11 @@ const exportToFile = async function() { 'filename': response.localData.lastBackupFile }); onLocalDataReceived(response.localData); -}; +} /******************************************************************************/ -const onLocalDataReceived = function(details) { +function onLocalDataReceived(details) { let v, unit; if ( typeof details.storageUsed === 'number' ) { v = details.storageUsed; @@ -187,32 +187,32 @@ const onLocalDataReceived = function(details) { dom.attr('[data-setting-name="hyperlinkAuditingDisabled"]', 'disabled', ''); dom.attr('[data-setting-name="webrtcIPAddressHidden"]', 'disabled', ''); } -}; +} /******************************************************************************/ -const resetUserData = function() { +function resetUserData() { const msg = i18n$('aboutResetDataConfirm'); const proceed = window.confirm(msg); if ( proceed !== true ) { return; } vAPI.messaging.send('dashboard', { what: 'resetUserData', }); -}; +} /******************************************************************************/ -const synchronizeDOM = function() { +function synchronizeDOM() { dom.cl.toggle( dom.body, 'advancedUser', qs$('[data-setting-name="advancedUserEnabled"]').checked === true ); -}; +} /******************************************************************************/ -const changeUserSettings = function(name, value) { +function changeUserSettings(name, value) { vAPI.messaging.send('dashboard', { what: 'userSettings', name, @@ -235,11 +235,11 @@ const changeUserSettings = function(name, value) { default: break; } -}; +} /******************************************************************************/ -const onValueChanged = function(ev) { +function onValueChanged(ev) { const input = ev.target; const name = dom.attr(input, 'data-setting-name'); let value = input.value; @@ -256,14 +256,20 @@ const onValueChanged = function(ev) { } changeUserSettings(name, value); -}; +} /******************************************************************************/ // TODO: use data-* to declare simple settings -const onUserSettingsReceived = function(details) { +function onUserSettingsReceived(details) { const checkboxes = qsa$('[data-setting-type="bool"]'); + const onchange = ev => { + const checkbox = ev.target; + const name = checkbox.dataset.settingName || ''; + changeUserSettings(name, checkbox.checked); + synchronizeDOM(); + }; for ( const checkbox of checkboxes ) { const name = dom.attr(checkbox, 'data-setting-name') || ''; if ( details[name] === undefined ) { @@ -272,10 +278,7 @@ const onUserSettingsReceived = function(details) { continue; } checkbox.checked = details[name] === true; - dom.on(checkbox, 'change', ( ) => { - changeUserSettings(name, checkbox.checked); - synchronizeDOM(); - }); + dom.on(checkbox, 'change', onchange); } if ( details.canLeakLocalIPAddresses === true ) { @@ -295,6 +298,14 @@ const onUserSettingsReceived = function(details) { dom.on('#restoreFilePicker', 'change', handleImportFilePicker); synchronizeDOM(); +} + +/******************************************************************************/ + +self.wikilink = 'https://github.com/gorhill/uBlock/wiki/Dashboard:-Settings'; + +self.hasUnsavedData = function() { + return false; }; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index 5762619..46a052f 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -63,6 +63,11 @@ import { /******************************************************************************/ +let lastVersionInt = 0; +let thisVersionInt = 0; + +/******************************************************************************/ + vAPI.app.onShutdown = ( ) => { staticFilteringReverseLookup.shutdown(); io.updateStop(); @@ -76,6 +81,10 @@ vAPI.app.onShutdown = ( ) => { permanentSwitches.reset(); }; +vAPI.alarms.onAlarm.addListener(alarm => { + µb.alarmQueue.push(alarm.name); +}); + /******************************************************************************/ // This is called only once, when everything has been loaded in memory after @@ -139,22 +148,29 @@ const initializeTabs = async ( ) => { // https://www.reddit.com/r/uBlockOrigin/comments/s7c9go/ // Abort suspending network requests when uBO is merely being installed. -const onVersionReady = lastVersion => { - if ( lastVersion === vAPI.app.version ) { return; } +const onVersionReady = async lastVersion => { + lastVersionInt = vAPI.app.intFromVersion(lastVersion); + thisVersionInt = vAPI.app.intFromVersion(vAPI.app.version); + if ( thisVersionInt === lastVersionInt ) { return; } vAPI.storage.set({ version: vAPI.app.version, versionUpdateTime: Date.now(), }); - const lastVersionInt = vAPI.app.intFromVersion(lastVersion); - // Special case: first installation if ( lastVersionInt === 0 ) { vAPI.net.unsuspend({ all: true, discard: true }); return; } + // Remove cache items with obsolete names + if ( lastVersionInt < vAPI.app.intFromVersion('1.56.1b5') ) { + io.remove(`compiled/${µb.pslAssetKey}`); + io.remove('compiled/redirectEngine/resources'); + io.remove('selfie/main'); + } + // Since built-in resources may have changed since last version, we // force a reload of all resources. redirectEngine.invalidateResourcesSelfie(io); @@ -162,11 +178,6 @@ const onVersionReady = lastVersion => { /******************************************************************************/ -// https://github.com/chrisaljoudi/uBlock/issues/226 -// Whitelist in memory. -// Whitelist parser needs PSL to be ready. -// gorhill 2014-12-15: not anymore -// // https://github.com/uBlockOrigin/uBlock-issues/issues/1433 // Allow admins to add their own trusted-site directives. @@ -174,16 +185,38 @@ const onNetWhitelistReady = (netWhitelistRaw, adminExtra) => { if ( typeof netWhitelistRaw === 'string' ) { netWhitelistRaw = netWhitelistRaw.split('\n'); } + + // Remove now obsolete built-in trusted directives + if ( lastVersionInt !== thisVersionInt ) { + if ( lastVersionInt < vAPI.app.intFromVersion('1.56.1b12') ) { + const obsolete = [ + 'about-scheme', + 'chrome-scheme', + 'edge-scheme', + 'opera-scheme', + 'vivaldi-scheme', + 'wyciwyg-scheme', + ]; + for ( const directive of obsolete ) { + const i = netWhitelistRaw.findIndex(s => + s === directive || s === `# ${directive}` + ); + if ( i === -1 ) { continue; } + netWhitelistRaw.splice(i, 1); + } + } + } + // Append admin-controlled trusted-site directives - if ( - adminExtra instanceof Object && - Array.isArray(adminExtra.trustedSiteDirectives) - ) { - for ( const directive of adminExtra.trustedSiteDirectives ) { - µb.netWhitelistDefault.push(directive); - netWhitelistRaw.push(directive); + if ( adminExtra instanceof Object ) { + if ( Array.isArray(adminExtra.trustedSiteDirectives) ) { + for ( const directive of adminExtra.trustedSiteDirectives ) { + µb.netWhitelistDefault.push(directive); + netWhitelistRaw.push(directive); + } } } + µb.netWhitelist = µb.whitelistFromArray(netWhitelistRaw); µb.netWhitelistModifyTime = Date.now(); }; @@ -221,8 +254,7 @@ const onUserSettingsReady = fetched => { fetched.importedLists.length === 0 && fetched.externalLists !== '' ) { - fetched.importedLists = - fetched.externalLists.trim().split(/[\n\r]+/); + fetched.importedLists = fetched.externalLists.trim().split(/[\n\r]+/); } fromFetch(µb.userSettings, fetched); @@ -252,19 +284,19 @@ const onUserSettingsReady = fetched => { // Wait for removal of invalid cached data to be completed. const onCacheSettingsReady = async (fetched = {}) => { + let selfieIsInvalid = false; if ( fetched.compiledMagic !== µb.systemSettings.compiledMagic ) { µb.compiledFormatChanged = true; - µb.selfieIsInvalid = true; + selfieIsInvalid = true; ubolog(`Serialized format of static filter lists changed`); } if ( fetched.selfieMagic !== µb.systemSettings.selfieMagic ) { - µb.selfieIsInvalid = true; + selfieIsInvalid = true; ubolog(`Serialized format of selfie changed`); } - if ( µb.selfieIsInvalid ) { - µb.selfieManager.destroy(); - cacheStorage.set(µb.systemSettings); - } + if ( selfieIsInvalid === false ) { return; } + µb.selfieManager.destroy({ janitor: true }); + cacheStorage.set(µb.systemSettings); }; /******************************************************************************/ @@ -303,12 +335,6 @@ const onHiddenSettingsReady = async ( ) => { ubolog(`WASM modules ready ${Date.now()-vAPI.T0} ms after launch`); }); } - - // Maybe override default cache storage - µb.supportStats.cacheBackend = await cacheStorage.select( - µb.hiddenSettings.cacheStorageAPI - ); - ubolog(`Backend storage for cache will be ${µb.supportStats.cacheBackend}`); }; /******************************************************************************/ @@ -322,7 +348,6 @@ const onFirstFetchReady = (fetched, adminExtra) => { } // Order is important -- do not change: - fromFetch(µb.localSettings, fetched); fromFetch(µb.restoreBackupSettings, fetched); permanentFirewall.fromString(fetched.dynamicFilteringString); @@ -333,7 +358,6 @@ const onFirstFetchReady = (fetched, adminExtra) => { sessionSwitches.assign(permanentSwitches); onNetWhitelistReady(fetched.netWhitelist, adminExtra); - onVersionReady(fetched.version); }; /******************************************************************************/ @@ -358,14 +382,9 @@ const createDefaultProps = ( ) => { 'dynamicFilteringString': µb.dynamicFilteringDefault.join('\n'), 'urlFilteringString': '', 'hostnameSwitchesString': µb.hostnameSwitchesDefault.join('\n'), - 'lastRestoreFile': '', - 'lastRestoreTime': 0, - 'lastBackupFile': '', - 'lastBackupTime': 0, 'netWhitelist': µb.netWhitelistDefault, 'version': '0.0.0.0' }; - toFetch(µb.localSettings, fetchableProps); toFetch(µb.restoreBackupSettings, fetchableProps); return fetchableProps; }; @@ -389,23 +408,25 @@ try { const adminExtra = await vAPI.adminStorage.get('toAdd'); ubolog(`Extra admin settings ready ${Date.now()-vAPI.T0} ms after launch`); - // https://github.com/uBlockOrigin/uBlock-issues/issues/1365 - // Wait for onCacheSettingsReady() to be fully ready. - const [ , , lastVersion ] = await Promise.all([ + // Maybe override default cache storage + µb.supportStats.cacheBackend = await cacheStorage.select( + µb.hiddenSettings.cacheStorageAPI + ); + ubolog(`Backend storage for cache will be ${µb.supportStats.cacheBackend}`); + + await vAPI.storage.get(createDefaultProps()).then(async fetched => { + ubolog(`Version ready ${Date.now()-vAPI.T0} ms after launch`); + await onVersionReady(fetched.version); + return fetched; + }).then(fetched => { + ubolog(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); + onFirstFetchReady(fetched, adminExtra); + }); + + await Promise.all([ µb.loadSelectedFilterLists().then(( ) => { ubolog(`List selection ready ${Date.now()-vAPI.T0} ms after launch`); }), - cacheStorage.get( - { compiledMagic: 0, selfieMagic: 0 } - ).then(fetched => { - ubolog(`Cache magic numbers ready ${Date.now()-vAPI.T0} ms after launch`); - onCacheSettingsReady(fetched); - }), - vAPI.storage.get(createDefaultProps()).then(fetched => { - ubolog(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); - onFirstFetchReady(fetched, adminExtra); - return fetched.version; - }), µb.loadUserSettings().then(fetched => { ubolog(`User settings ready ${Date.now()-vAPI.T0} ms after launch`); onUserSettingsReady(fetched); @@ -413,10 +434,15 @@ try { µb.loadPublicSuffixList().then(( ) => { ubolog(`PSL ready ${Date.now()-vAPI.T0} ms after launch`); }), + cacheStorage.get({ compiledMagic: 0, selfieMagic: 0 }).then(bin => { + ubolog(`Cache magic numbers ready ${Date.now()-vAPI.T0} ms after launch`); + onCacheSettingsReady(bin); + }), + µb.loadLocalSettings(), ]); // https://github.com/uBlockOrigin/uBlock-issues/issues/1547 - if ( lastVersion === '0.0.0.0' && vAPI.webextFlavor.soup.has('chromium') ) { + if ( lastVersionInt === 0 && vAPI.webextFlavor.soup.has('chromium') ) { vAPI.app.restart(); return; } @@ -434,7 +460,7 @@ let selfieIsValid = false; try { selfieIsValid = await µb.selfieManager.load(); if ( selfieIsValid === true ) { - ubolog(`Selfie ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Loaded filtering engine from selfie ${Date.now()-vAPI.T0} ms after launch`); } } catch (ex) { console.trace(ex); @@ -471,15 +497,6 @@ webRequest.start(); // as possible ensure minimal memory usage baseline. lz4Codec.relinquish(); -// https://github.com/chrisaljoudi/uBlock/issues/184 -// Check for updates not too far in the future. -io.addObserver(µb.assetObserver.bind(µb)); -µb.scheduleAssetUpdater({ - updateDelay: µb.userSettings.autoUpdate - ? µb.hiddenSettings.autoUpdateDelayAfterLaunch * 1000 - : 0 -}); - // Force an update of the context menu according to the currently // active tab. contextMenu.update(); @@ -504,5 +521,47 @@ ubolog(`All ready ${µb.supportStats.allReadyAfter} after launch`); µb.isReadyResolve(); + +// https://github.com/chrisaljoudi/uBlock/issues/184 +// Check for updates not too far in the future. +io.addObserver(µb.assetObserver.bind(µb)); +if ( µb.userSettings.autoUpdate ) { + let needEmergencyUpdate = false; + const entries = await io.getUpdateAges({ + filters: µb.selectedFilterLists, + internal: [ '*' ], + }); + for ( const entry of entries ) { + if ( entry.ageNormalized < 2 ) { continue; } + needEmergencyUpdate = true; + break; + } + const updateDelay = needEmergencyUpdate + ? 2000 + : µb.hiddenSettings.autoUpdateDelayAfterLaunch * 1000; + µb.scheduleAssetUpdater({ + auto: true, + updateDelay, + fetchDelay: needEmergencyUpdate ? 1000 : undefined + }); +} + +// Process alarm queue +while ( µb.alarmQueue.length !== 0 ) { + const what = µb.alarmQueue.shift(); + ubolog(`Processing alarm event from suspended state: '${what}'`); + switch ( what ) { + case 'assetUpdater': + µb.scheduleAssetUpdater({ auto: true, updateDelay: 2000, fetchDelay : 1000 }); + break; + case 'createSelfie': + µb.selfieManager.create(); + break; + case 'saveLocalSettings': + µb.saveLocalSettings(); + break; + } +} + // <<<<< end of async/await scope })(); diff --git a/src/js/static-dnr-filtering.js b/src/js/static-dnr-filtering.js index fb677ad..ca66b86 100644 --- a/src/js/static-dnr-filtering.js +++ b/src/js/static-dnr-filtering.js @@ -299,10 +299,10 @@ function addToDNR(context, list) { if ( parser.isComment() ) { if ( line === `!#trusted on ${context.secret}` ) { - parser.trustedSource = true; + parser.options.trustedSource = true; context.trustedSource = true; } else if ( line === `!#trusted off ${context.secret}` ) { - parser.trustedSource = false; + parser.options.trustedSource = false; context.trustedSource = false; } continue; @@ -312,6 +312,8 @@ function addToDNR(context, list) { if ( parser.hasError() ) { if ( parser.astError === sfp.AST_ERROR_OPTION_EXCLUDED ) { context.invalid.add(`Incompatible with DNR: ${line}`); + } else { + context.invalid.add(`Rejected filter: ${line}`); } continue; } diff --git a/src/js/static-ext-filtering-db.js b/src/js/static-ext-filtering-db.js index 64a9c8d..e669c1e 100644 --- a/src/js/static-ext-filtering-db.js +++ b/src/js/static-ext-filtering-db.js @@ -141,8 +141,8 @@ const StaticExtFilteringHostnameDB = class { toSelfie() { return { version: this.version, - hostnameToSlotIdMap: Array.from(this.hostnameToSlotIdMap), - regexToSlotIdMap: Array.from(this.regexToSlotIdMap), + hostnameToSlotIdMap: this.hostnameToSlotIdMap, + regexToSlotIdMap: this.regexToSlotIdMap, hostnameSlots: this.hostnameSlots, strSlots: this.strSlots, size: this.size @@ -150,11 +150,11 @@ const StaticExtFilteringHostnameDB = class { } fromSelfie(selfie) { - if ( selfie === undefined ) { return; } - this.hostnameToSlotIdMap = new Map(selfie.hostnameToSlotIdMap); + if ( typeof selfie !== 'object' || selfie === null ) { return; } + this.hostnameToSlotIdMap = selfie.hostnameToSlotIdMap; // Regex-based lookup available in uBO 1.47.0 and above - if ( Array.isArray(selfie.regexToSlotIdMap) ) { - this.regexToSlotIdMap = new Map(selfie.regexToSlotIdMap); + if ( selfie.regexToSlotIdMap ) { + this.regexToSlotIdMap = selfie.regexToSlotIdMap; } this.hostnameSlots = selfie.hostnameSlots; this.strSlots = selfie.strSlots; diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js index 8a2905e..e616e63 100644 --- a/src/js/static-ext-filtering.js +++ b/src/js/static-ext-filtering.js @@ -26,9 +26,8 @@ import cosmeticFilteringEngine from './cosmetic-filtering.js'; import htmlFilteringEngine from './html-filtering.js'; import httpheaderFilteringEngine from './httpheader-filtering.js'; -import io from './assets.js'; -import logger from './logger.js'; import scriptletFilteringEngine from './scriptlet-filtering.js'; +import logger from './logger.js'; /******************************************************************************* @@ -147,34 +146,24 @@ staticExtFilteringEngine.fromCompiledContent = function(reader, options) { htmlFilteringEngine.fromCompiledContent(reader, options); }; -staticExtFilteringEngine.toSelfie = function(path) { - return io.put( - `${path}/main`, - JSON.stringify({ - cosmetic: cosmeticFilteringEngine.toSelfie(), - scriptlets: scriptletFilteringEngine.toSelfie(), - httpHeaders: httpheaderFilteringEngine.toSelfie(), - html: htmlFilteringEngine.toSelfie(), - }) - ); +staticExtFilteringEngine.toSelfie = function() { + return { + cosmetic: cosmeticFilteringEngine.toSelfie(), + scriptlets: scriptletFilteringEngine.toSelfie(), + httpHeaders: httpheaderFilteringEngine.toSelfie(), + html: htmlFilteringEngine.toSelfie(), + }; }; -staticExtFilteringEngine.fromSelfie = function(path) { - return io.get(`${path}/main`).then(details => { - let selfie; - try { - selfie = JSON.parse(details.content); - } catch (ex) { - } - if ( selfie instanceof Object === false ) { return false; } - cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); - httpheaderFilteringEngine.fromSelfie(selfie.httpHeaders); - htmlFilteringEngine.fromSelfie(selfie.html); - if ( scriptletFilteringEngine.fromSelfie(selfie.scriptlets) === false ) { - return false; - } - return true; - }); +staticExtFilteringEngine.fromSelfie = async function(selfie) { + if ( typeof selfie !== 'object' || selfie === null ) { return false; } + cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); + httpheaderFilteringEngine.fromSelfie(selfie.httpHeaders); + htmlFilteringEngine.fromSelfie(selfie.html); + if ( scriptletFilteringEngine.fromSelfie(selfie.scriptlets) === false ) { + return false; + } + return true; }; /******************************************************************************/ diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index eb8988b..48c5f62 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -896,7 +896,8 @@ export class AstFilterParser { this.reResponseheaderPattern = /^\^responseheader\(.*\)$/; this.rePatternScriptletJsonArgs = /^\{.*\}$/; this.reGoodRegexToken = /[^\x01%0-9A-Za-z][%0-9A-Za-z]{7,}|[^\x01%0-9A-Za-z][%0-9A-Za-z]{1,6}[^\x01%0-9A-Za-z]/; - this.reBadCSP = /(?:=|;)\s*report-(?:to|uri)\b/; + this.reBadCSP = /(?:^|[;,])\s*report-(?:to|uri)\b/i; + this.reBadPP = /(?:^|[;,])\s*report-to\b/i; this.reNoopOption = /^_+$/; this.scriptletArgListParser = new ArgListParser(','); } @@ -1298,6 +1299,7 @@ export class AstFilterParser { let modifierType = 0; let requestTypeCount = 0; let unredirectableTypeCount = 0; + let badfilter = false; for ( let i = 0, n = this.nodeTypeRegisterPtr; i < n; i++ ) { const type = this.nodeTypeRegister[i]; const targetNode = this.nodeTypeLookupTable[type]; @@ -1321,6 +1323,8 @@ export class AstFilterParser { realBad = hasValue; break; case NODE_TYPE_NET_OPTION_NAME_BADFILTER: + badfilter = true; + /* falls through */ case NODE_TYPE_NET_OPTION_NAME_NOOP: realBad = isNegated || hasValue; break; @@ -1400,7 +1404,11 @@ export class AstFilterParser { realBad = this.isRegexPattern() === false; break; case NODE_TYPE_NET_OPTION_NAME_PERMISSIONS: - realBad = modifierType !== 0 || (hasValue || isException) === false; + realBad = modifierType !== 0 || + (hasValue || isException) === false || + this.reBadPP.test( + this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_PERMISSIONS) + ); if ( realBad ) { break; } modifierType = type; break; @@ -1457,6 +1465,9 @@ export class AstFilterParser { this.addFlags(AST_FLAG_HAS_ERROR); } } + const requiresTrustedSource = ( ) => + this.options.trustedSource !== true && + isException === false && badfilter === false; switch ( modifierType ) { case NODE_TYPE_NET_OPTION_NAME_CNAME: realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; @@ -1484,7 +1495,7 @@ export class AstFilterParser { case NODE_TYPE_NET_OPTION_NAME_REPLACE: { realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; if ( realBad ) { break; } - if ( isException !== true && this.options.trustedSource !== true ) { + if ( requiresTrustedSource() ) { this.astError = AST_ERROR_UNTRUSTED_SOURCE; realBad = true; break; @@ -1496,20 +1507,21 @@ export class AstFilterParser { } break; } - case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: + case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: { realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; if ( realBad ) { break; } - if ( isException !== true && this.options.trustedSource !== true ) { + if ( requiresTrustedSource() ) { this.astError = AST_ERROR_UNTRUSTED_SOURCE; realBad = true; break; } const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM); - if ( parseReplaceValue(value) === undefined ) { + if ( value !== '' && parseReplaceValue(value) === undefined ) { this.astError = AST_ERROR_OPTION_BADVALUE; realBad = true; } break; + } case NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM: realBad = abstractTypeCount || behaviorTypeCount; break; @@ -3112,7 +3124,7 @@ class ExtSelectorCompiler { // context. const cssIdentifier = '[A-Za-z_][\\w-]*'; const cssClassOrId = `[.#]${cssIdentifier}`; - const cssAttribute = `\\[${cssIdentifier}(?:[*^$]?="[^"\\]\\\\]+")?\\]`; + const cssAttribute = `\\[${cssIdentifier}(?:[*^$]?="[^"\\]\\\\\\x09-\\x0D]+")?\\]`; const cssSimple = '(?:' + `${cssIdentifier}(?:${cssClassOrId})*(?:${cssAttribute})*` + '|' + @@ -3196,6 +3208,7 @@ class ExtSelectorCompiler { 'matches-path', 'min-text-length', 'others', + 'shadow', 'upward', 'watch-attr', 'xpath', @@ -3297,10 +3310,9 @@ class ExtSelectorCompiler { if ( this.astHasType(parts, 'Error') ) { return; } if ( this.astHasType(parts, 'Selector') === false ) { return; } if ( this.astIsValidSelectorList(parts) === false ) { return; } - if ( - this.astHasType(parts, 'ProceduralSelector') === false && - this.astHasType(parts, 'ActionSelector') === false - ) { + if ( this.astHasType(parts, 'ProceduralSelector') ) { + if ( this.astHasType(parts, 'PseudoElementSelector') ) { return; } + } else if ( this.astHasType(parts, 'ActionSelector') === false ) { return this.astSerialize(parts); } const r = this.astCompile(parts); @@ -3453,6 +3465,8 @@ class ExtSelectorCompiler { // https://github.com/uBlockOrigin/uBlock-issues/issues/2300 // Unquoted attribute values are parsed as Identifier instead of String. + // https://github.com/uBlockOrigin/uBlock-issues/issues/3127 + // Escape [\t\n\v\f\r] astSerializePart(part) { const out = []; const { data } = part; @@ -3468,7 +3482,14 @@ class ExtSelectorCompiler { if ( typeof value !== 'string' ) { value = data.value.name; } - value = value.replace(/["\\]/g, '\\$&'); + if ( /["\\]/.test(value) ) { + value = value.replace(/["\\]/g, '\\$&'); + } + if ( /[\x09-\x0D]/.test(value) ) { + value = value.replace(/[\x09-\x0D]/g, s => + `\\${s.charCodeAt(0).toString(16).toUpperCase()} ` + ); + } let flags = ''; if ( typeof data.flags === 'string' ) { if ( /^(is?|si?)$/.test(data.flags) === false ) { return; } @@ -3842,6 +3863,8 @@ class ExtSelectorCompiler { return this.compileText(arg); case 'remove-class': return this.compileText(arg); + case 'shadow': + return this.compileSelector(arg); case 'style': return this.compileStyleProperties(arg); case 'upward': @@ -3979,6 +4002,10 @@ class ExtSelectorCompiler { compileUpwardArgument(s) { const i = this.compileInteger(s, 1, 256); if ( i !== undefined ) { return i; } + return this.compilePlainSelector(s); + } + + compilePlainSelector(s) { const parts = this.astFromRaw(s, 'selectorList' ); if ( this.astIsValidSelectorList(parts) !== true ) { return; } if ( this.astHasType(parts, 'ProceduralSelector') ) { return; } @@ -4023,6 +4050,7 @@ class ExtSelectorCompiler { compileXpathExpression(s) { const r = this.unquoteString(s); if ( r.i !== s.length ) { return; } + if ( globalThis.document instanceof Object === false ) { return r.s; } try { globalThis.document.createExpression(r.s, null); } catch (e) { diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index d1e9a70..9a252fd 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -28,7 +28,6 @@ import { queueTask, dropTask } from './tasks.js'; import BidiTrieContainer from './biditrie.js'; import HNTrieContainer from './hntrie.js'; -import { sparseBase64 } from './base64-custom.js'; import { CompiledListReader } from './static-filtering-io.js'; import * as sfp from './static-filtering-parser.js'; @@ -493,17 +492,13 @@ const filterDataReset = ( ) => { filterData.fill(0); filterDataWritePtr = 2; }; -const filterDataToSelfie = ( ) => { - return JSON.stringify(Array.from(filterData.subarray(0, filterDataWritePtr))); -}; +const filterDataToSelfie = ( ) => + filterData.subarray(0, filterDataWritePtr); + const filterDataFromSelfie = selfie => { - if ( typeof selfie !== 'string' || selfie === '' ) { return false; } - const data = JSON.parse(selfie); - if ( Array.isArray(data) === false ) { return false; } - filterDataGrow(data.length); - filterDataWritePtr = data.length; - filterData.set(data); - filterDataShrink(); + if ( selfie instanceof Int32Array === false ) { return false; } + filterData = selfie; + filterDataWritePtr = selfie.length; return true; }; @@ -519,53 +514,15 @@ const filterRefsReset = ( ) => { filterRefs.fill(null); filterRefsWritePtr = 1; }; -const filterRefsToSelfie = ( ) => { - const refs = []; - for ( let i = 0; i < filterRefsWritePtr; i++ ) { - const v = filterRefs[i]; - if ( v instanceof RegExp ) { - refs.push({ t: 1, s: v.source, f: v.flags }); - continue; - } - if ( Array.isArray(v) ) { - refs.push({ t: 2, v }); - continue; - } - if ( typeof v !== 'object' || v === null ) { - refs.push({ t: 0, v }); - continue; - } - const out = Object.create(null); - for ( const prop of Object.keys(v) ) { - const value = v[prop]; - out[prop] = prop.startsWith('$') - ? (typeof value === 'string' ? '' : null) - : value; - } - refs.push({ t: 3, v: out }); - } - return JSON.stringify(refs); -}; +const filterRefsToSelfie = ( ) => + filterRefs.slice(0, filterRefsWritePtr); + const filterRefsFromSelfie = selfie => { - if ( typeof selfie !== 'string' || selfie === '' ) { return false; } - const refs = JSON.parse(selfie); - if ( Array.isArray(refs) === false ) { return false; } - for ( let i = 0; i < refs.length; i++ ) { - const v = refs[i]; - switch ( v.t ) { - case 0: - case 2: - case 3: - filterRefs[i] = v.v; - break; - case 1: - filterRefs[i] = new RegExp(v.s, v.f); - break; - default: - throw new Error('Unknown filter reference!'); - } + if ( Array.isArray(selfie) === false ) { return false; } + for ( let i = 0, n = selfie.length; i < n; i++ ) { + filterRefs[i] = selfie[i]; } - filterRefsWritePtr = refs.length; + filterRefsWritePtr = selfie.length; return true; }; @@ -3121,14 +3078,11 @@ const urlTokenizer = new (class { } toSelfie() { - return sparseBase64.encode( - this.knownTokens.buffer, - this.knownTokens.byteLength - ); + return this.knownTokens; } fromSelfie(selfie) { - return sparseBase64.decode(selfie, this.knownTokens.buffer); + this.knownTokens = selfie; } // https://github.com/chrisaljoudi/uBlock/issues/1118 @@ -4095,7 +4049,7 @@ FilterCompiler.prototype.FILTER_UNSUPPORTED = 2; /******************************************************************************/ /******************************************************************************/ -const FilterContainer = function() { +const StaticNetFilteringEngine = function() { this.compilerVersion = '10'; this.selfieVersion = '10'; @@ -4113,7 +4067,7 @@ const FilterContainer = function() { /******************************************************************************/ -FilterContainer.prototype.prime = function() { +StaticNetFilteringEngine.prototype.prime = function() { origHNTrieContainer.reset( keyvalStore.getItem('SNFE.origHNTrieContainer.trieDetails') ); @@ -4125,7 +4079,7 @@ FilterContainer.prototype.prime = function() { /******************************************************************************/ -FilterContainer.prototype.reset = function() { +StaticNetFilteringEngine.prototype.reset = function() { this.processedFilterCount = 0; this.acceptedCount = 0; this.discardedCount = 0; @@ -4159,7 +4113,7 @@ FilterContainer.prototype.reset = function() { /******************************************************************************/ -FilterContainer.prototype.freeze = function() { +StaticNetFilteringEngine.prototype.freeze = function() { const unserialize = CompiledListReader.unserialize; for ( const line of this.goodFilters ) { @@ -4256,7 +4210,7 @@ FilterContainer.prototype.freeze = function() { /******************************************************************************/ -FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) { +StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...args) { if ( op === 'begin' ) { Object.assign(context, { good: new Set(), @@ -4571,17 +4525,7 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) { } break; case 'uritransform': { - const path = rule.__modifierValue; - let priority = rule.priority || 1; - if ( rule.__modifierAction !== ALLOW_REALM ) { - const transform = { path }; - rule.action.type = 'redirect'; - rule.action.redirect = { transform }; - rule.priority = priority + 1; - } else { - rule.action.type = 'block'; - rule.priority = priority + 2; - } + dnrAddRuleError(rule, `Incompatible with DNR: uritransform=${rule.__modifierValue}`); break; } default: @@ -4601,7 +4545,7 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) { /******************************************************************************/ -FilterContainer.prototype.addFilterUnit = function( +StaticNetFilteringEngine.prototype.addFilterUnit = function( bits, tokenHash, inewunit @@ -4628,7 +4572,7 @@ FilterContainer.prototype.addFilterUnit = function( /******************************************************************************/ -FilterContainer.prototype.optimize = function(throttle = 0) { +StaticNetFilteringEngine.prototype.optimize = function(throttle = 0) { if ( this.optimizeTaskId !== undefined ) { dropTask(this.optimizeTaskId); this.optimizeTaskId = undefined; @@ -4684,55 +4628,28 @@ FilterContainer.prototype.optimize = function(throttle = 0) { /******************************************************************************/ -FilterContainer.prototype.toSelfie = async function(storage, path) { - if ( typeof storage !== 'object' || storage === null ) { return; } - if ( typeof storage.put !== 'function' ) { return; } - +StaticNetFilteringEngine.prototype.toSelfie = function() { + this.optimize(0); bidiTrieOptimize(true); - keyvalStore.setItem( - 'SNFE.origHNTrieContainer.trieDetails', + keyvalStore.setItem('SNFE.origHNTrieContainer.trieDetails', origHNTrieContainer.optimize() ); - - return Promise.all([ - storage.put( - `${path}/destHNTrieContainer`, - destHNTrieContainer.serialize(sparseBase64) - ), - storage.put( - `${path}/origHNTrieContainer`, - origHNTrieContainer.serialize(sparseBase64) - ), - storage.put( - `${path}/bidiTrie`, - bidiTrie.serialize(sparseBase64) - ), - storage.put( - `${path}/filterData`, - filterDataToSelfie() - ), - storage.put( - `${path}/filterRefs`, - filterRefsToSelfie() - ), - storage.put( - `${path}/main`, - JSON.stringify({ - version: this.selfieVersion, - processedFilterCount: this.processedFilterCount, - acceptedCount: this.acceptedCount, - discardedCount: this.discardedCount, - bitsToBucket: Array.from(this.bitsToBucket).map(kv => { - kv[1] = Array.from(kv[1]); - return kv; - }), - urlTokenizer: urlTokenizer.toSelfie(), - }) - ) - ]); + return { + version: this.selfieVersion, + processedFilterCount: this.processedFilterCount, + acceptedCount: this.acceptedCount, + discardedCount: this.discardedCount, + bitsToBucket: this.bitsToBucket, + urlTokenizer: urlTokenizer.toSelfie(), + destHNTrieContainer: destHNTrieContainer.toSelfie(), + origHNTrieContainer: origHNTrieContainer.toSelfie(), + bidiTrie: bidiTrie.toSelfie(), + filterData: filterDataToSelfie(), + filterRefs: filterRefsToSelfie(), + }; }; -FilterContainer.prototype.serialize = async function() { +StaticNetFilteringEngine.prototype.serialize = async function() { const selfie = []; const storage = { put(name, data) { @@ -4745,53 +4662,27 @@ FilterContainer.prototype.serialize = async function() { /******************************************************************************/ -FilterContainer.prototype.fromSelfie = async function(storage, path) { - if ( typeof storage !== 'object' || storage === null ) { return; } - if ( typeof storage.get !== 'function' ) { return; } +StaticNetFilteringEngine.prototype.fromSelfie = async function(selfie) { + if ( typeof selfie !== 'object' || selfie === null ) { return; } this.reset(); this.notReady = true; - const results = await Promise.all([ - storage.get(`${path}/main`), - storage.get(`${path}/destHNTrieContainer`).then(details => - destHNTrieContainer.unserialize(details.content, sparseBase64) - ), - storage.get(`${path}/origHNTrieContainer`).then(details => - origHNTrieContainer.unserialize(details.content, sparseBase64) - ), - storage.get(`${path}/bidiTrie`).then(details => - bidiTrie.unserialize(details.content, sparseBase64) - ), - storage.get(`${path}/filterData`).then(details => - filterDataFromSelfie(details.content) - ), - storage.get(`${path}/filterRefs`).then(details => - filterRefsFromSelfie(details.content) - ), - ]); - + const results = [ + destHNTrieContainer.fromSelfie(selfie.destHNTrieContainer), + origHNTrieContainer.fromSelfie(selfie.origHNTrieContainer), + bidiTrie.fromSelfie(selfie.bidiTrie), + filterDataFromSelfie(selfie.filterData), + filterRefsFromSelfie(selfie.filterRefs), + ]; if ( results.slice(1).every(v => v === true) === false ) { return false; } - const details = results[0]; - if ( typeof details !== 'object' || details === null ) { return false; } - if ( typeof details.content !== 'string' ) { return false; } - if ( details.content === '' ) { return false; } - let selfie; - try { - selfie = JSON.parse(details.content); - } catch (ex) { - } - if ( typeof selfie !== 'object' || selfie === null ) { return false; } if ( selfie.version !== this.selfieVersion ) { return false; } this.processedFilterCount = selfie.processedFilterCount; this.acceptedCount = selfie.acceptedCount; this.discardedCount = selfie.discardedCount; - this.bitsToBucket = new Map(selfie.bitsToBucket.map(kv => { - kv[1] = new Map(kv[1]); - return kv; - })); + this.bitsToBucket = selfie.bitsToBucket; urlTokenizer.fromSelfie(selfie.urlTokenizer); // If this point is never reached, it means the internal state is @@ -4804,7 +4695,7 @@ FilterContainer.prototype.fromSelfie = async function(storage, path) { return true; }; -FilterContainer.prototype.unserialize = async function(s) { +StaticNetFilteringEngine.prototype.unserialize = async function(s) { const selfie = new Map(JSON.parse(s)); const storage = { async get(name) { @@ -4816,13 +4707,13 @@ FilterContainer.prototype.unserialize = async function(s) { /******************************************************************************/ -FilterContainer.prototype.createCompiler = function() { +StaticNetFilteringEngine.prototype.createCompiler = function() { return new FilterCompiler(); }; /******************************************************************************/ -FilterContainer.prototype.fromCompiled = function(reader) { +StaticNetFilteringEngine.prototype.fromCompiled = function(reader) { reader.select('NETWORK_FILTERS:GOOD'); while ( reader.next() ) { this.acceptedCount += 1; @@ -4841,7 +4732,7 @@ FilterContainer.prototype.fromCompiled = function(reader) { /******************************************************************************/ -FilterContainer.prototype.matchAndFetchModifiers = function( +StaticNetFilteringEngine.prototype.matchAndFetchModifiers = function( fctxt, modifierName ) { @@ -5018,7 +4909,7 @@ FilterContainer.prototype.matchAndFetchModifiers = function( /******************************************************************************/ -FilterContainer.prototype.realmMatchString = function( +StaticNetFilteringEngine.prototype.realmMatchString = function( realmBits, typeBits, partyBits @@ -5145,7 +5036,7 @@ FilterContainer.prototype.realmMatchString = function( // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ // Add support for `specifichide`. -FilterContainer.prototype.matchRequestReverse = function(type, url) { +StaticNetFilteringEngine.prototype.matchRequestReverse = function(type, url) { const typeBits = typeNameToTypeValue[type] | 0x80000000; // Prime tokenizer: we get a normalized URL in return. @@ -5194,7 +5085,7 @@ FilterContainer.prototype.matchRequestReverse = function(type, url) { * * @returns {integer} 0=no match, 1=block, 2=allow (exception) */ -FilterContainer.prototype.matchRequest = function(fctxt, modifiers = 0) { +StaticNetFilteringEngine.prototype.matchRequest = function(fctxt, modifiers = 0) { let typeBits = typeNameToTypeValue[fctxt.type]; if ( modifiers === 0 ) { if ( typeBits === undefined ) { @@ -5241,7 +5132,7 @@ FilterContainer.prototype.matchRequest = function(fctxt, modifiers = 0) { /******************************************************************************/ -FilterContainer.prototype.matchHeaders = function(fctxt, headers) { +StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, headers) { const typeBits = typeNameToTypeValue[fctxt.type] || otherTypeBitValue; const partyBits = fctxt.is3rdPartyToDoc() ? THIRDPARTY_REALM : FIRSTPARTY_REALM; @@ -5278,7 +5169,7 @@ FilterContainer.prototype.matchHeaders = function(fctxt, headers) { /******************************************************************************/ -FilterContainer.prototype.redirectRequest = function(redirectEngine, fctxt) { +StaticNetFilteringEngine.prototype.redirectRequest = function(redirectEngine, fctxt) { const directives = this.matchAndFetchModifiers(fctxt, 'redirect-rule'); // No directive is the most common occurrence. if ( directives === undefined ) { return; } @@ -5296,28 +5187,40 @@ FilterContainer.prototype.redirectRequest = function(redirectEngine, fctxt) { return directives; }; -FilterContainer.prototype.transformRequest = function(fctxt) { +StaticNetFilteringEngine.prototype.transformRequest = function(fctxt) { const directives = this.matchAndFetchModifiers(fctxt, 'uritransform'); if ( directives === undefined ) { return; } - const directive = directives[directives.length-1]; - if ( (directive.bits & ALLOW_REALM) !== 0 ) { return directives; } - if ( directive.refs instanceof Object === false ) { return; } - const { refs } = directive; - if ( refs.$cache === null ) { - refs.$cache = sfp.parseReplaceValue(refs.value); - } - const cache = refs.$cache; - if ( cache === undefined ) { return; } const redirectURL = new URL(fctxt.url); - const before = redirectURL.pathname + redirectURL.search; - if ( cache.re.test(before) !== true ) { return; } - const after = before.replace(cache.re, cache.replacement); - if ( after === before ) { return; } - const searchPos = after.includes('?') && after.indexOf('?') || after.length; - redirectURL.pathname = after.slice(0, searchPos); - redirectURL.search = after.slice(searchPos); - fctxt.redirectURL = redirectURL.href; - return directives; + const out = []; + for ( const directive of directives ) { + if ( (directive.bits & ALLOW_REALM) !== 0 ) { + out.push(directive); + continue; + } + const { refs } = directive; + if ( refs instanceof Object === false ) { continue; } + if ( refs.$cache === null ) { + refs.$cache = sfp.parseReplaceValue(refs.value); + } + const cache = refs.$cache; + if ( cache === undefined ) { continue; } + const before = `${redirectURL.pathname}${redirectURL.search}${redirectURL.hash}`; + if ( cache.re.test(before) !== true ) { continue; } + const after = before.replace(cache.re, cache.replacement); + if ( after === before ) { continue; } + const hashPos = after.indexOf('#'); + redirectURL.hash = hashPos !== -1 ? after.slice(hashPos) : ''; + const afterMinusHash = hashPos !== -1 ? after.slice(0, hashPos) : after; + const searchPos = afterMinusHash.indexOf('?'); + redirectURL.search = searchPos !== -1 ? afterMinusHash.slice(searchPos) : ''; + redirectURL.pathname = searchPos !== -1 ? after.slice(0, searchPos) : after; + out.push(directive); + } + if ( out.length === 0 ) { return; } + if ( redirectURL.href !== fctxt.url ) { + fctxt.redirectURL = redirectURL.href; + } + return out; }; function parseRedirectRequestValue(directive) { @@ -5348,7 +5251,7 @@ function compareRedirectRequests(redirectEngine, a, b) { // https://github.com/uBlockOrigin/uBlock-issues/issues/1626 // Do not redirect when the number of query parameters does not change. -FilterContainer.prototype.filterQuery = function(fctxt) { +StaticNetFilteringEngine.prototype.filterQuery = function(fctxt) { const directives = this.matchAndFetchModifiers(fctxt, 'removeparam'); if ( directives === undefined ) { return; } const url = fctxt.url; @@ -5422,7 +5325,7 @@ FilterContainer.prototype.filterQuery = function(fctxt) { fctxt.redirectURL = url.slice(0, qpos); if ( params.size !== 0 ) { fctxt.redirectURL += '?' + Array.from(params).map(a => - a[1] === '' ? a[0] : `${a[0]}=${a[1]}` + a[1] === '' ? `${a[0]}=` : `${a[0]}=${a[1]}` ).join('&'); } if ( hpos !== url.length ) { @@ -5442,14 +5345,14 @@ function parseQueryPruneValue(directive) { /******************************************************************************/ -FilterContainer.prototype.hasQuery = function(fctxt) { +StaticNetFilteringEngine.prototype.hasQuery = function(fctxt) { urlTokenizer.setURL(fctxt.url); return urlTokenizer.hasQuery(); }; /******************************************************************************/ -FilterContainer.prototype.toLogData = function() { +StaticNetFilteringEngine.prototype.toLogData = function() { if ( this.$filterUnit !== 0 ) { return new LogData(this.$catBits, this.$tokenHash, this.$filterUnit); } @@ -5457,19 +5360,19 @@ FilterContainer.prototype.toLogData = function() { /******************************************************************************/ -FilterContainer.prototype.isBlockImportant = function() { +StaticNetFilteringEngine.prototype.isBlockImportant = function() { return this.$filterUnit !== 0 && $isBlockImportant; }; /******************************************************************************/ -FilterContainer.prototype.getFilterCount = function() { +StaticNetFilteringEngine.prototype.getFilterCount = function() { return this.acceptedCount - this.discardedCount; }; /******************************************************************************/ -FilterContainer.prototype.enableWASM = function(wasmModuleFetcher, path) { +StaticNetFilteringEngine.prototype.enableWASM = function(wasmModuleFetcher, path) { return Promise.all([ bidiTrie.enableWASM(wasmModuleFetcher, path), origHNTrieContainer.enableWASM(wasmModuleFetcher, path), @@ -5481,7 +5384,7 @@ FilterContainer.prototype.enableWASM = function(wasmModuleFetcher, path) { /******************************************************************************/ -FilterContainer.prototype.test = async function(docURL, type, url) { +StaticNetFilteringEngine.prototype.test = async function(docURL, type, url) { const fctxt = new FilteringContext(); fctxt.setDocOriginFromURL(docURL); fctxt.setType(type); @@ -5495,7 +5398,7 @@ FilterContainer.prototype.test = async function(docURL, type, url) { /******************************************************************************/ -FilterContainer.prototype.bucketHistogram = function() { +StaticNetFilteringEngine.prototype.bucketHistogram = function() { const results = []; for ( const [ bits, bucket ] of this.bitsToBucket ) { for ( const [ th, iunit ] of bucket ) { @@ -5516,7 +5419,7 @@ FilterContainer.prototype.bucketHistogram = function() { // Dump the internal state of the filtering engine to the console. // Useful to make development decisions and investigate issues. -FilterContainer.prototype.dump = function() { +StaticNetFilteringEngine.prototype.dump = function() { const thConstants = new Map([ [ NO_TOKEN_HASH, 'NO_TOKEN_HASH' ], [ DOT_TOKEN_HASH, 'DOT_TOKEN_HASH' ], @@ -5646,6 +5549,6 @@ FilterContainer.prototype.dump = function() { /******************************************************************************/ -const staticNetFilteringEngine = new FilterContainer(); +const staticNetFilteringEngine = new StaticNetFilteringEngine(); export default staticNetFilteringEngine; diff --git a/src/js/storage.js b/src/js/storage.js index 151717c..cd340fc 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -19,44 +19,39 @@ Home: https://github.com/gorhill/uBlock */ -'use strict'; - /******************************************************************************/ -import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js'; -import punycode from '../lib/punycode.js'; +import * as sfp from './static-filtering-parser.js'; -import io from './assets.js'; +import { CompiledListReader, CompiledListWriter } from './static-filtering-io.js'; +import { LineIterator, orphanizeString } from './text-utils.js'; import { broadcast, filteringBehaviorChanged, onBroadcast } from './broadcast.js'; +import { i18n, i18n$ } from './i18n.js'; +import { + permanentFirewall, + permanentSwitches, + permanentURLFiltering, +} from './filtering-engines.js'; +import { ubolog, ubologSet } from './console.js'; + import cosmeticFilteringEngine from './cosmetic-filtering.js'; +import { hostnameFromURI } from './uri-utils.js'; +import io from './assets.js'; import logger from './logger.js'; import lz4Codec from './lz4.js'; +import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js'; +import punycode from '../lib/punycode.js'; +import { redirectEngine } from './redirect-engine.js'; import staticExtFilteringEngine from './static-ext-filtering.js'; import staticFilteringReverseLookup from './reverselookup.js'; import staticNetFilteringEngine from './static-net-filtering.js'; import µb from './background.js'; -import { hostnameFromURI } from './uri-utils.js'; -import { i18n, i18n$ } from './i18n.js'; -import { redirectEngine } from './redirect-engine.js'; -import { sparseBase64 } from './base64-custom.js'; -import { ubolog, ubologSet } from './console.js'; -import * as sfp from './static-filtering-parser.js'; -import { - permanentFirewall, - permanentSwitches, - permanentURLFiltering, -} from './filtering-engines.js'; - -import { - CompiledListReader, - CompiledListWriter, -} from './static-filtering-io.js'; +/******************************************************************************/ -import { - LineIterator, - orphanizeString, -} from './text-utils.js'; +// https://eslint.org/docs/latest/rules/no-prototype-builtins +const hasOwnProperty = (o, p) => + Object.prototype.hasOwnProperty.call(o, p); /******************************************************************************/ @@ -98,24 +93,80 @@ import { /******************************************************************************/ { - let localSettingsLastSaved = Date.now(); + const requestStats = µb.requestStats; + let requestStatsDisabled = false; + + µb.loadLocalSettings = async ( ) => { + requestStatsDisabled = µb.hiddenSettings.requestStatsDisabled; + if ( requestStatsDisabled ) { return; } + return Promise.all([ + vAPI.sessionStorage.get('requestStats'), + vAPI.storage.get('requestStats'), + vAPI.storage.get([ 'blockedRequestCount', 'allowedRequestCount' ]), + ]).then(([ a, b, c ]) => { + if ( a instanceof Object && a.requestStats ) { return a.requestStats; } + if ( b instanceof Object && b.requestStats ) { return b.requestStats; } + if ( c instanceof Object && Object.keys(c).length === 2 ) { + return { + blockedCount: c.blockedRequestCount, + allowedCount: c.allowedRequestCount, + }; + } + return { blockedCount: 0, allowedCount: 0 }; + }).then(({ blockedCount, allowedCount }) => { + requestStats.blockedCount += blockedCount; + requestStats.allowedCount += allowedCount; + }); + }; - const shouldSave = ( ) => { - if ( µb.localSettingsLastModified > localSettingsLastSaved ) { - µb.saveLocalSettings(); - } - saveTimer.on(saveDelay); + const SAVE_DELAY_IN_MINUTES = 3.6; + const QUICK_SAVE_DELAY_IN_SECONDS = 23; + + const stopTimers = ( ) => { + vAPI.alarms.clear('saveLocalSettings'); + quickSaveTimer.off(); + saveTimer.off(); }; - const saveTimer = vAPI.defer.create(shouldSave); - const saveDelay = { sec: 23 }; + const saveTimer = vAPI.defer.create(( ) => { + µb.saveLocalSettings(); + }); + + const quickSaveTimer = vAPI.defer.create(( ) => { + if ( vAPI.sessionStorage.unavailable !== true ) { + vAPI.sessionStorage.set({ requestStats: requestStats }); + } + if ( requestStatsDisabled ) { return; } + saveTimer.on({ min: SAVE_DELAY_IN_MINUTES }); + vAPI.alarms.createIfNotPresent('saveLocalSettings', { + delayInMinutes: SAVE_DELAY_IN_MINUTES + 0.5 + }); + }); - saveTimer.onidle(saveDelay); + µb.incrementRequestStats = (blocked, allowed) => { + requestStats.blockedCount += blocked; + requestStats.allowedCount += allowed; + quickSaveTimer.on({ sec: QUICK_SAVE_DELAY_IN_SECONDS }); + }; - µb.saveLocalSettings = function() { - localSettingsLastSaved = Date.now(); - return vAPI.storage.set(this.localSettings); + µb.saveLocalSettings = ( ) => { + stopTimers(); + if ( requestStatsDisabled ) { return; } + return vAPI.storage.set({ requestStats: µb.requestStats }); }; + + onBroadcast(msg => { + if ( msg.what !== 'hiddenSettingsChanged' ) { return; } + const newState = µb.hiddenSettings.requestStatsDisabled; + if ( requestStatsDisabled === newState ) { return; } + requestStatsDisabled = newState; + if ( newState ) { + stopTimers(); + µb.requestStats.blockedCount = µb.requestStats.allowedCount = 0; + } else { + µb.loadLocalSettings(); + } + }); } /******************************************************************************/ @@ -136,7 +187,7 @@ import { for ( const entry of adminSettings ) { if ( entry.length < 1 ) { continue; } const name = entry[0]; - if ( usDefault.hasOwnProperty(name) === false ) { continue; } + if ( hasOwnProperty(usDefault, name) === false ) { continue; } const value = entry.length < 2 ? usDefault[name] : this.settingValueFromString(usDefault, name, entry[1]); @@ -165,8 +216,8 @@ import { const toRemove = []; for ( const key in this.userSettings ) { - if ( this.userSettings.hasOwnProperty(key) === false ) { continue; } - if ( toSave.hasOwnProperty(key) ) { continue; } + if ( hasOwnProperty(this.userSettings, key) === false ) { continue; } + if ( hasOwnProperty(toSave, key) ) { continue; } toRemove.push(key); } if ( toRemove.length !== 0 ) { @@ -203,7 +254,7 @@ import { for ( const entry of advancedSettings ) { if ( entry.length < 1 ) { continue; } const name = entry[0]; - if ( hsDefault.hasOwnProperty(name) === false ) { continue; } + if ( hasOwnProperty(hsDefault, name) === false ) { continue; } const value = entry.length < 2 ? hsDefault[name] : this.hiddenSettingValueFromString(name, entry[1]); @@ -237,8 +288,8 @@ import { } for ( const key in hsDefault ) { - if ( hsDefault.hasOwnProperty(key) === false ) { continue; } - if ( hsAdmin.hasOwnProperty(name) ) { continue; } + if ( hasOwnProperty(hsDefault, key) === false ) { continue; } + if ( hasOwnProperty(hsAdmin, name) ) { continue; } if ( typeof hs[key] !== typeof hsDefault[key] ) { continue; } this.hiddenSettings[key] = hs[key]; } @@ -283,8 +334,8 @@ onBroadcast(msg => { const matches = /^\s*(\S+)\s+(.+)$/.exec(line); if ( matches === null || matches.length !== 3 ) { continue; } const name = matches[1]; - if ( out.hasOwnProperty(name) === false ) { continue; } - if ( this.hiddenSettingsAdmin.hasOwnProperty(name) ) { continue; } + if ( hasOwnProperty(out, name) === false ) { continue; } + if ( hasOwnProperty(this.hiddenSettingsAdmin, name) ) { continue; } const value = this.hiddenSettingValueFromString(name, matches[2]); if ( value !== undefined ) { out[name] = value; @@ -296,7 +347,7 @@ onBroadcast(msg => { µb.hiddenSettingValueFromString = function(name, value) { if ( typeof name !== 'string' || typeof value !== 'string' ) { return; } const hsDefault = this.hiddenSettingsDefault; - if ( hsDefault.hasOwnProperty(name) === false ) { return; } + if ( hasOwnProperty(hsDefault, name) === false ) { return; } let r; switch ( typeof hsDefault[name] ) { case 'boolean': @@ -369,6 +420,9 @@ onBroadcast(msg => { /******************************************************************************/ µb.isTrustedList = function(assetKey) { + if ( assetKey === this.userFiltersPath ) { + if ( this.userSettings.userFiltersTrusted ) { return true; } + } if ( this.parsedTrustedListPrefixes.length === 0 ) { this.parsedTrustedListPrefixes = µb.hiddenSettings.trustedListPrefixes.split(/ +/).map(prefix => { @@ -530,7 +584,6 @@ onBroadcast(msg => { // https://github.com/gorhill/uBlock/issues/1022 // Be sure to end with an empty line. content = content.trim(); - if ( content !== '' ) { content += '\n'; } this.removeCompiledFilterList(this.userFiltersPath); return io.put(this.userFiltersPath, content); }; @@ -626,6 +679,11 @@ onBroadcast(msg => { cosmeticFilteringEngine.removeFromSelectorCache( hostnameFromURI(details.docURL) ); + staticFilteringReverseLookup.resetLists(); +}; + +µb.userFiltersAreEnabled = function() { + return this.selectedFilterLists.includes(this.userFiltersPath); }; /******************************************************************************/ @@ -633,7 +691,7 @@ onBroadcast(msg => { µb.autoSelectRegionalFilterLists = function(lists) { const selectedListKeys = [ this.userFiltersPath ]; for ( const key in lists ) { - if ( lists.hasOwnProperty(key) === false ) { continue; } + if ( hasOwnProperty(lists, key) === false ) { continue; } const list = lists[key]; if ( list.content !== 'filters' ) { continue; } if ( list.off !== true ) { @@ -845,8 +903,10 @@ onBroadcast(msg => { let loadingPromise; let t0 = 0; + const elapsed = ( ) => `${Date.now() - t0} ms`; + const onDone = ( ) => { - ubolog(`loadFilterLists() took ${Date.now()-t0} ms`); + ubolog(`loadFilterLists() All filters in memory at ${elapsed()}`); staticNetFilteringEngine.freeze(); staticExtFilteringEngine.freeze(); @@ -854,14 +914,16 @@ onBroadcast(msg => { vAPI.net.unsuspend(); filteringBehaviorChanged(); - vAPI.storage.set({ 'availableFilterLists': µb.availableFilterLists }); + ubolog(`loadFilterLists() All filters ready at ${elapsed()}`); logger.writeOne({ realm: 'message', type: 'info', - text: 'Reloading all filter lists: done' + text: `Reloading all filter lists: done, took ${elapsed()}` }); + vAPI.storage.set({ 'availableFilterLists': µb.availableFilterLists }); + broadcast({ what: 'staticFilteringDataChanged', parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters, @@ -877,12 +939,13 @@ onBroadcast(msg => { }; const applyCompiledFilters = (assetKey, compiled) => { + ubolog(`loadFilterLists() Loading filters from ${assetKey} at ${elapsed()}`); const snfe = staticNetFilteringEngine; const sxfe = staticExtFilteringEngine; let acceptedCount = snfe.acceptedCount + sxfe.acceptedCount; let discardedCount = snfe.discardedCount + sxfe.discardedCount; µb.applyCompiledFilters(compiled, assetKey === µb.userFiltersPath); - if ( µb.availableFilterLists.hasOwnProperty(assetKey) ) { + if ( hasOwnProperty(µb.availableFilterLists, assetKey) ) { const entry = µb.availableFilterLists[assetKey]; entry.entryCount = snfe.acceptedCount + sxfe.acceptedCount - acceptedCount; @@ -910,13 +973,15 @@ onBroadcast(msg => { µb.selfieManager.destroy(); staticFilteringReverseLookup.resetLists(); + ubolog(`loadFilterLists() All filters removed at ${elapsed()}`); + // We need to build a complete list of assets to pull first: this is // because it *may* happens that some load operations are synchronous: // This happens for assets which do not exist, or assets with no // content. const toLoad = []; for ( const assetKey in lists ) { - if ( lists.hasOwnProperty(assetKey) === false ) { continue; } + if ( hasOwnProperty(lists, assetKey) === false ) { continue; } if ( lists[assetKey].off ) { continue; } toLoad.push( µb.getCompiledFilterList(assetKey).then(details => { @@ -945,11 +1010,14 @@ onBroadcast(msg => { µb.loadFilterLists = function() { if ( loadingPromise instanceof Promise ) { return loadingPromise; } + ubolog('loadFilterLists() Start'); t0 = Date.now(); loadedListKeys.length = 0; loadingPromise = Promise.all([ this.getAvailableLists().then(lists => onFilterListsReady(lists)), - this.loadRedirectResources(), + this.loadRedirectResources().then(( ) => { + ubolog(`loadFilterLists() Redirects/scriptlets ready at ${elapsed()}`); + }), ]).then(( ) => { onDone(); }); @@ -960,7 +1028,7 @@ onBroadcast(msg => { /******************************************************************************/ µb.getCompiledFilterList = async function(assetKey) { - const compiledPath = 'compiled/' + assetKey; + const compiledPath = `compiled/${assetKey}`; // https://github.com/uBlockOrigin/uBlock-issues/issues/1365 // Verify that the list version matches that of the current compiled @@ -969,11 +1037,10 @@ onBroadcast(msg => { this.compiledFormatChanged === false && this.badLists.has(assetKey) === false ) { - const compiledDetails = await io.get(compiledPath); + const content = await io.fromCache(compiledPath); const compilerVersion = `${this.systemSettings.compiledMagic}\n`; - if ( compiledDetails.content.startsWith(compilerVersion) ) { - compiledDetails.assetKey = assetKey; - return compiledDetails; + if ( content.startsWith(compilerVersion) ) { + return { assetKey, content }; } } @@ -1003,7 +1070,7 @@ onBroadcast(msg => { assetKey, trustedSource: this.isTrustedList(assetKey), }); - io.put(compiledPath, compiledContent); + io.toCache(compiledPath, compiledContent); return { assetKey, content: compiledContent }; }; @@ -1032,7 +1099,7 @@ onBroadcast(msg => { /******************************************************************************/ µb.removeCompiledFilterList = function(assetKey) { - io.remove('compiled/' + assetKey); + io.remove(`compiled/${assetKey}`); }; µb.removeFilterList = function(assetKey) { @@ -1135,7 +1202,10 @@ onBroadcast(msg => { µb.loadRedirectResources = async function() { try { const success = await redirectEngine.resourcesFromSelfie(io); - if ( success === true ) { return true; } + if ( success === true ) { + ubolog('Loaded redirect/scriptlets resources from selfie'); + return true; + } const fetcher = (path, options = undefined) => { if ( path.startsWith('/web_accessible_resources/') ) { @@ -1159,20 +1229,17 @@ onBroadcast(msg => { const results = await Promise.all(fetchPromises); if ( Array.isArray(results) === false ) { return results; } - let content = ''; + const content = []; for ( let i = 1; i < results.length; i++ ) { const result = results[i]; - if ( - result instanceof Object === false || - typeof result.content !== 'string' || - result.content === '' - ) { - continue; - } - content += '\n\n' + result.content; + if ( result instanceof Object === false ) { continue; } + if ( typeof result.content !== 'string' ) { continue; } + if ( result.content === '' ) { continue; } + content.push(result.content); + } + if ( content.length !== 0 ) { + redirectEngine.resourcesFromString(content.join('\n\n')); } - - redirectEngine.resourcesFromString(content); redirectEngine.selfieFromResources(io); } catch(ex) { ubolog(ex); @@ -1211,8 +1278,11 @@ onBroadcast(msg => { } try { - const result = await io.get(`compiled/${this.pslAssetKey}`); - if ( psl.fromSelfie(result.content, sparseBase64) ) { return; } + const selfie = await io.fromCache(`selfie/${this.pslAssetKey}`); + if ( psl.fromSelfie(selfie) ) { + ubolog('Loaded PSL from selfie'); + return; + } } catch (reason) { ubolog(reason); } @@ -1226,7 +1296,8 @@ onBroadcast(msg => { µb.compilePublicSuffixList = function(content) { const psl = publicSuffixList; psl.parse(content, punycode.toASCII); - io.put(`compiled/${this.pslAssetKey}`, psl.toSelfie(sparseBase64)); + ubolog(`Loaded PSL from ${this.pslAssetKey}`); + return io.toCache(`selfie/${this.pslAssetKey}`, psl.toSelfie()); }; /******************************************************************************/ @@ -1246,39 +1317,24 @@ onBroadcast(msg => { if ( µb.inMemoryFilters.length !== 0 ) { return; } if ( Object.keys(µb.availableFilterLists).length === 0 ) { return; } await Promise.all([ - io.put( - 'selfie/main', - JSON.stringify({ - magic: µb.systemSettings.selfieMagic, - availableFilterLists: µb.availableFilterLists, - }) - ), - redirectEngine.toSelfie('selfie/redirectEngine'), - staticExtFilteringEngine.toSelfie( - 'selfie/staticExtFilteringEngine' + io.toCache('selfie/staticMain', { + magic: µb.systemSettings.selfieMagic, + availableFilterLists: µb.availableFilterLists, + }), + io.toCache('selfie/staticExtFilteringEngine', + staticExtFilteringEngine.toSelfie() ), - staticNetFilteringEngine.toSelfie(io, - 'selfie/staticNetFilteringEngine' + io.toCache('selfie/staticNetFilteringEngine', + staticNetFilteringEngine.toSelfie() ), ]); lz4Codec.relinquish(); µb.selfieIsInvalid = false; + ubolog('Filtering engine selfie created'); }; const loadMain = async function() { - const details = await io.get('selfie/main'); - if ( - details instanceof Object === false || - typeof details.content !== 'string' || - details.content === '' - ) { - return false; - } - let selfie; - try { - selfie = JSON.parse(details.content); - } catch(ex) { - } + const selfie = await io.fromCache('selfie/staticMain'); if ( selfie instanceof Object === false ) { return false; } if ( selfie.magic !== µb.systemSettings.selfieMagic ) { return false; } if ( selfie.availableFilterLists instanceof Object === false ) { return false; } @@ -1292,12 +1348,11 @@ onBroadcast(msg => { try { const results = await Promise.all([ loadMain(), - redirectEngine.fromSelfie('selfie/redirectEngine'), - staticExtFilteringEngine.fromSelfie( - 'selfie/staticExtFilteringEngine' + io.fromCache('selfie/staticExtFilteringEngine').then(selfie => + staticExtFilteringEngine.fromSelfie(selfie) ), - staticNetFilteringEngine.fromSelfie(io, - 'selfie/staticNetFilteringEngine' + io.fromCache('selfie/staticNetFilteringEngine').then(selfie => + staticNetFilteringEngine.fromSelfie(selfie) ), ]); if ( results.every(v => v) ) { @@ -1307,33 +1362,26 @@ onBroadcast(msg => { catch (reason) { ubolog(reason); } + ubolog('Filtering engine selfie not available'); destroy(); return false; }; - const destroy = function() { + const destroy = function(options = {}) { if ( µb.selfieIsInvalid === false ) { - io.remove(/^selfie\//); + io.remove(/^selfie\/static/, options); µb.selfieIsInvalid = true; - } - if ( µb.wakeupReason === 'createSelfie' ) { - µb.wakeupReason = ''; - return createTimer.offon({ sec: 27 }); + ubolog('Filtering engine selfie marked for invalidation'); } vAPI.alarms.create('createSelfie', { - delayInMinutes: µb.hiddenSettings.selfieAfter + delayInMinutes: (µb.hiddenSettings.selfieDelayInSeconds + 17) / 60, }); - createTimer.offon({ min: µb.hiddenSettings.selfieAfter }); + createTimer.offon({ sec: µb.hiddenSettings.selfieDelayInSeconds }); }; const createTimer = vAPI.defer.create(create); - vAPI.alarms.onAlarm.addListener(alarm => { - if ( alarm.name !== 'createSelfie') { return; } - µb.wakeupReason = 'createSelfie'; - }); - - µb.selfieManager = { load, destroy }; + µb.selfieManager = { load, create, destroy }; } /******************************************************************************/ @@ -1385,8 +1433,8 @@ onBroadcast(msg => { const µbus = this.userSettings; const adminus = data.userSettings; for ( const name in µbus ) { - if ( µbus.hasOwnProperty(name) === false ) { continue; } - if ( adminus.hasOwnProperty(name) === false ) { continue; } + if ( hasOwnProperty(µbus, name) === false ) { continue; } + if ( hasOwnProperty(adminus, name) === false ) { continue; } bin[name] = adminus[name]; binNotEmpty = true; } @@ -1449,13 +1497,21 @@ onBroadcast(msg => { vAPI.storage.set(bin); } - if ( - Array.isArray(toOverwrite.filters) && - toOverwrite.filters.length !== 0 - ) { - this.saveUserFilters(toOverwrite.filters.join('\n')); + let userFiltersAfter; + if ( Array.isArray(toOverwrite.filters) ) { + userFiltersAfter = toOverwrite.filters.join('\n').trim(); } else if ( typeof data.userFilters === 'string' ) { - this.saveUserFilters(data.userFilters); + userFiltersAfter = data.userFilters.trim(); + } + if ( typeof userFiltersAfter === 'string' ) { + const bin = await vAPI.storage.get(this.userFiltersPath); + const userFiltersBefore = bin && bin[this.userFiltersPath] || ''; + if ( userFiltersAfter !== userFiltersBefore ) { + await Promise.all([ + this.saveUserFilters(userFiltersAfter), + this.selfieManager.destroy(), + ]); + } } }; @@ -1493,7 +1549,6 @@ onBroadcast(msg => { { let next = 0; - let lastEmergencyUpdate = 0; const launchTimer = vAPI.defer.create(fetchDelay => { next = 0; @@ -1502,6 +1557,7 @@ onBroadcast(msg => { µb.scheduleAssetUpdater = async function(details = {}) { launchTimer.off(); + vAPI.alarms.clear('assetUpdater'); if ( details.now ) { next = 0; @@ -1520,40 +1576,23 @@ onBroadcast(msg => { this.hiddenSettings.autoUpdatePeriod * 3600000; const now = Date.now(); - let needEmergencyUpdate = false; - - // Respect cooldown period before launching an emergency update. - const timeSinceLastEmergencyUpdate = (now - lastEmergencyUpdate) / 3600000; - if ( timeSinceLastEmergencyUpdate > 1 ) { - const entries = await io.getUpdateAges({ - filters: µb.selectedFilterLists, - internal: [ '*' ], - }); - for ( const entry of entries ) { - if ( entry.ageNormalized < 2 ) { continue; } - needEmergencyUpdate = true; - lastEmergencyUpdate = now; - break; - } - } // Use the new schedule if and only if it is earlier than the previous // one. if ( next !== 0 ) { - updateDelay = Math.min(updateDelay, Math.max(next - now, 0)); - } - - if ( needEmergencyUpdate ) { - updateDelay = Math.min(updateDelay, 15000); + updateDelay = Math.min(updateDelay, Math.max(next - now, 1)); } next = now + updateDelay; - const fetchDelay = needEmergencyUpdate - ? 2000 - : this.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 || 60000; + const fetchDelay = details.fetchDelay || + this.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 || + 60000; launchTimer.on(updateDelay, fetchDelay); + vAPI.alarms.create('assetUpdater', { + delayInMinutes: Math.ceil(updateDelay / 60000) + 0.25 + }); }; } @@ -1566,7 +1605,7 @@ onBroadcast(msg => { if ( topic === 'before-asset-updated' ) { if ( details.type === 'filters' ) { if ( - this.availableFilterLists.hasOwnProperty(details.assetKey) === false || + hasOwnProperty(this.availableFilterLists, details.assetKey) === false || this.selectedFilterLists.indexOf(details.assetKey) === -1 || this.badLists.get(details.assetKey) ) { @@ -1580,9 +1619,8 @@ onBroadcast(msg => { if ( topic === 'after-asset-updated' ) { // Skip selfie-related content. if ( details.assetKey.startsWith('selfie/') ) { return; } - const cached = typeof details.content === 'string' && - details.content !== ''; - if ( this.availableFilterLists.hasOwnProperty(details.assetKey) ) { + const cached = typeof details.content === 'string' && details.content !== ''; + if ( hasOwnProperty(this.availableFilterLists, details.assetKey) ) { if ( cached ) { if ( this.selectedFilterLists.indexOf(details.assetKey) !== -1 ) { this.extractFilterListMetadata( @@ -1590,8 +1628,7 @@ onBroadcast(msg => { details.content ); if ( this.badLists.has(details.assetKey) === false ) { - io.put( - 'compiled/' + details.assetKey, + io.toCache(`compiled/${details.assetKey}`, this.compileFilters(details.content, { assetKey: details.assetKey, trustedSource: this.isTrustedList(details.assetKey), diff --git a/src/js/traffic.js b/src/js/traffic.js index bf34fd4..df86a86 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -551,7 +551,7 @@ const onHeadersReceived = function(details) { } } if ( jobs.length !== 0 ) { - bodyFilterer.doFilter(fctxt, jobs); + bodyFilterer.doFilter(details.requestId, fctxt, jobs); } } @@ -590,7 +590,7 @@ const onHeadersReceived = function(details) { } }; -const reMediaContentTypes = /^(?:audio|image|video)\//; +const reMediaContentTypes = /^(?:audio|image|video)\/|(?:\/ogg)$/; /******************************************************************************/ @@ -749,7 +749,7 @@ const bodyFilterer = (( ) => { /* t */ if ( bytes[i+6] !== 0x74 ) { continue; } break; } - if ( (i - 40) >= 65536 ) { return; } + if ( (i + 40) >= 65536 ) { return; } i += 8; // find first alpha character let j = -1; @@ -827,13 +827,17 @@ const bodyFilterer = (( ) => { } if ( this.status !== 'finishedtransferringdata' ) { return; } - // If encoding is still unknown, try to extract from stream data + // If encoding is still unknown, try to extract from stream data. + // Just assume utf-8 if ultimately no encoding can be looked up. if ( session.charset === undefined ) { const charsetFound = charsetFromStream(session.buffer); - if ( charsetFound === undefined ) { return streamClose(session); } - const charsetUsed = textEncode.normalizeCharset(charsetFound); - if ( charsetUsed === undefined ) { return streamClose(session); } - session.charset = charsetUsed; + if ( charsetFound !== undefined ) { + const charsetUsed = textEncode.normalizeCharset(charsetFound); + if ( charsetUsed === undefined ) { return streamClose(session); } + session.charset = charsetUsed; + } else { + session.charset = 'utf-8'; + } } while ( session.jobs.length !== 0 ) { @@ -886,10 +890,10 @@ const bodyFilterer = (( ) => { this.str = s; this.modified = true; } - static doFilter(fctxt, jobs) { + static doFilter(requestId, fctxt, jobs) { if ( jobs.length === 0 ) { return; } const session = new Session(fctxt, mime, charset, jobs); - session.stream = browser.webRequest.filterResponseData(session.id); + session.stream = browser.webRequest.filterResponseData(requestId); session.stream.ondata = onStreamData; session.stream.onstop = onStreamStop; session.stream.onerror = onStreamError; diff --git a/src/js/ublock.js b/src/js/ublock.js index e963377..cfc6349 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -148,7 +148,7 @@ const matchBucket = function(url, hostname, bucket, start) { } bucket.push(directive); this.saveWhitelist(); - filteringBehaviorChanged({ hostname: targetHostname }); + filteringBehaviorChanged({ hostname: targetHostname, direction: -1 }); return true; } diff --git a/src/js/whitelist.js b/src/js/whitelist.js index e7905ee..b8f0eaa 100644 --- a/src/js/whitelist.js +++ b/src/js/whitelist.js @@ -30,12 +30,12 @@ import { dom, qs$ } from './dom.js'; const reComment = /^\s*#\s*/; -const directiveFromLine = function(line) { +function directiveFromLine(line) { const match = reComment.exec(line); return match === null ? line.trim() : line.slice(match.index + match[0].length).trim(); -}; +} /******************************************************************************/ @@ -43,7 +43,7 @@ CodeMirror.defineMode("ubo-whitelist-directives", function() { const reRegex = /^\/.+\/$/; return { - token: function(stream) { + token: function token(stream) { const line = stream.string.trim(); stream.skipToEnd(); if ( reBadHostname === undefined ) { @@ -100,18 +100,18 @@ uBlockDashboard.patchCodeMirrorEditor(cmEditor); /******************************************************************************/ -const getEditorText = function() { +function getEditorText() { let text = cmEditor.getValue().replace(/\s+$/, ''); return text === '' ? text : text + '\n'; -}; +} -const setEditorText = function(text) { +function setEditorText(text) { cmEditor.setValue(text.replace(/\s+$/, '') + '\n'); -}; +} /******************************************************************************/ -const whitelistChanged = function() { +function whitelistChanged() { const whitelistElem = qs$('#whitelist'); const bad = qs$(whitelistElem, '.cm-error') !== null; const changedWhitelist = getEditorText().trim(); @@ -119,13 +119,13 @@ const whitelistChanged = function() { qs$('#whitelistApply').disabled = !changed || bad; qs$('#whitelistRevert').disabled = !changed; CodeMirror.commands.save = changed && !bad ? applyChanges : noopFunc; -}; +} cmEditor.on('changes', whitelistChanged); /******************************************************************************/ -const renderWhitelist = async function() { +async function renderWhitelist() { const details = await messaging.send('dashboard', { what: 'getWhitelist', }); @@ -161,11 +161,11 @@ const renderWhitelist = async function() { if ( first ) { cmEditor.clearHistory(); } -}; +} /******************************************************************************/ -const handleImportFilePicker = function() { +function handleImportFilePicker() { const file = this.files[0]; if ( file === undefined || file.name === '' ) { return; } if ( file.type.indexOf('text') !== 0 ) { return; } @@ -179,22 +179,22 @@ const handleImportFilePicker = function() { setEditorText(content); }; fr.readAsText(file); -}; +} /******************************************************************************/ -const startImportFilePicker = function() { +function startImportFilePicker() { const input = qs$('#importFilePicker'); // Reset to empty string, this will ensure an change event is properly // triggered if the user pick a file, even if it is the same as the last // one picked. input.value = ''; input.click(); -}; +} /******************************************************************************/ -const exportWhitelistToFile = function() { +function exportWhitelistToFile() { const val = getEditorText(); if ( val === '' ) { return; } const filename = @@ -205,42 +205,44 @@ const exportWhitelistToFile = function() { 'url': `data:text/plain;charset=utf-8,${encodeURIComponent(val + '\n')}`, 'filename': filename }); -}; +} /******************************************************************************/ -const applyChanges = async function() { +async function applyChanges() { cachedWhitelist = getEditorText().trim(); await messaging.send('dashboard', { what: 'setWhitelist', whitelist: cachedWhitelist, }); renderWhitelist(); -}; +} -const revertChanges = function() { +function revertChanges() { setEditorText(cachedWhitelist); -}; +} /******************************************************************************/ -const getCloudData = function() { +function getCloudData() { return getEditorText(); -}; +} -const setCloudData = function(data, append) { +function setCloudData(data, append) { if ( typeof data !== 'string' ) { return; } if ( append ) { data = uBlockDashboard.mergeNewLines(getEditorText().trim(), data); } setEditorText(data.trim()); -}; +} self.cloud.onPush = getCloudData; self.cloud.onPull = setCloudData; /******************************************************************************/ +self.wikilink = 'https://github.com/gorhill/uBlock/wiki/Dashboard:-Trusted-sites'; + self.hasUnsavedData = function() { return getEditorText().trim() !== cachedWhitelist; }; diff --git a/src/lib/publicsuffixlist/publicsuffixlist.js b/src/lib/publicsuffixlist/publicsuffixlist.js index 6483c89..87910d4 100644 --- a/src/lib/publicsuffixlist/publicsuffixlist.js +++ b/src/lib/publicsuffixlist/publicsuffixlist.js @@ -13,8 +13,6 @@ /*! Home: https://github.com/gorhill/publicsuffixlist.js -- GPLv3 APLv2 */ -/* globals WebAssembly, exports:true, module */ - 'use strict'; /******************************************************************************* @@ -70,7 +68,7 @@ const RULES_PTR_SLOT = 100; // 100 / 400 (400-256=144 => 144>128) const SUFFIX_NOT_FOUND_SLOT = 399; // -- / 399 (safe, see above) const CHARDATA_PTR_SLOT = 101; // 101 / 404 const EMPTY_STRING = ''; -const SELFIE_MAGIC = 2; +const SELFIE_MAGIC = 3; let wasmMemory; let pslBuffer32; @@ -499,9 +497,7 @@ const toSelfie = function(encoder) { } return { magic: SELFIE_MAGIC, - buf32: Array.from( - new Uint32Array(pslBuffer8.buffer, 0, pslByteLength >>> 2) - ), + buf32: pslBuffer32.subarray(0, pslByteLength >> 2), }; }; @@ -524,7 +520,7 @@ const fromSelfie = function(selfie, decoder) { } else if ( selfie instanceof Object && selfie.magic === SELFIE_MAGIC && - Array.isArray(selfie.buf32) + selfie.buf32 instanceof Uint32Array ) { byteLength = selfie.buf32.length << 2; allocateBuffers(byteLength); diff --git a/src/logger-ui.html b/src/logger-ui.html index 33ffd24..8603b91 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -58,27 +58,35 @@ <input type="search" placeholder="logFilterPrompt" spellcheck="false"> <span id="filterExprButton" class="button fa-icon expanded" data-i18n-title="loggerRowFiltererBuiltinTip">angle-up</span> <div id="filterExprPicker"> - <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t--\t|\t<<\t|\t##" data-i18n="loggerRowFiltererBuiltinBlocked"></span><span data-filtex="\t\+\+\t|\t\*\*\t|\t#@#" data-i18n="loggerRowFiltererBuiltinAllowed"></span><span data-filtex="[$,](?:csp|permissions|removeparam|redirect-rule|replace|urltransform)=|\t\<\<\t" data-i18n="loggerRowFiltererBuiltinModified"></span></div> + <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\x1F--\x1F|\x1F<<\x1F|\x1F##" data-i18n="loggerRowFiltererBuiltinBlocked"></span><span data-filtex="\x1F\+\+\x1F|\x1F\*\*\x1F|\x1F#@#" data-i18n="loggerRowFiltererBuiltinAllowed"></span><span data-filtex="[$,](?:csp|permissions|removeparam|redirect-rule|replace|urltransform)=|\x1F\<\<\x1F" data-i18n="loggerRowFiltererBuiltinModified"></span></div> <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span> <span style="flex-direction: column;"> - <div><span data-filtex="\t(?:css|(?:inline-)?font)\t">css/font</span><span data-filtex="\timage\t">image</span><span data-filtex="\tmedia\t">media</span><span data-filtex="\t(?:inline-)?script(?:ing)?\t">script</span></div> - <div><span data-filtex="\t(?:websocket|xhr)\t">xhr</span><span data-filtex="\t(?:frame|object)\t">frame</span><span data-filtex="\t(?:dom|g(?:eneric)?hide|s(?:pecific)?hide)\t">dom</span><span data-filtex="\t(?:scriptlet)\t">scriptlet</span><span data-filtex="\t(?:beacon|csp_report|doc|ping|popup|popunder|other)\t">other</span></div> + <div><span data-filtex="\x1F(?:css|(?:inline-)?font)\x1F">css/font</span><span data-filtex="\x1Fimage\x1F">image</span><span data-filtex="\x1Fmedia\x1F">media</span><span data-filtex="\x1F(?:inline-)?script(?:ing)?\x1F">script</span></div> + <div><span data-filtex="\x1F(?:websocket|xhr)\x1F">xhr</span><span data-filtex="\x1F(?:frame|object)\x1F">frame</span><span data-filtex="\x1F(?:dom|g(?:eneric)?hide|s(?:pecific)?hide)\x1F">dom</span><span data-filtex="\x1F(?:scriptlet)\x1F">scriptlet</span><span data-filtex="\x1F(?:beacon|csp_report|doc|ping|popup|popunder|other)\x1F">other</span></div> </span> </div> - <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t(?:0,)?1\t" data-i18n="loggerRowFiltererBuiltin1p"></span><span data-filtex="\t(?:3(?:,\d)?|0,3)\t" data-i18n="loggerRowFiltererBuiltin3p"></span><span data-filtex="\t0,\d\t">tabless</span></div> - <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\tget\t">get</span><span data-filtex="\thead\t">head</span><span data-filtex="\tpost\t">post</span></div> + <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\x1F(?:0,)?1\x1F" data-i18n="loggerRowFiltererBuiltin1p"></span><span data-filtex="\x1F(?:3(?:,\d)?|0,3)\x1F" data-i18n="loggerRowFiltererBuiltin3p"></span><span data-filtex="\x1F0,\d\x1F">tabless</span></div> + <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\x1Fget\x1F">get</span><span data-filtex="\x1Fhead\x1F">head</span><span data-filtex="\x1Fpost\x1F">post</span></div> <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot" class="on"></span> <span style="flex-direction: column;"> - <div><span data-filtex="\bcsp=[^\t]+\t(?:--|\+\+)\t">csp</span><span data-filtex="\bpermissions=[^\t]+\t(?:--|\+\+)\t" class="on">permissions</span><span data-filtex="\bredirect-rule=[^\t]+\t(?:--|\+\+)\t">redirect</span></div> - <div><span data-filtex="\bremoveparam=[^\t]+\t(?:--|\+\+)\t">removeparam</span><span data-filtex="\burltransform=[^\t]+\t(?:--|\+\+)\t">urltransform</span></div> + <div><span data-filtex="\bcsp=[^\x1F]+\x1F(?:--|\+\+)\x1F">csp</span><span data-filtex="\bpermissions=[^\x1F]+\x1F(?:--|\+\+)\x1F" class="on">permissions</span><span data-filtex="\bredirect-rule=[^\x1F]+\x1F(?:--|\+\+)\x1F">redirect</span></div> + <div><span data-filtex="\bremoveparam=[^\x1F]+\x1F(?:--|\+\+)\x1F">removeparam</span><span data-filtex="\buritransform=[^\x1F]+\x1F(?:--|\+\+)\x1F">uritransform</span></div> + </span> + </div> + <div id="filterExprCnameOf" style="display:none"><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\x1FaliasURL=.">CNAME</span></div> + <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span> + <span style="flex-direction: column;"> + <div><span data-filtex="\x1Finfo\x1F">info</span><span data-filtex="\x1Ferror\x1F">error</span></div> </span> </div> - <div id="filterExprCnameOf" style="display:none"><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\taliasURL=.">CNAME</span></div> </div> </span> </span> </div> <div> + <button id="logLevel" class="iconified notext" title="Toggle verbose mode" type="button"><span class="fa-icon">volume-up</span><span class="hover"></span></button> + </div> + <div> <button id="loggerStats" class="iconified notext" style="display: none;"><span class="fa-icon">bar-chart</span><span class="hover"></span></button> <button id="loggerExport" class="iconified notext"><span class="fa-icon">clipboard</span><span class="hover"></span></button> <button id="loggerSettings" class="iconified notext"><span class="fa-icon">cog</span><span class="hover"></span></button> @@ -92,7 +100,7 @@ </div> </div> <div id="vwLineSizer"> - <div class="logEntry oneLine"><div><span>00:00:00</span><span> </span><span>**</span><span> </span><span>3,3</span><span>options</span><span>inline-script</span><span> </span></div></div> + <div class="logEntry oneLine"><div class="fields"><span>00:00:00</span><span> </span><span>**</span><span> </span><span>3,3</span><span>options</span><span>inline-script</span><span> </span></div></div> </div> </div> </div> @@ -109,7 +117,7 @@ </div> <div id="templates" style="display: none;"> - <div id="logEntryTemplate"><div><span></span>​<span></span>​<span></span>​<span></span>​<span></span>​<span></span>​<span></span>​<span></span></div></div> + <div id="logEntryTemplate"><div class="fields"><span></span>​<span></span>​<span></span>​<span></span>​<span></span>​<span></span>​<span></span>​<span></span></div></div> <div class="netFilteringDialog" data-pane="details"> <div class="dialogControls"> diff --git a/src/web_accessible_resources/chartbeat.js b/src/web_accessible_resources/chartbeat.js index 76e7a29..780d3dd 100644 --- a/src/web_accessible_resources/chartbeat.js +++ b/src/web_accessible_resources/chartbeat.js @@ -27,4 +27,7 @@ activity: noopfn, virtualPage: noopfn }; + for ( const hider of document.querySelectorAll('style[id^=chartbeat-flicker-control]') ) { + hider.remove(); + } })(); diff --git a/src/web_accessible_resources/epicker-ui.html b/src/web_accessible_resources/epicker-ui.html index bd92f50..ef5e9d7 100644 --- a/src/web_accessible_resources/epicker-ui.html +++ b/src/web_accessible_resources/epicker-ui.html @@ -15,7 +15,8 @@ </head> <body> -<aside> +<aside style="right: 2px; bottom: 2px;"> +<div id="windowbar"><div id="minimize"><svg viewBox="0 0 64 64"><path d="M 16,48 H 48" /><rect x="16" y="16" height="32" width="32" /></div><div id="move"></div><div id="quit" data-i18n-title="pickerQuit"><svg viewBox="0 0 64 64"><path d="M16 16L48 48M16 48L48 16" /></div></div> <section> <div> <div class="codeMirrorContainer codeMirrorBreakAll cm-theme-override"></div> @@ -35,14 +36,10 @@ </div> <div id="toolbar"> <div> - <button id="preview" type="button" data-i18n="pickerPreview">_<span class="hover"></span></button> - </div> - <div id="move"></div> - <div> - <button id="create" type="button" class="preferred" disabled data-i18n="pickerCreate">_<span class="hover"></span></button> <button id="pick" type="button" data-i18n="pickerPick">_<span class="hover"></span></button> - <button id="quit" type="button" data-i18n="pickerQuit">_<span class="hover"></span></button> + <button id="preview" type="button" data-i18n="pickerPreview">_<span class="hover"></span></button> </div> + <button id="create" type="button" class="preferred" disabled data-i18n="pickerCreate">_<span class="hover"></span></button> </div> </section> <ul id="candidateFilters"> @@ -56,7 +53,7 @@ </li> </ul> </aside> -<svg><path d></path><path d></path></svg> +<svg id="sea"><path d></path><path d></path></svg> <script src="../lib/codemirror/lib/codemirror.js"></script> <script src="../lib/codemirror/addon/edit/closebrackets.js"></script> diff --git a/src/whitelist.html b/src/whitelist.html index 02d31d8..517c0f5 100644 --- a/src/whitelist.html +++ b/src/whitelist.html @@ -21,16 +21,15 @@ <div class="body"> <div id="cloudWidget" class="hide" data-cloud-entry="whitelistPane"></div> - - <p class="vverbose"><span data-i18n="whitelistPrompt"></span> <a class="fa-icon info important" href="https://github.com/gorhill/uBlock/wiki/Dashboard:-Trusted-sites">info-circle</a> - </p> <p> <button id="whitelistApply" class="preferred iconified" type="button" disabled><span class="fa-icon">check</span><span data-i18n="whitelistApply">_</span><span class="hover"></span></button> <button id="whitelistRevert" class="iconified" type="button" disabled><span class="fa-icon">undo</span><span data-i18n="genericRevert">_</span><span class="hover"></span></button> -    +   <button id="importWhitelistFromFile" class="iconified" type="button"><span class="fa-icon">download-alt</span><span data-i18n="whitelistImport">_</span><span class="hover"></span></button> <button id="exportWhitelistToFile" class="iconified" type="button"><span class="fa-icon">upload-alt</span><span data-i18n="whitelistExport">_</span><span class="hover"></span></button> </p> + <p class="vverbose" data-i18n="whitelistPrompt"> + </p> </div> <div id="whitelist" class="codeMirrorContainer cm-theme-override"></div> |