Полезная информация

Пользователи не любят читать документацию. Станьте оригинальным, будьте не как все. Ознакомьтесь с нашей базой знаний.

№123-03-2015 21:26:45

HaGEN
Забанен
 
Группа: Members
Зарегистрирован: 04-07-2013
Сообщений: 155
UA: Palemoon 25.0

Silent Block - urlfiltr для Firefox

Silent Block - Махонький и быстрый блокировщик для Firefox . 10,97 Кбайт. АМО SilentBlock

Описание
Маленький  и быстрый блокировщик URL.
Создайте свои файлы с правилами в регулярных выражениях JS, чтобы заблокировать соответствующие URL-адреса.
Поместите файлы конфигурации в каталог профиля или укажите ваш путь к каталогу с файлами правил в параметре about:config extensions.silentblock.altdir
        Имена файлов конфигурации должны быть:
        contentblock-regex.txt
        contentblock-regex1.txt
        contentblock-regex2.txt
        contentblock-regex3.txt
        …
        contentblock-whitelist-regex.txt
        contentblock-whitelist-regex1.txt
        contentblock-whitelist-regex2.txt
        …
        Числа должны быть последовательными, начиная с 1.
    Перезапустите Firefox для перезагрузки файлов с правилами.
    Результаты работы (заблокированные адреса  и правила по которым произошла блокировка) выводятся в "Консоль ошибок > Сообщения".
Пример вывода в консоль сообщения:

Выделить код

Код:

SilentBlock:  Blocked  https://ssl.google-analytics.com/ga.js
  by  /https?:\/\/(?:www\.|ssl\.)?google-analytics\.com/i
-------------------------------------------------------------------------
SilentBlock:  Blocked  http://ad.adriver.ru/cgi-bin/erle.cgi?sid=170642&bt=16&target=blank&rnd=802820437&tail256=http%3A//maxpark.com/iframe_pre_c
  by  /(?:_|\W)ad(?!%)s?(?:_|\W)/i

Вывод отключен по умолчанию. Для включения вывода переключить парамтр extensions.silentblock.logging.enabled.
    Без GUI. Вашпе.


Правила воспринимает в Регулярных выражениях - RegExp JS
Проверять/создавать правила можно например тут в Конструктор регулярных выражений
Для создания правил будет полезна кнопка созданная ув. тов. Dumby HTTPRequest Logger
Правила можно крутить какие хочешь, какие сможешь составить. Например несколько:
Пример правил

Выделить код

Код:

(?:_|\W)ad(?!%)s?(?:_|\W)
\Wad(?!%)(?:labs?|master|sense|creative\d?|sniper|nxs|proxy\d*|river||fox|symptotic|high|gear)\W
https?:\/\/direct\.yandex\.(?:ru|net|ua|com)
\.adshost(?:net.|\d{0,3})?\.
\/onclickads\.
\/pagead\/
\/teaser\.php\?id=
# ... и т.д.


Несколько файлов моих с правилами.

Кнопка для SilentBlock ВКЛ/ВЫКЛ запись в консоль

Выделить код

Код:

custombutton://%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0D%0A%3Ccustombutton%20xmlns%3Acb%3D%22http%3A//xsms.nm.ru/custombuttons/%22%3E%0A%20%20%3Cname%3E%u041F%u0435%u0440%u0435%u043A%u043B%u044E%u0447%u0438%u0442%u044C%20%u0437%u043D%u0430%u0447%u0435%u043D%u0438%u0435%20SilentBlock%20logging%5B%20Enable%20/%20Disable%20%5D%3C/name%3E%0A%20%20%3Cimage%3E%3C%21%5BCDATA%5Bdata%3Aimage/png%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC9UlEQVQ4y22T3WtbdRjHP7/fyUly8jKb2Beb2A66Fl9QN5R2jZ3TIc4VNyaCXsxdKIIXolf+A/0HvFMEUbwQRLxw0zlBpMwXVCpVu602tnNLu83GJlleuibpyTnn93gRN0T3+QM+z/fLl0e9cuoF4QZK4XptfOOjlAIgGnLQSgOw7W9jJAAUju2gUIT4FyKGw3c9Q2+sD4MhMAGnl0/S8rYQhOmxIwwkBvECj9MrJ2l5zRsCwVI2bb9FoX6Rp+959qa01WnySf4jAG53enlk5wEAvls7w6bbQCtAKU3LaxK34/y6Ps87829ipJvgwMhBBhKDGGOYu/oDgfgEJsAJxRAE3fbbTI8eZSKbo9qukHbS/HjlW75Y+QxLW8TsGE+MTGMQNppFKq0KlrZwbAdjDDowASknxcvjr3Fw11Nsug3i4SSf/v4x83/OAZAb3s+u9BjlZol8aRGAZGQHhgCtlCIQHyOG43te4vndL+IbD4Xmg4V3Wa1dIhqKcmj0MIEELJW7gp5oChFBA7h+B600gvD4yCFenXidHZHbqLlV3vvlbRrbNR7K7OXevvv5rXQWgMFEBlBYuePjMxerF9h0N8kmszh2jP7EHdzXv5srjcvky4tUWiUeHt5PJpnlzOpXVJoVVq7labh1rL3HHpzxxWe5ssT8+k94QYfBZIbeeB8T2RwNt843q7OIGMrNMmv1AmuNAtfaZcJWBGv82J4ZBIwYmt51zpcWOL+xgEYzkh5l/M4cYcvm1PIJLtUuYFthwlYYS9sIgvXAc3fPPDl2hKnhR/GNjzGG4tY6c1e/Z6H4Mz1Omvp2lULtD+LhBEYMggDdDwj54pNJZpkc2se+nY9Ra1dZruRZKp9jceMcb8298c/VCL7x+S8hjWarc51O0MFSmpSTZnJoismhKQA+PPs+s4UviVgRAgn+L4jaDp+vnODr1VmckEPMjhGz4/REU/THByhuFbsTi3ArQgqF67u0vb8wYrodRW52jYaihK0oBnNLwd8kPV45BcbzzQAAAABJRU5ErkJggg%3D%3D%5D%5D%3E%3C/image%3E%0A%20%20%3Cmode%3E0%3C/mode%3E%0A%20%20%3Cinitcode%3E%3C%21%5BCDATA%5B/*Initialization%20Code*/%5D%5D%3E%3C/initcode%3E%0A%20%20%3Ccode%3E%3C%21%5BCDATA%5B//%20%u041B%u041A%u041C%20-%20%u0432%u043A%u043B%u044E%u0447%u0438%u0442%u044C%20%u0432%u044B%u0432%u043E%u0434%20%u0441%u043E%u043E%u0431%u0449%u0435%u043D%u0438%u0439%20%u0432%20%u043A%u043E%u043D%u0441%u043E%u043B%u044C%20%u0440%u0430%u0441%u0448%u0438%u0440%u0435%u043D%u0438%u044F%20SilentBlock%0A//%20%u0421%u041A%u041C%20-%20%u0432%u044B%u043A%u043B%u044E%u0447%u0438%u0442%u044C%0A%09var%20prefs%20%3D%20Components.classes%5B%22@mozilla.org/preferences-service%3B1%22%5D.getService%28Components.interfaces.nsIPrefService%29%3B%0A%09this.PS%20%3D%20Components.classes%5B%27@mozilla.org/preferences-service%3B1%27%5D%20.getService%28Components.interfaces.nsIPrefBranch%29%3B%0A%0A%09var%20name%3D%22extensions.silentblock.logging.enabled%22%3B%0A%09var%20t%3D1%3B%20%0A%09var%20f%3D0%3B%0A%0A%09this.onclick%20%3D%20function%28event%29%20%7B%0A%09%20%20%20%20if%28event.button%20%3D%3D%200%29%20%7B%0A%09%09this.PS.setBoolPref%28name%2Ct%29%3B//%u0434%u043B%u044F%20%u043B%u043E%u0433%u0438%u0447%u0435%u0441%u043A%u0438%u0445%0A%09%09this.checked%20%3D%20true%3B%0A%09%09%7D%3B%0A%0A%09if%28event.button%20%3D%3D%201%29%20%7B%0A%09%09this.PS.setBoolPref%28name%2Cf%29%0A%09%09this.checked%20%3D%20false%3B%0A%09%09%7D%3B%0A%09%7D%5D%5D%3E%3C/code%3E%0A%20%20%3Caccelkey%3E%3C%21%5BCDATA%5B%5D%5D%3E%3C/accelkey%3E%0A%20%20%3Chelp%3E%3C%21%5BCDATA%5B%5D%5D%3E%3C/help%3E%0A%20%20%3Cattributes/%3E%0A%3C/custombutton%3E
Выделить код

Код:

// ЛКМ - включить вывод сообщений в консоль расширения SilentBlock
// СКМ - выключить
    var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
    this.PS = Components.classes['@mozilla.org/preferences-service;1'] .getService(Components.interfaces.nsIPrefBranch);

    var name="extensions.silentblock.logging.enabled";
    var t=1; 
    var f=0;

    this.onclick = function(event) {
        if(event.button == 0) {
        this.PS.setBoolPref(name,t);//для логических
        this.checked = true;
        };

    if(event.button == 1) {
        this.PS.setBoolPref(name,f)
        this.checked = false;
        };
    }


зы. И правила и сам плугин SilentBlock требуют перезапуска бравзера при изменении. Это наверное единственный "минус" которые есть у него.
Исчез минус этого расширения благодаря тов. Dumby good2.gif
Он подсказал как перегрузить правила не перегружая бравзер. Кнопка/жест/горячая клавиша с кодом :

Выделить код

Код:

SchuzakJp.SilentBlock.init.XPCBlocker.loadPatternList();

Обновят загруженные правила сразу, без перезапуска бравзера. Благодарю тов. Dumby за внимание и помощь!

Правила для SilentBlock на Ядиск-е
В архиве четыре файла:
contentblock-regex.txt  - общие правила блокировки рекламы
contentblock-regex1.txt - счётчики, следилки, виджеты соц. сетей, прочая статистика
contentblock-regex2.txt - порнушные рекламщики. бывают на картинко-хостингах и тематических сайтах
contentblock-whitelist-regex.txt  -  "Белый список" запросы для которых отключены правила блокировки. Пример для ВКонтакта. Сам ВК работает, а его виджетов на др. сайтах не будет.
Правила для SilentBlock на ЯДиск

Отредактировано HaGEN (31-03-2015 18:52:39)

Отсутствует

 

№223-03-2015 23:08:04

AlAvis
Участник
 
Группа: Members
Зарегистрирован: 16-06-2014
Сообщений: 572
UA: unknown 0.0

Re: Silent Block - urlfiltr для Firefox

HaGEN пишет

Правила воспринимает в Регулярных выражениях - RegExp JS.

Посмотришь какие результаты лиса показывает в тесте RegExp на octane-benchmark и грустно становится ...
Лучше , наверное , на ABP остаться . Да и правила писать гораздо легче .

Отсутствует

 

№324-03-2015 01:21:36

Dumby
Участник
 
Группа: Members
Зарегистрирован: 12-08-2012
Сообщений: 2129
UA: Firefox 31.0

Re: Silent Block - urlfiltr для Firefox

HaGEN пишет

правила ... требуют перезапуска бравзера при изменении

скрытый текст

Выделить код

Код:

SchuzakJp.SilentBlock.init.XPCBlocker.loadPatternList();

Отсутствует

 

№424-03-2015 01:43:57

HaGEN
Забанен
 
Группа: Members
Зарегистрирован: 04-07-2013
Сообщений: 155
UA: Palemoon 25.0

Re: Silent Block - urlfiltr для Firefox

Да я вокруг него танцевал наверно месяц :D В регулярках не шарю ниразу, а тама правило-пример на домашней странице афтара было такое:

Выделить код

Код:

^https?://(?:[^/]{0,2}(?:[^w/][^/]*)?\.)?ad-?(?:s(?:e?rv(?:er?|ing)?)?|im(?:age|g)s?)?\d*(?:(?:\.[\w-]+){3,}|\.[\w-]+\.\w{3,}|\.[\w-]{4,}\.\w{2})/|/[_-]*ad[_-]*(?:se?rv(?:er?|ing)?|im(?:age|g)s?|vertis(?:e(?:ments?)?|ing))\d*[\./]|//.+/.*\.ads?\d*[/-]|/ad(?:sv?\d{0,2}/|\d*/js/|\d-|j?/.+;.*[\?&])|^[^\?&]+/(?:load)?ads?(?:(?:frame|_[a-z]+)*\d*\.(?:js|php))?(?:$|[\?&])|[/\?&]ads?_?(?:id|type|format|size)=|=ad(?:&|$)

:cry: я отъехал вапче... несколько раз включал-выключал... забывал-вспоминал, пробовал... опять выключал. Ну нельзяж такие примеры давать. А потом ниччё, покурил доки пошло нормально когда с регулярками трошки разобрался. этих монстров постирал, свои подобавлял и щас норм. вроде.

okkamas_knife пишет

и очень большой - пока настроишь замучаешься перезапускать.
да и если какоето правило на какомто сайте косячит это ж надо весь браузер перезапускать для отключения.

Да. уже нет :)
у меня так и было. не мог я однажды скачать видео с ютуб. Ну не тянется по прямой ссылке хоть убей, вроде нету его. А онож есть, в другом бравзере играется. потом дошло поглядеть в SilentBlock а там оно его режет изза правила:

Выделить код

Код:

# старый вариант который блокирует видео если в ссылке есть символы %AB%
# для длинных ссылок (например youtube | googlevideo.com) которые кодируют в символах 
# &title=%D0%AD%D1%82%D0% и т.д. ибо попадает в фильтр  %AD% что не есть гут.
(?:_|\W)ads?(?:_|\W)

пришлось добавить проверку если за AD идёт символ % то пропускать, не срабатывать

Выделить код

Код:

# нормальное правило
(?:_|\W)ad(?!%)s?(?:_|\W)

AlAvis я не вкурсе за скоростя, но как мну думается: ежели нечто(скрипт или флэш) не загрузится вообще то и ничего не выполнится, соотв. скорость исполнения RegExp скомпенсируется НЕвыполнением скриптов и стилей/баннеров и прочей рекламмы/ненужного :)

Отредактировано HaGEN (24-03-2015 02:02:28)

Отсутствует

 

№524-03-2015 02:09:48

AlAvis
Участник
 
Группа: Members
Зарегистрирован: 16-06-2014
Сообщений: 572
UA: unknown 0.0

Re: Silent Block - urlfiltr для Firefox

HaGEN
Это если сравнивать с "голым" браузером , а как сравнение с конкурентами ? У меня ABP с 7 подписками и 300+ собственных правил на всех тестах снижает скорость в пределах 1.5% (на не самом мощном ноуте) . Насколько упадёт скорость с Silent Block ? Не говоря о "переводе" такого количества правил . А ставить в дополнение не имеет смысла .

Отсутствует

 

№624-03-2015 14:39:37

HaGEN
Забанен
 
Группа: Members
Зарегистрирован: 04-07-2013
Сообщений: 155
UA: Palemoon 25.0

Re: Silent Block - urlfiltr для Firefox

okkamas_knife а я и спорить не буду. Прально всё. Открыть файл, создать правило, проверить в Конструкторе RegExp, сохранить , перегрузить правила. А если реклама встроенная, то надобно её в CSS отрезать (Stylish или я пользую superUserContent ибо в Мозилле userContent.css требует перезапуска) - все это верно. Но у мну мотивация другая совсем , яж понять хочу. А для этого нужно поковыряцца и в CSS и в RegExp :D вот и ковыряюсь.
Вообще за всю стороннюю рекламу и прочее у мну отвечает RequestPolicy , а SilentBlock чисто на "подхвате". Но иногда я отключаю RequestPolicy и тогда попадаю в страшный, неведомый мне мир интронэтов с горами скриптов и тоннами баннеров... и начинаю упражняться с правилами в RegExp и CSS. Сейчас же , когда в эту тему зашёл свет исторгаемый гуру и осветил тёмные закоулки неведомого до ныне - можно играться с "регулярками" много и часто :)
AlAvis замеров я не делал, ибо рано , првил мало слишком и без доп. средств с теми правилами что щас у мну есть SilentBlock не много рекламы отрезает. "Нам ннужно ббольше минералов"
ABP есть давно и хорошо известен. И сразу любая тема про блокировщики другие перерастает в сравнение их с ABP, ита печальна. А мне вот нравится SilentBlock и RequestPolicy.

Отсутствует

 

№724-03-2015 15:32:52

SendInfo
.
 
Группа: Members
Зарегистрирован: 14-02-2011
Сообщений: 271
UA: Firefox 39.0

Re: Silent Block - urlfiltr для Firefox

HaGENу

поиграться— разобраться

Выделить код

Код:

// ==UserScript==
// @name           urlfilter.uc.js
// @description    Opera Из urlfilter.ini Что-то иш
// @namespace      http://d.hatena.ne.jp/Griever/
// @author         Griever
// @license        MIT License
// @compatibility  Firefox 11
// @charset        UTF-8
// @include        main
// @version        0.0.8
// @note           0.0.8 Remove E4X
// @note           0.0.7 Исправлена ​​проблема, которая фильтра назад матч ошибается взрыв
// @note           0.0.6 URL Исправлена ​​проблема, которая должна быть странно долго и замораживания
// @note           0.0.5 В целом перезаписи
// @note           0.0.5 Изменить имя глобальной переменной
// @note           0.0.5 Изменение работоспособности меню
// @note           0.0.5 Был окончательно записать количество времени, заблокированный
// @note           0.0.5 json От ini Перейдите в(ini Если нет json Читает ini Я делаю)
// @note           0.0.5 Adblock Белый список(@@)И $image В соответствии с
// ==/UserScript==

/*
Opera Из urlfilter.ini Что-то иш
Adblock Следует отметить, что не совместимо с
*/

(function(CSS, WRITE_DESCRIPTION){

const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
if (!window.Services) Cu.import("resource://gre/modules/Services.jsm");

var list = Services.wm.getEnumerator("navigator:browser");
while (list.hasMoreElements()) if (list.getNext() != window) return;


if (window.gURLFilter) {
    window.gURLFilter.destroy();
    delete window.gURLFilter;
}

function Filter(aStr) {
    if (typeof aStr != "string") return aStr;
    this.text = aStr;
}
Filter.prototype = {
    _text: '',
    get text() this._text,
    set text(aStr) {
        if (typeof aStr != "string") return aStr;
        aStr = aStr.trim();
        this._text = aStr;
        this.setFilter(aStr);
        return aStr;
    },
    _enable: true,
    get enable() this._enable,
    set enable(bool) {
        if (bool = !!bool) {
            delete gURLFilter.disables[this.text];
        } else {
            gURLFilter.disables[this.text] = true;
            gURLFilter[this.type + 'Filter'] = null;
        }
        return this._enable = bool;
    },
    _count: null,
    get count() {
        if (!this._count) return this._count;
        var sa = new Date().getTime() - this._count;
        var day = 86400000;
        return sa > day * 365 ? parseInt(sa/day/365, 10) + "Назад"   : 
               sa > day * 30  ? parseInt(sa/day/30, 10)  + "Месяц назад" : 
               sa > day * 7   ? parseInt(sa/day/7, 10)   + "За неделю до" : 
               sa > day       ? parseInt(sa/day, 10)     + "Несколько дней назад"   : 
               sa > 3600000   ? parseInt(sa/3600000, 10) + "Время назад" : 
               sa > 60000     ? parseInt(sa/60000, 10)   + "Минуту до"   : "1В течение нескольких минут";
    },
    set count(number) {
        if (typeof number == "number" && number > 0) {
            gURLFilter.counter[this.text] = number;
            this._count = number;
        } else {
            delete gURLFilter.counter[this.text];
            this._count = null;
        }
        return number;
    },
    type: "sonota",
    litreg: /^\/([^\/].*[^\\])\/[igm]*?$/,
    wldreg: /[^*]+\*[^*]+|\*\*|:\/\/\*\./,
    zenreg: /^\w+:[^*]+\*$/,
    koureg: /^\*[^*]+$/,
    bubreg: /^\*[^*]+\*$/,
    kanreg: /^\w+:[^*]+$/,
    dolreg: /\$([^/]+)$/,
    dosreg: /~?third-party|~?image|~?domain=[^,]+/g,
    symreg: /[()\[\]{}|+.,^$?\\]/g,
    setFilter: function(aStr) {
        if (aStr.length < 4 || !/^(?:@@)?[h*f/]/.test(aStr)) return;
        var temp = this;
        var overrideType = null;
        if (aStr.indexOf("@@") == 0) {
            overrideType = "white";
            aStr = aStr.replace(/^@{2,}/, "");
        }
        var doller = this.dolreg.exec(aStr);
        var dolmatch = doller && doller[1] ? doller[1].match(this.dosreg) : null;
        if (dolmatch) {
            aStr = aStr.replace(doller[0], "");
            dolmatch.forEach(function(n){
                if (n === "third-party") return temp.$third = true;
                if (n === "~third-party") return temp.$third = false;
                if (n === "image") return temp.$image = true;
                if (n === "~image") return temp.$image = false;
                if (n.indexOf("domain=") === 0) {
                    var ok = [], ng = [];
                    n.substr(7).split("|").forEach(function(d){
                        if (d[0] === "~") {
                            ng.push(d.substr(1).replace(this.symreg, "\\$&"));
                        } else {
                            ok.push(d.replace(this.symreg, "\\$&"));
                        }
                    });
                    try {
                        if (ok.length) temp.$domain_regexp = new RegExp(ok.join("|") + "$");
                        if (ng.length) temp.$domain_ng_regexp = new RegExp(ng.join("|") + "$");
                    } catch (e) {
                        overrideType = "sonota";
                    }
                    return;
                }
            }, this);
        }

        if (overrideType === "sonota") return;
        if (this.litreg.test(aStr)) {
            temp.type = "seiki";
            temp.source = RegExp.$1;
        } else if (this.wldreg.test(aStr)) {
            temp.type = "seiki";
            temp.source = this.wildcardToRegExpStr(aStr);
        } else if (this.kanreg.test(aStr)) {
            temp.type = "kanzen";
            temp.word = aStr;
            temp.source = "^" + this.wildcardToRegExpStr(aStr) + "$";
        } else if (this.zenreg.test(aStr)) {
            temp.type = "zenpou";
            temp.word = aStr.replace(/\*/g, "");
            temp.source = "^" + this.wildcardToRegExpStr(aStr);
        } else if (this.koureg.test(aStr)) {
            temp.type = "kouhou";
            temp.word = aStr.replace(/\*/g, "");
            temp.source = this.wildcardToRegExpStr(aStr) + "$";
        } else if (this.bubreg.test(aStr)) {
            temp.type = "bubun";
            temp.word = aStr.replace(/\*/g, "");
            temp.source = this.wildcardToRegExpStr(aStr);
        } else {
            overrideType = "sonota";
        }
        if (temp.source && (temp.type === "seiki" || overrideType === "white")) {
            try {
                temp.regexp = new RegExp(temp.source, "i");
            } catch (e) {
                overrideType = "sonota";
            }
        }

        if (this._text in gURLFilter.disables)
            temp.enable = false;
        if (this._text in gURLFilter.counter)
            temp.count = gURLFilter.counter[this._text];
        if (overrideType) 
            temp.type = overrideType;
        return temp;
    },
    wildcardToRegExpStr: function (urlstr) {
        var res = urlstr.replace(this.symreg, "\\$&")
        .replace(/\*+|:\/\/\*\\\./g, function(str){
            if (str === "\\^") return "(?:^|$|\\b)";
            if (str === "://*\\.") return "://(?:[^/]+\\.)?";
            if (str[0] === "*") return ".*";
            return str;
        })
        return res;
    },
    match: function(url) {
        if (!this._enable) return false;
        switch(this.type) {
            case "white" : return this.regexp.test(url);
            case "kanzen": return url === this.word;
            case "zenpou": return url.indexOf(this.word) === 0;
            case "kouhou": 
                let index = url.lastIndexOf(this.word);
                if (index === -1) return false;
                return index === url.length - this.word.length;
            case "bubun" : return url.indexOf(this.word) >= 0;
            case "seiki" : return this.regexp.test(url);
            default: return false;
        }
    },
    matchDoller: function(pageHost, isThird, isImage) {
        return (!("$domain_regexp" in this) || this.$domain_regexp.test(pageHost)) && 
               (!("$domain_ng_regexp" in this) || !this.$domain_ng_regexp.test(pageHost)) && 
               (!("$third" in this) || this.$third === isThird) && 
               (!("$image" in this) || this.$image === isImage)
    },
};
Object.defineProperty(Filter.prototype, "toString", {enumerable: false, value: function() this.text});


window.gURLFilter = {
    Filter: Filter,
    WRITE_DESCRIPTION: WRITE_DESCRIPTION,
    kanzen: {},
    zenpou: {},
    kouhou: {},
    bubun: {},
    seiki: {},
    white: {},
    sonota: {},
    counter: {},
    disables: {},
    removed: {},
    popupFlag: { kanzen: false, zenpou: false, kouhou: false, bubun: false, seiki: false, white: false, counter:false },
    saveFlag: false,
    lastInputFilter: "",
    lastModifiedTime: 0,

    _disabled: true,
    get disabled () this._disabled,
    set disabled (bool) {
        bool = !!bool;
        if (this._disabled != bool) {
            if (bool) {
                Services.obs.removeObserver(this, "http-on-modify-request");
                this.log("stop");
            } else {
                Services.obs.addObserver(this, "http-on-modify-request", false);
                this.log("start");
            }
        }
        this._disabled = bool;
        this.updateIcon();
        return this._disabled;
    },
    get prefs() {
        delete this.prefs;
        return this.prefs = Services.prefs.getBranch("urlfilter.");
    },
    get file() {
        var aFile = Services.dirsvc.get("UChrm", Ci.nsILocalFile);
        aFile.appendRelativePath("urlfilter.ini");
        delete this.file;
        return this.file = aFile;
    },
    getFocusedWindow: function(){
        var win = document.commandDispatcher.focusedWindow;
        return (!win || win == window) ? window.content : win;
    },
    init: function() {
        this.xulstyle = addStyle(CSS);

        var menutooltiptext = [
            "Включить / выключить левой кнопкой мыши",
            "Sfhit+Убрать фильтр Щелкните",
            "Изменить фильтр щелкнув правой кнопкой мыши",
        ].join("\n");

        this.icon = $("urlbar-icons").appendChild($C("image", {
            id: "urlfilter-icon",
            tooltiptext: "urlfilter" ,
            context: "urlfilter-menupopup" ,
            onclick: "gURLFilter.onClick(event);",
        }));
        this.icon.style.padding = "0px 2px";

        var xml = '\
            <menupopup id="urlfilter-menupopup"\
                       onpopupshowing="gURLFilter.onPopupshowing(event);">\
                <menuitem label="Добавить фильтр"\
                          accesskey="A"\
                          oncommand="gURLFilter.addFilter();"/>\
                <menuitem label="INI Перезагрузить файл"\
                          accesskey="R"\
                          oncommand="gURLFilter.loadINI();"/>\
                <menuitem label="INI Редактирование файлов"\
                          accesskey="E"\
                          oncommand="gURLFilter.editINI();"/>\
                <menuseparator />\
                <menu label="Точно">\
                    <menupopup popupType="kanzen"\
                               oncommand="gURLFilter.onFilterItemCommand(event);"\
                               onclick="gURLFilter.onFilterItemClick(event);"\
                               tooltiptext="'+ menutooltiptext +'" />\
                </menu>\
                <menu label="Начинается с">\
                    <menupopup popupType="zenpou"\
                               oncommand="gURLFilter.onFilterItemCommand(event);"\
                               onclick="gURLFilter.onFilterItemClick(event);"\
                               tooltiptext="'+ menutooltiptext +'" />\
                </menu>\
                <menu label="Задняя согласуется">\
                    <menupopup popupType="kouhou"\
                               oncommand="gURLFilter.onFilterItemCommand(event);"\
                               onclick="gURLFilter.onFilterItemClick(event);"\
                               tooltiptext="'+ menutooltiptext +'" />\
                </menu>\
                <menu label="Частичное соглашение">\
                    <menupopup popupType="bubun"\
                               oncommand="gURLFilter.onFilterItemCommand(event);"\
                               onclick="gURLFilter.onFilterItemClick(event);"\
                               tooltiptext="'+ menutooltiptext +'" />\
                </menu>\
                <menu label="Такие, как регулярное выражение">\
                    <menupopup popupType="seiki"\
                               oncommand="gURLFilter.onFilterItemCommand(event);"\
                               onclick="gURLFilter.onFilterItemClick(event);"\
                               tooltiptext="'+ menutooltiptext +'" />\
                </menu>\
                <menu label="Белый список">\
                    <menupopup popupType="white"\
                               oncommand="gURLFilter.onFilterItemCommand(event);"\
                               onclick="gURLFilter.onFilterItemClick(event);"\
                               tooltiptext="'+ menutooltiptext +'" />\
                </menu>\
                <menuseparator class="urlfilter-menuend-separator" />\
            </menupopup>\
        ';
        var range = document.createRange();
        range.selectNodeContents($('mainPopupSet'));
        range.collapse(false);
        range.insertNode(range.createContextualFragment(xml.replace(/\n|\t/g, '')));
        range.detach();

        var ins = $("spell-separator");
        ins.parentNode.insertBefore($C("menuitem", {
            id: "context-urlfilter-add-image",
            label: "Изображение URL  urlfilter Для зарегистрированы",
            class: "menuitem-iconic",
            accesskey: "U",
            oncommand: "gURLFilter.addFilter(gContextMenu.mediaURL);",
        }), ins);
        ins.parentNode.insertBefore($C("menuitem", {
            id: "context-urlfilter-add-frame",
            label: "Из кадраURL  urlfilter Для зарегистрированы",
            class: "menuitem-iconic",
            accesskey: "F",
            oncommand: "gURLFilter.addFilter(gContextMenu.target.ownerDocument.location.href);",
        }), ins);

        this.loadPrefSetting();
        this.loadINI() || this.importJSON();

        try {
            this.disabled = this.prefs.getBoolPref("disabled");
        } catch (e) {
            this.prefs.setBoolPref("disabled", false);
            this.disabled = false;
        }

        window.addEventListener("unload", this, false);
        gBrowser.mPanelContainer.addEventListener("popupshowing", this, false);
        gBrowser.mPanelContainer.addEventListener("load", this, true);
        gBrowser.mTabContainer.addEventListener("TabSelect", this, false);
        this.updateIcon();
    },
    uninit: function() {
        window.removeEventListener("unload", this, false);
        gBrowser.mPanelContainer.removeEventListener("popupshowing", this, false);
        gBrowser.mPanelContainer.removeEventListener("load", this, true);
        gBrowser.mTabContainer.removeEventListener("TabSelect", this, false);
        this.savePrefSetting();
        if (this.saveFlag || !this.file.exists())
            this.saveINI();
        this.prefs.setBoolPref("disabled", this.disabled);
        this.disabled = true;
    },
    destroy: function(){
        this.uninit();
        var ids = ["urlfilter-icon", "urlfilter-menupopup",
            "context-urlfilter-add-image", "context-urlfilter-add-frame"];
        for (let [, id] in Iterator(ids)) {
            let e = document.getElementById(id);
            if (e) e.parentNode.removeChild(e);
        }
        this.xulstyle.parentNode.removeChild(this.xulstyle);
    },
    handleEvent: function(event) {
        switch(event.type) {
            case "TabSelect":
                if (this.disabled) return;
                setTimeout(this.updateIcon.bind(this), 10);
                break;
            case "load":
                if (this.disabled) return;
                this.updateIcon();
                break;
            case "popupshowing":
                var popup = event.target;
                if (popup.id.indexOf("hud_panel") != 0 || popup.hasAttribute("urlfilter-added")) return;
                popup.setAttribute("urlfilter-added", "true");
                popup.insertBefore(document.createElement("menuseparator"), popup.firstChild);
                var m = popup.insertBefore(document.createElement("menuitem"), popup.firstChild);
                m.setAttribute("label", "urlfilter Добавить в");
                m.setAttribute("accesskey", "A");
                m.setAttribute("oncommand", [
                    "var rich = this.parentNode.parentNode.querySelector('richlistbox');"
                    ,"if (!rich || rich.selectedIndex === -1) return;"
                    ,"var label = rich.selectedItem.querySelector('.webconsole-msg-url');"
                    ,"if (label) gURLFilter.addFilter(label.value);"
                ].join("\n"));
                break;
            case "unload":
                this.uninit();
                break;
        }
    },
    onPopupshowing: function(event) {
        var popup = event.target;

        if (popup === event.currentTarget) {
            popup.setAttribute("hasmanager", "gURLFilterManager" in window);
            for (let n in this.popupFlag) {
                this.popupFlag[n] = false;
            }
            var sep = popup.querySelector(".urlfilter-menuend-separator");
            if (sep.nextSibling) {
                let range = document.createRange();
                range.setStartBefore(sep.nextSibling);
                range.setEndAfter(popup.lastChild);
                range.deleteContents();
                range.detach();
            }

            var win = this.getFocusedWindow();
            if (win.blocked) {
                var menutooltiptext = [
                    "Добавить в белый список на левой кнопкой мыши",
                    "В средней кнопкой мыши URL Я открываю",
                    "Изменить фильтр щелкнув правой кнопкой мыши",
                ].join("\n");
                let menuitem = document.createElement("menuitem");
                menuitem.setAttribute("label", win.location.host + " В I был заблокирован URL");
                menuitem.setAttribute("disabled", "true");
                menuitem.setAttribute("style", "font-weight:bold;");
                popup.appendChild(menuitem);

                for (let [key, val] in Iterator(win.blocked)) {
                    let menuitem = document.createElement("menuitem");
                    menuitem.setAttribute("label", key);
                    menuitem.setAttribute("matched", val);
                    menuitem.setAttribute("tooltiptext", menutooltiptext + '\n' + val);
                    menuitem.setAttribute("oncommand", 'gURLFilter.onBlockedItemCommand(event);');
                    menuitem.setAttribute("onclick", 'gURLFilter.onBlockedItemClick(event);');
                    popup.appendChild(menuitem);
                }
            }
        }
        var type = popup.getAttribute("popupType");
        if (!type || this.popupFlag[type]) return;
        this.popupFlag[type] = true;

        if (popup.hasChildNodes()) {
            let range = document.createRange();
            range.selectNodeContents(popup);
            range.deleteContents();
            range.detach();
        }
        var arr = [x for each(x in this[type])];
        if (arr.length) {
            arr.sort(function(a, b) a._count < b._count ? 1  :
                                    a._count > b._count ? -1 :
                                    a.text   < b.text   ? -1 :
                                    a.text   > b.text   ? 1  : -1);
            arr.forEach(function({text,type,count,enable}) {
                var menuitem = document.createElement("menuitem");
                menuitem.setAttribute("label", text);
                menuitem.setAttribute("acceltext", count);
                menuitem.setAttribute("filterType", type);
                menuitem.setAttribute("checked", enable);
                menuitem.setAttribute("type", "checkbox");
                menuitem.setAttribute("autoCheck", "false");
                menuitem.setAttribute("closemenu", "none");
                popup.appendChild(menuitem);
            }, this);
        }
    },
    onBlockedItemCommand: function(event) {
        this.addFilter("@@" + event.target.getAttribute("label"));
    },
    onBlockedItemClick: function(event) {
        if (!event.button) return;
        if (event.button === 1) {
            this.openURL(event.target.getAttribute("label"));
            closeMenus(event.target);
        } else if (event.button === 2) {
            let temp = this.getFilterFromText(event.target.getAttribute("matched"));
            closeMenus(event.target);
            if (temp) {
                this.editFilter(temp.text, temp.type);
            }
        }
        event.preventDefault();
        event.stopPropagation();
    },
    onFilterItemCommand: function(event) {
        var menuitem = event.target;
        var type = menuitem.getAttribute("filterType");
        var text = menuitem.getAttribute("label");
        if (!type || !text) return;

        if (event.shiftKey) {
            this.removeFilter(text, type);
            menuitem.parentNode.removeChild(menuitem);
        } else {
            var obj = this[type][text];
            obj.enable = !obj.enable;
            menuitem.setAttribute("checked", obj.enable);
        }
    },
    onFilterItemClick: function(event) {
        if (event.button === 1) return this.onFilterItemCommand(event);
        if (event.button != 2) return;
        var menuitem = event.target;
        var type = menuitem.getAttribute("filterType");
        var text = menuitem.getAttribute("label");
        if (!type || !text) return;
        event.preventDefault();
        event.stopPropagation();
        closeMenus(menuitem);
        this.editFilter(text, type);
    },
    onClick: function(event) {
        if (!event.button) {
            this.disabled = !this.disabled;
        } else if (event.button === 1) {
            this.loadINI();
        }
    },
    updateIcon: function() {
        if (this.disabled) {
            this.icon.setAttribute("state", "off");
            this.icon.setAttribute("tooltiptext", "urlfilter off");
        } else {
            this.icon.setAttribute("tooltiptext", "urlfilter on");
            if ("blocked" in this.getFocusedWindow()) {
                this.icon.setAttribute("state", "blocked");
            } else {
                this.icon.setAttribute("state", "on");
            }
        }
    },
    observe: function(subject, topic, data) {
        if (topic === "http-on-modify-request") {
            var http = subject.QueryInterface(Ci.nsIHttpChannel).QueryInterface(Ci.nsIRequest);
            var {spec, host, scheme} = http.URI;
            if (this.onceThroughURL === spec)
                return this.onceThroughURL = null;
//            if (spec.length > 1000)
//                return this.log("URLЭто слишком долго." + spec);

            var win = this.getRequesterWindow(http);
            var {href:pageURL, host:pageHost, protocol:pageProtocol } = win ? win.location : { href:"", host:""};
            var isImage = !http.loadGroup || !http.loadGroup.groupObserver;
            var isThird = !(scheme == pageProtocol && host == pageHost);
            var matched;

            var obj = this.scanX(spec, "white", pageHost, isThird, isImage) || 
                this.scanX(spec, "kanzen", pageHost, isThird, isImage) || 
                this.scanX(spec, "zenpou", pageHost, isThird, isImage) || 
                this.scanX(spec, "kouhou", pageHost, isThird, isImage) || 
                this.scanX(spec, "bubun", pageHost, isThird, isImage) || 
                this.scanX(spec, "seiki", pageHost, isThird, isImage);
            if (obj) {
                obj.count = new Date().getTime();
                if (obj.type === "white") return;
                matched = obj.text;
            } else if (isImage && host.indexOf("amazon.") >= 0 && pageHost.indexOf("amazon.") === -1) {
                matched = "Default::images-amazon.com";
            }
            if (!matched) return;
            http.cancel(Cr.NS_ERROR_FAILURE);

            if (!win) return;
            if (!win.blocked) {
                win.blocked = {};
                this.updateIcon();
            }
            win.blocked[spec] = matched;
        }
    },
    scanX: function(url, type, pageHost, isThird, isImage) {
        if (type === "white")
            for each(let obj in this.white)
                if (obj.match(url) && obj.matchDoller(pageHost, isThird, isImage))
                    return obj;
//        if (type === "kanzen")
//            for each(let obj in this.kanzen)
//                if (obj.match(url) && obj.matchDoller(pageHost, isThird, isImage))
//                    return obj;
        if (type === "kanzen") {
            let obj = this.kanzen[url];
            if (obj && obj.match(url) && obj.matchDoller(pageHost, isThird, isImage)){
                return obj;
            }
        }

        if (type === "zenpou")
            for each(let obj in this.zenpou)
                if (obj.match(url) && obj.matchDoller(pageHost, isThird, isImage))
                    return obj;
        if (type === "kouhou")
            for each(let obj in this.kouhou)
                if (obj.match(url) && obj.matchDoller(pageHost, isThird, isImage))
                    return obj;
        if (type === "bubun")
            for each(let obj in this.bubun)
                if (obj.match(url) && obj.matchDoller(pageHost, isThird, isImage))
                    return obj;
        if (type === "seiki")
            for each(let obj in this.seiki)
                if (obj.match(url) && obj.matchDoller(pageHost, isThird, isImage))
                    return obj;
        return null;
    },
    getRequesterWindow: function(aRequest) {
        if (aRequest.notificationCallbacks) {
            try {
                return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
            } catch (ex) { }
        }
        if (aRequest.loadGroup && aRequest.loadGroup.notificationCallbacks) {
            try {
                return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
            } catch (ex) { }
        }
        return null;
    },
    getFilterFromText: function(aText) {
        return this.kanzen[aText] || this.zenpou[aText] || this.kouhou[aText] 
               this.bubun[aText]  || this.seiki[aText]  || this.white[aText];
    },
    getTypeFromText: function(aText) {
        return aText in this.kanzen ? "kanzen" : 
               aText in this.zenpou ? "zenpou" : 
               aText in this.kouhou ? "kouhou" : 
               aText in this.bubun  ? "bubun"  : 
               aText in this.seiki  ? "seiki"  : 
               aText in this.white  ? "white"  : 
               aText in this.sonota ? "sonota" : "";
    },
    addFilter: function(aURL) {
        var des = this.WRITE_DESCRIPTION.replace(/\;\s+/g, "");
        var ok = prompt(des, aURL || this.lastInputFilter || getBrowserSelection());
        if (!ok) return;
        this.lastInputFilter = ok;
        var temp = new this.Filter(ok);
        if (temp.type === "sonota") return;
        this.addData(temp);
        this.lastInputFilter = "";
        this.saveFlag = true;
        return temp;
    },
    removeFilter: function(aText, aType){
        aType || (aType = this.getTypeFromText(aText));
        if (!aType) return false;
        this.removed[aText] = true;
        delete this[aType][aText];
        this[aType + "Filter"] = null;
        this.saveFlag = true;
        return true;
    },
    editFilter: function(aText, aType) {
        aType || (aType = this.getTypeFromText(aText));
        if (!aType) return false;
        var temp = this[aType][aText];
        var des = this.WRITE_DESCRIPTION.replace(/\;\s+/g, "");
        var ok = prompt(des, temp.text);
        if (!ok) return;
        temp.text = ok;
        temp.count = null;

        delete this[aType][aText];
        this[temp.type][temp.text] = temp;
        this.removed[aText] = true;
        this[aType + "Filter"] = null;
        this[temp.type + "Filter"] = null;
        return temp;
    },
    addData: function(temp) {
        var {text, line, type} = temp;
        var tt = this[type];
        if (typeof line === "number" || !tt[text] || typeof tt[text].line != "number") {
            if (type === "sonota") {
                tt["[line: "+ line +"] " + text] = temp;
            } else {
                tt[text] = temp;
                this[type + "Filter"] = null;
            }
        }
    },
    importJSON: function() {
        var data = this.loadFile("urlfilter.json");
        if (!data) return null;

        data = JSON.parse(data);
        for (let [filterType, filterHash] in Iterator(data)) {
            for (let n in filterHash) {
                let enable = filterHash[n].enable;
                if (filterType === "seiki" && /\\|\[/.test(n)) 
                    n = "/" + n + "/";
                let temp = new this.Filter(n);
                temp.enable = enable;
                this.addData(temp);
            }
        }
        this.log("JSON Я прочел");
        this.saveFlag = true;
        return true;
    },
    loadINI: function() {
        var file = this.file;
        if (!file.exists() || !file.isFile()) return;
        if (this.lastModifiedTime === file.lastModifiedTimeOfLink) return;
        this.lastModifiedTime = this.file.lastModifiedTimeOfLink;
        var data = this.loadText(file);
        if (!data) return;
        data = data
            .replace(/=UUID:.*/g, "")
            .replace(/\n\"(.*)\"/g, "\n$1")
            ;
        ["kanzen", "zenpou", "kouhou", "bubun", "seiki", "white", "sonota"].forEach(function(type){
            var hash = this[type];
            for (let [key, {ini}] in Iterator(hash)) {
                if (ini) delete hash[key];// ini Удалить фильтр, загруженное из
            }
        }, this);
        data.split(/\r?\n/).forEach(function(line, i){
            var temp = new this.Filter(line);
            temp.line = i;
            temp.ini = true;
            this.addData(temp);
        }, this);
        this.log("ini Я прочел");
        return true;
    },
    saveINI: function() {
        var file = this.file;
        if (file.exists() && !file.isFile()) return;
        var res = [];
        var added = [];
        ["kanzen", "zenpou", "kouhou", "bubun", "seiki", "white", "sonota"].forEach(function(type){
            var hash = this[type];
            for each(let temp in hash) {
                if (temp.ini) res[temp.line] = temp;
                else added.push(temp);
            }
        }, this);
        res = res.map(function(temp) temp ? temp.text : "");

        added.forEach(function({text, line}) {
            if (typeof line === "number") {
                res[line] = res[line] ? res[line] + "\n" + text: text;
            } else {
                res.push(text);
            }
        });
        res = res.join("\n");
        if (!file.exists())
            res = this.WRITE_DESCRIPTION + "\n" + res;
        this.saveText(file, res);
        this.saveFlag = false;
        this.lastModifiedTime = file.lastModifiedTimeOfLink;
    },
    editINI: function() {
        if (!this.file.exists()) return alert("urlfilter.ini Не нашли");
        var editor = Services.prefs.getCharPref("view_source.editor.path");
        if (!editor) return alert("Путь редактор не установлен.\n view_source.editor.path Пожалуйста, установите");
        try {
            var UI = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
            UI.charset = window.navigator.platform.toLowerCase().indexOf("win") >= 0? "Shift_JIS": "UTF-8";
            var path = UI.ConvertFromUnicode(this.file.path);
            var app = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
            app.initWithPath(editor);
            var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
            process.init(app);
            process.run(false, [path], 1);
        } catch (e) {}
    },
    savePrefSetting: function() {
        var ds = {}, ct = {};
        var data = [].concat(
            [x for each(x in this.kanzen)]
            ,[x for each(x in this.zenpou)]
            ,[x for each(x in this.kouhou)]
            ,[x for each(x in this.bubun)]
            ,[x for each(x in this.seiki)]
            ,[x for each(x in this.white)]);
        data.forEach(function({text,enable,_count}){
            if (_count) ct[text] = _count;
            if (!enable) ds[text] = true;
        }, this);
        this.counter = ct;
        this.disables = ds;
        var str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
        str.data = JSON.stringify(this.counter);
        this.prefs.setComplexValue("counter", Ci.nsISupportsString, str);
        var str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
        str.data = JSON.stringify(this.disables);
        this.prefs.setComplexValue("disables", Ci.nsISupportsString, str);
    },
    loadPrefSetting: function() {
        try {
            this.counter = JSON.parse(this.prefs.getCharPref("counter"));
        } catch (e) {}
        try {
            this.disables = JSON.parse(this.prefs.getCharPref("disables"));
        } catch (e) {}
    },
    openURL: function(aURL) {
        this.onceThroughURL = aURL;
        openLinkIn(aURL, "tab", {});
    },
    copyString: function(aStr) {
        Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper).copyString(aStr);
    },
    log : function() {
        Application.console.log("[urlfilter]" + Array.slice(arguments));
    },
    loadText: loadText,
    saveText: saveText,
    loadFile: loadFile,
    saveFile: saveFile,
};

window.gURLFilter.init();


function loadText(aFile) {
    if (!aFile.exists() || !aFile.isFile()) return null;
    var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
    var sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
    fstream.init(aFile, -1, 0, 0);
    sstream.init(fstream);
    var data = sstream.read(sstream.available());
    try { data = decodeURIComponent(escape(data)); } catch(e) {}
    sstream.close();
    fstream.close();
    return data;
}
function saveText(aFile, data) {
    var suConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
    suConverter.charset = "UTF-8";
    data = suConverter.ConvertFromUnicode(data);
    var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
    foStream.init(aFile, 0x02 | 0x08 | 0x20, 0664, 0);
    foStream.write(data, data.length);
    foStream.close();
};
function loadFile(aLeafName) {
    var aFile = Services.dirsvc.get("UChrm", Ci.nsILocalFile);
    aFile.appendRelativePath(aLeafName);
    return loadText(aFile);
}
function saveFile(aLeafName, data) {
    var aFile = Services.dirsvc.get("UChrm", Ci.nsILocalFile);
    aFile.appendRelativePath(aLeafName);
    saveText(aFile);
};

function log() { Application.console.log(Array.slice(arguments)) }
function $(id) { return document.getElementById(id); }
function $$(exp, doc) { return Array.prototype.slice.call((doc || document).querySelectorAll(exp)); }
function $A(args) { return Array.prototype.slice.call(args); }
function U(text) 1 < 'あ'.length ? decodeURIComponent(escape(text)) : text;
function $C(name, attr) {
    var el = document.createElement(name);
    if (attr) Object.keys(attr).forEach(function(n) el.setAttribute(n, attr[n]));
    return el;
}
function addStyle(css) {
    var pi = document.createProcessingInstruction(
        'xml-stylesheet',
        'type="text/css" href="data:text/css;utf-8,' + encodeURIComponent(css) + '"'
    );
    return document.insertBefore(pi, document.documentElement);
}

})('\
/*\
 * Some icons by Yusuke Kamiyamane. All rights reserved.\
 * http://p.yusukekamiyamane.com/\
 */\
#urlfilter-icon,\
#context-urlfilter-add-image,\
#context-urlfilter-add-frame {\
  list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB5ElEQVQ4jZ3OQUgiURzH8R8y4CAooiCEeFLRq9QQQwSBdApBpEMMpAdpKQ8TrHSaw7peBOnQIeiSgZWF0im6hBDUwByWd2io1qi85AheOs15+O9lW0ifl/3Dgwff9z78gbFpAN4OoLSBZgfQO4D+9640AO/4+y9zAsycAy1jYa4/Kpdsu6o5dlVzRuWSbSzM9c+B1gkww/3cALynwNnTWnb0sbNNw3z+y/nY2aantezoFDjjbnIMKHfzs/2hukW9XM65SCYHdY/HrHs85kUyOejlcs5Q3aK7+dn+MaBMAEdA82WzYN9nMtSKxQYVoK4BYQ0IV4B6KxYb3Gcy9LJZsI+A5gRwCOhvpaJjpNNUc7tNDQh/Ng0I19xu00in6a1UdA4BfQI4AFhvI083skwVgI33CsBuZJl+F9fpgNOxD7AHZZW6qdRUoJtK0YOySvs8YA9gj9kVuk0kpgK3iQQ9ZldojwfsAux1eYlYJDIVYJEIvS4v0S4PqAHsfVGm51BoKvAcCtH7okw1HiBJ0oZpmtRud0mSpDKnf2OM0eWlzu2IRqP/gHg8rvL6J8Dr8Pl8qmVZdHVlUCAQ+D7eg8GgalkWXV//Ir/fP9EhimJeEISKIAg/RVEs8LrL5fohCEKV1/97/gCuDO25VCRH2wAAAABJRU5ErkJggg==");\
}\
\
#urlfilter-icon[state="off"] {\
  filter: url("chrome://mozapps/skin/extensions/extensions.svg#greyscale);"\
//filter: grayscale(1);\
}\
\
#urlfilter-icon[state="blocked"] {\
  list-style-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACZElEQVQ4jZ3MXUhTcRjH8Z92wJOk+FKDkBGk4oJuZI6aIUnDbkIwSZJBkwjLJCa0JGKCyxvZ8KILqyG+m62thWgLMt9ag6PJP3DaUGej1GN6QAmbuwnH01WH1NNNP3iuvjwfYN86gBQPYHQDPR4g4AECbqDnnRGdbAAD069xff+PvD7guAvoF84VRDYstdFokzUebbLGv9+7Ff1oP/rzx/Lj2LT30IuJLvAHnjuAlGfA81Bl2cZWfR2tmUzyRe4baGWsknZjLyk0eF4SPLh8AOgFjB/OaCNr5ts0X14e92o0q47k5GDLkeTZscb02PZXO1VUVNDmonXX/yTh7YQN3B6gE+gJ11RFZ0pLqT8nZ9UGOKxAlucOrk65Tq9uiw0EgNYXakjoOrE+7oJhD9AOBL7U3ogLBgM1JyUFrUAWgITRPjiXQ9c25+cu0htfPc3OFNOnibO/RvvgBJAgA08BNl9tonG9nmwAAwBfGwr9r45Nflu8QJGQlgBQJKSlSKiA3ntTJn1tKJSBVoDNGa/QSH6+DAw60R6eOilJS6dICmsotvWApLCGpLCGFoUsadCJdhl4BLDPZZfIn5cnA70OtHbbIXTbIbTVYcdlOUzehnTqqMNOtx1CrwOtMtACsKWSYmJqtQz8PRvAmFpNSyXF1KLQ0QywlSI9LahU/wQWVCpaKdJTsxKg0+mqg8Egud0jpNPpLAr9JmOMhoYCih3Z2dkykJuba1bqfwCljtTUVLMoiuTzCZSRkXF3f8/MzDSLokjDw9OUlpZ2oIPneRPHcTaO4x7yPF+l1BMTExs5jmtS6v+932DjNskjJRnPAAAAAElFTkSuQmCC");\
}\
\
/* И с ног на голову, чтобы показать значок в U. ! Расстроен знак… */\
#urlfilter-icon,\
#context-urlfilter-add-image .menu-iconic-icon,\
#context-urlfilter-add-frame .menu-iconic-icon{\
  -moz-transform:  rotate(180deg);\
}\
\
\
.urlfilter-menuend-separator:last-child,\
#context-copyimage[hidden="true"] ~ #context-urlfilter-add-image,\
#frame[hidden="true"] ~ #context-urlfilter-add-frame,\
#urlfilter-menupopup[hasmanager="false"] > #urlfilter-run-manager,\
#urlfilter-menupopup[hasmanager="false"] > #urlfilter-run-elements\
  { display: none; }\
\
\
','\
; Opera Из urlfilter.ini Из такого идеальный матч\
; Джокер(*)После шаблоны доступны и\
; ・"://*."  Chrome Из @match Таких как операционные\
; ・/~/ В регулярном выражении. Дело не отличается. Частичное совпадение.\
; ・@@ Строки, начинающиеся с белого списка\
'
);


В инициализацию кнопки Custom Buttons. В адресной строке "U" ЛКМ - вкл - выкл. ПКМ - настройки
urlfilter.uc.js Пример конфигурационного файла

Выделить код

Код:

; Opera Из urlfilter.ini Из такого идеальный матч; Джокер(*)После шаблоны доступны и; ・"://*."  Chrome Из @match Таких как операционные; ・/~/ В регулярном выражении. Дело не отличается. Частичное совпадение.; ・@@ Строки, начинающиеся с белого списка


; urlfilter.uc.js Пример конфигурационного файла

http://www.google-analytics.com/ga.js
http://pagead2.googlesyndication.com/pagead/show_ads.js
*/google_ads.js
*.doubleclick.net/*
*/google_afc*
*/googleAnalytics2.js

http://ai.yimg.jp/bdv/*$image
http://im.ov.yahoo.co.jp/*
http://b6.yahoo.co.jp/b?*
*.yimg.com/ad*
*/A-affiliate2/*

http://affiliate.*
http://b.st-hatena.com/js/bookmark_button*
http://bnr.rssad.jp/*
http://displayads.*
http://rss.rssad.jp/rss/img/*
*/image/ad/*$image
*?ad=*

*/adframe.js
*/adsense.js
*/analyze.js
*-ad.*
*.sitemeter.com/*
*/ad/bnr_*
*/adfeeds/*
*/ads/*
*/advertise/*
*/affiliate/*
*/ana/processor.php?*
*/analytics/*
*/analyze/*
*/analyzer.php?*
*/bana/*
*/banner/*
*/banners/*

/https?://analyzer?[0-9]*\b/
/https?://ads?[0-9]*\b/
@@http://ad.impress.co.jp/*
/https?://banners?[0-9]?\b/
/https?://x[0-9]?\b/
/https?://mf[0-9]?\b/
/https?://js[0-9]\.infoseek\.co\.jp//
/https?://cn?t[0-9]?\b/

/\bads?[0-9]*.(?:js|css|jpe?g|gif|png)/
/\D120[x_-]*60\D/
/\D468[x_-]*60\D/
/\D728[x_-]*90\D/
/\D150[x_-]*600\D/

Отсутствует

 

№824-03-2015 15:46:38

AlAvis
Участник
 
Группа: Members
Зарегистрирован: 16-06-2014
Сообщений: 572
UA: unknown 0.0

Re: Silent Block - urlfiltr для Firefox

HaGENСогласен , что больше подходит сравнение с переделками AB по принципу "мы тебя кастрируем - будешь быстро бегать" , но пример привёл о том - что тормоза практически не заметны с таким количеством правил . Сомнения вызывает само использование RegExp . И , кстати , все забывают что к таким обрубкам ставят всякие носкрипты , йесскрипты и полиси . AB же справляется сам , причём блокирует только "вредные" скрипты , совсем без них на большинстве сайтов делать нечего .
okkamas_knifeВо времена медленной лисы пробовал вытащить правила из списков , что бы уменьшить нагрузку . В итоге одна подписка превратиласть в 7 и груду дополнений . :lol: Правда моя работа связана с просмотром большого количества сайтов и другим хватит меньшего , но уже написал что на скорость и загрузку процессора влияет терпимо , удобство важнее .

Отсутствует

 

№924-03-2015 18:59:21

HaGEN
Забанен
 
Группа: Members
Зарегистрирован: 04-07-2013
Сообщений: 155
UA: Palemoon 25.0

Re: Silent Block - urlfiltr для Firefox

okkamas_knife

okkamas_knife пишет

... если я открываю новостной сайт я там читаю новости а не на дизайн смотрю поэтому  все элементы дизайна и стили со скриптами режу сразу потом скрываю всё так что остаётся только текст новости. и грузит меньше и изыски погромистов глаза не мозолят. ну а ноускрипт выключаю очень редко.
грубо говоря если я зашел за сосисками то хочу видеть только их а не кетчупы горчицы макароны тарелки вилки и поваров
.


attachment.php?attachmentid=108361&d=1408373664
SendInfo благодарю, не знал за такое. Попробуем.

Отсутствует

 

№1027-03-2015 19:01:28

SendInfo
.
 
Группа: Members
Зарегистрирован: 14-02-2011
Сообщений: 271
UA: Firefox 39.0

Re: Silent Block - urlfiltr для Firefox

HaGEN, как успехи с urlfilter?

Отсутствует

 

№1128-03-2015 21:49:20

HaGEN
Забанен
 
Группа: Members
Зарегистрирован: 04-07-2013
Сообщений: 155
UA: Palemoon 25.0

Re: Silent Block - urlfiltr для Firefox

SendInfo пишет

как успехи с urlfilter?

Вашпе-то это бомба! Есть всё что нужно, все что должно быть. Это первый подход в плугине для FF  именно верный, как должно. И иконка в строке есть и пункты в меню с блокировкой фрэйма/изображения. Всё работаем и в моём PaleMoon.
Но , есть одно НО. С этой кнопкой ровно в половину мой бравзер жрёть оперативы больше. ровно в половину. Один этот сайт открыт: без этой кнопки(с SilentBlock и прочими ~30шт. плугами):

Выделить код

Код:

palemoon.exe | 145,65 MB | 137,6 MB | 232,94 MB | SYS\user | Pale Moon web browser

Включаю кнопку:

Выделить код

Код:

palemoon.exe | 308,24 MB | 437,6 MB | 532,2 MB | SYS\user | Pale Moon web browser

... возможно получится его упаковать в расширение у кого-нибудь, может тогда будет работать как обычное расширение и соотв. не будет столько памяти поглощать.
Сильно я его не гонял ибо у мну подходит к концу создание своей сборки переносной и добиваю её. :cool: если есть ориг. без перевода, ибо он ужасен, скинь.
зы. заблокировать на ютубе каменты в фильтре SilentBlock:

Выделить код

Код:

# каменты из гугла для ютуб
\/widget\/render\/comments\?usegapi=\d&first_party_property=YOUTUBE&\w

Отсутствует

 

№1229-03-2015 19:15:46

SendInfo
.
 
Группа: Members
Зарегистрирован: 14-02-2011
Сообщений: 271
UA: Firefox 39.0

Re: Silent Block - urlfiltr для Firefox

HaGEN пишет

... возможно получится его упаковать в расширение у кого-нибудь, может тогда будет работать как обычное расширение и соотв. не будет столько памяти поглощать.

Нет, его надо переписать под СВ. Изначально оно написано для userChromeJS / uc как и UserScriptLoader

HaGEN пишет

если есть ориг. без перевода, ибо он ужасен, скинь.

Нет проблем) Не нравится гуглоперевод - осваивай японский)) urlfilter · Griever

Отсутствует

 

№1329-03-2015 20:24:55

HaGEN
Забанен
 
Группа: Members
Зарегистрирован: 04-07-2013
Сообщений: 155
UA: Palemoon 25.0

Re: Silent Block - urlfiltr для Firefox

Понятно.
Пасиб за ориг. Переведу для себя.

Отсутствует

 

№1431-03-2015 18:51:10

HaGEN
Забанен
 
Группа: Members
Зарегистрирован: 04-07-2013
Сообщений: 155
UA: Palemoon 25.0

Re: Silent Block - urlfiltr для Firefox

SendInfo пишет

Изначально оно написано для userChromeJS / uc

Поставил плуг UC , этот скрипт добавил, перезагрузил бравзер... всё клёва. Работает и памяти есть столько же. Теперь думаю что выбрать из друх отличных плагинов:
SilentBlock или UC со скриптом scratch_one-s_head.gif и то и то годно! Всё как мну нравицца - настройки в ini|txt и маленькое.

Отсутствует

 

№1531-03-2015 20:26:08

SendInfo
.
 
Группа: Members
Зарегистрирован: 14-02-2011
Сообщений: 271
UA: Firefox 40.0

Re: Silent Block - urlfiltr для Firefox

HaGEN, чтобы было веселее думать censureblock - URLFilter, CensureBlock - Google Project Hosting

P.S. В UC ещё кучу разных скриптов накидать можно.

Отсутствует

 

№1627-09-2015 02:15:51

jars
Забанен
 
Группа: Members
Зарегистрирован: 28-07-2015
Сообщений: 235
UA: Palemoon 25.0

Re: Silent Block - urlfiltr для Firefox

Кнопка для этого плугина. Вкл-Выкл логирование в консоль + перегрузка правил. Редактирование по ПКМ + модификатор.
URl

скрытый текст

Выделить код

Код:

custombutton://%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0D%0A%3Ccustombutton%20xmlns%3Acb%3D%22http%3A//xsms.nm.ru/custombuttons/%22%3E%0A%20%20%3Cname%3E%u041A%u043D%u043E%u043F%u043A%u0430%20%u0434%u043B%u044F%20%u043F%u043B%u0443%u0433%u0430%20SilentBlock%20%5B%20Enable%20/%20Disable%20%u0436%u0443%u0440%u043D%u0430%u043B%20+%20%u043F%u0435%u0440%u0435%u0433%u0440%u0443%u0437%u043A%u0430%20%u043F%u0440%u0430%u0432%u0438%u043B%5D%3C/name%3E%0A%20%20%3Cimage%3E%3C%21%5BCDATA%5Bdata%3Aimage/png%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAADAFBMVEVPctpQc9tRc9tRdNtSdNtSddtTddtTdttUdttUdtxVd9xWd9xWeNxXeNxXedtXedxYedxYetxZedxZetxZet1aetxae9xae91be91bfN1cfN1cfd1dfd1efd1eft1eft5ff91ff95ggN5hgN5hgd5kg95lg95mhd9nhd9oht9ph99qh99qiOBriN9rieBsieBtid9uiuBui+Fvi+FvjOBvjOFwjOFxjeFyjuByjuFzjuJzj+Fzj+J0j+F0j+J0kOJ1kOJ4k+J5k+N5lON6lON6lOR7luJ8luOAmeSAmuSBmuOCm+SDnOSEnOSEneWFneWGnuSGnuWHn+WJoOWJoeWKoeaOpOeRpueTqeiUqeiVq+iXrOiZreiZremarembr+mgsuqhs+qoueyru+2svO2sveytvO2uve2vv+2wv+61xO62xO+2xe+4xu+5x/C8yfC9yfDAzfDAzfHBzfHBzvDDz/HEz/HF0PLF0fLG0fLI0vPM1fPM1vTO1/PO2PPR2vTS2vTU3fXV3fXV3fbZ4Pba4vbc4/fd4/fd5Pff5fjf5vjg5vjh5/jm6/nn7Pro7Pno7fnp7fnq7vrr7/rs8Pru8fvv8vrw8/vy9Pzy9fvz9vv09vz19/z29/v4+f35+v36+/37/P78/P78/f38/f79/f79/v39/v7+/v7+/v/+//////7///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADlA2HhAAAAu0lEQVQY01XPvQtBURgG8FPq4n50br1XT8quLMpgNiOSicVgsSmx2/wDtyyXgeWk54/0novwDufj19v7nGMASJIkVvdyNXoISDpBlby+AG2VIeRCZjAiKjOVEAPSwiTsq+wcl2h6VeBK5+ZkIGcGJfCYwtJtsXk0YGyhEXdBXfsNx5oiHe1hii5PmSb42HhKxxEmkX+CyQLNDdfkAmX5ofOWZHYffcGxOLyvCja/+aGUD/jfxrVK7xf+6gmwFEG72pgBPgAAAABJRU5ErkJggg%3D%3D%5D%5D%3E%3C/image%3E%0A%20%20%3Cmode%3E0%3C/mode%3E%0A%20%20%3Cinitcode%3E%3C%21%5BCDATA%5Bthis.onclick%20%3D%20function%28e%29%20%7B%0A%09this.PS%20%3D%20Components.classes%5B%27@mozilla.org/preferences-service%3B1%27%5D%20.getService%28Components.interfaces.nsIPrefBranch%29%3B%0A%09var%20name%3D%22extensions.silentblock.logging.enabled%22%3B%0A%0A%20%20%20%20%20if%20%28%20e.button%20%3D%3D%200%20%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20this.PS.setBoolPref%28name%2C1%29%3B%20this.checked%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20Services.console.logStringMessage%28%22%u0412%u041A%u041B%u044E%u0447%u0435%u043D%20%u0432%u044B%u0432%u043E%u0434%20%u0441%u043E%u043E%u0431%u0449%u0435%u043D%u0438%u044F%20SilentBlock%22%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20if%20%28%20e.button%20%3D%3D%201%20%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20SchuzakJp.SilentBlock.init.XPCBlocker.loadPatternList%28%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20Services.console.logStringMessage%28%22%u041F%u0440%u0430%u0432%u0438%u043B%u0430%20SilentBlock%20%u043F%u0435%u0440%u0435%u0433%u0440%u0443%u0436%u0435%u043D%u044B%22%29%3B%0A%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%0A%20%20%20%20%20if%20%28%20e.button%20%3D%3D%202%20%26%26%20%21e.ctrlKey%20%26%26%20%21e.shiftKey%20%26%26%20%21e.altKey%29%20%7B%0A%20%20%20%20%20%20%20%20%20%20e.preventDefault%20%28%29%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20this.PS.setBoolPref%28name%2C0%29%3B%20this.checked%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20Services.console.logStringMessage%28%22%u0412%u042B%u041A%u041B%u044E%u0447%u0435%u043D%20%u0432%u044B%u0432%u043E%u0434%20%u0441%u043E%u043E%u0431%u0449%u0435%u043D%u0438%u044F%20SilentBlock%22%29%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%7D%3B%0A%0Athis.tooltipText%20%3D%20%22%u041B%u041A%u041C%20-%20%u0412%u041A%u041B%u044E%u0447%u0438%u0442%u044C%20%u0432%u044B%u0432%u043E%u0434%20%u0441%u043E%u043E%u0431%u0449%u0435%u043D%u0438%u0439%20%u0432%20%u043A%u043E%u043D%u0441%u043E%u043B%u044C%20%u0440%u0430%u0441%u0448%u0438%u0440%u0435%u043D%u0438%u044F%20SilentBlock%5Cn%5C%0A%u041F%u041A%u041C%20-%20%u0412%u042B%u041A%u041B%u044E%u0447%u0438%u0442%u044C%5Cn%5C--------------------------------------------------------------%5Cn%5C%0A%u0421%u041A%u041C%20-%20%u041F%u0435%u0440%u0435%u0433%u0440%u0443%u0437%u0438%u0442%u044C%20%u043F%u0440%u0430%u0432%u0438%u043B%u0430%20SilentBlock%5Cn%5C%0A%u041B%u044E%u0431%u043E%u0439%20%u043C%u043E%u0434%u0438%u0444%u0438%u043A%u0430%u0442%u043E%u0440%20+%20%u041F%u041A%u041C%20-%20%u0440%u0435%u0434%u0430%u043A%u0442%u0438%u0440%u043E%u0432%u0430%u0442%u044C%20%u043A%u043D%u043E%u043F%u043A%u0443%22%3B%0A%5D%5D%3E%3C/initcode%3E%0A%20%20%3Ccode%3E%3C%21%5BCDATA%5B/*CODE*/%5D%5D%3E%3C/code%3E%0A%20%20%3Caccelkey%3E%3C%21%5BCDATA%5B%5D%5D%3E%3C/accelkey%3E%0A%20%20%3Chelp%3E%3C%21%5BCDATA%5B%5D%5D%3E%3C/help%3E%0A%20%20%3Cattributes/%3E%0A%3C/custombutton%3E


Init
скрытый текст

Выделить код

Код:

this.onclick = function(e) {
    this.PS = Components.classes['@mozilla.org/preferences-service;1'] .getService(Components.interfaces.nsIPrefBranch);
    var name="extensions.silentblock.logging.enabled";

     if ( e.button == 0 ) {
             this.PS.setBoolPref(name,1); this.checked = true;
             Services.console.logStringMessage("ВКЛючен вывод сообщения SilentBlock");
             }

     if ( e.button == 1 ) {
            SchuzakJp.SilentBlock.init.XPCBlocker.loadPatternList();
            Services.console.logStringMessage("Правила SilentBlock перегружены");
       }
     
     if ( e.button == 2 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
          e.preventDefault ();
            this.PS.setBoolPref(name,0); this.checked = false;
            Services.console.logStringMessage("ВЫКЛючен вывод сообщения SilentBlock");
        }
 };

this.tooltipText = "ЛКМ - ВКЛючить вывод сообщений в консоль расширения SilentBlock\n\
ПКМ - ВЫКЛючить\n\--------------------------------------------------------------\n\
СКМ - Перегрузить правила SilentBlock\n\
Любой модификатор + ПКМ - редактировать кнопку";

Отсутствует

 

Board footer

Powered by PunBB
Modified by Mozilla Russia
Copyright © 2004–2020 Mozilla Russia GitHub mark
Язык отображения форума: [Русский] [English]