mirror of
https://github.com/anticensority/runet-censorship-bypass.git
synced 2024-11-24 02:13:43 +03:00
Refactor err handlers, unixify newlines, restructure files
This commit is contained in:
parent
257495168c
commit
6750872ea5
|
@ -1,26 +1,30 @@
|
|||
# О расширении
|
||||
|
||||
Обход интернет-цензуры в России пока что не является преступлением.
|
||||
|
||||
Расширение позволяет обходить блокировки РосКомНадзора, давая вам доступ
|
||||
к библиотекам, энциклопедиям, сайтам оппозиционеров, а также к неповинным
|
||||
сайтам, случайно заблокированным в силу разных причин.
|
||||
|
||||
Проксирует только заблокированные сайты, оставляя нетронутыми все остальные.
|
||||
|
||||
Устанавливает PAC-скрипт, работающий через сервера anticenz.org и antizapret.prostovpn.org.
|
||||
|
||||
Обновляет PAC-скрипт каждые 4 часа, что составляет примерно 7MB трафика в сутки.
|
||||
Также расширение постоянно потребляет ~15MB памяти для информирования о блокировках через иконку.
|
||||
|
||||
Синяя лента – кампания фонда EFF в защиту свобод слова, прессы и союзов.
|
||||
|
||||
Если расширение не работает: https://git.io/vgDDj
|
||||
|
||||
Антицензура на Реддите: https://www.reddit.com/r/anticensorship_russia
|
||||
Группа в G+: https://goo.gl/Lh0Cjh
|
||||
История изменений: https://github.com/ilyaigpetrov/anti-censorship-russia/releases
|
||||
|
||||
# Дополнительно
|
||||
|
||||
Иконка синей ленты: http://www.iconsdb.com/icon-sets/cardboard-blue-icons/ribbon-12-icon.html
|
||||
# Dev
|
||||
|
||||
Linting JS: `npm run lint`
|
||||
|
||||
# О расширении
|
||||
|
||||
Обход интернет-цензуры в России пока что не является преступлением.
|
||||
|
||||
Расширение позволяет обходить блокировки РосКомНадзора, давая вам доступ
|
||||
к библиотекам, энциклопедиям, сайтам оппозиционеров, а также к неповинным
|
||||
сайтам, случайно заблокированным в силу разных причин.
|
||||
|
||||
Проксирует только заблокированные сайты, оставляя нетронутыми все остальные.
|
||||
|
||||
Устанавливает PAC-скрипт, работающий через сервера anticenz.org и antizapret.prostovpn.org.
|
||||
|
||||
Обновляет PAC-скрипт каждые 4 часа, что составляет примерно 7MB трафика в сутки.
|
||||
Также расширение постоянно потребляет ~15MB памяти для информирования о блокировках через иконку.
|
||||
|
||||
Синяя лента – кампания фонда EFF в защиту свобод слова, прессы и союзов.
|
||||
|
||||
Если расширение не работает: https://git.io/vgDDj
|
||||
|
||||
Антицензура на Реддите: https://www.reddit.com/r/anticensorship_russia
|
||||
Группа в G+: https://goo.gl/Lh0Cjh
|
||||
История изменений: https://github.com/ilyaigpetrov/anti-censorship-russia/releases
|
||||
|
||||
# Дополнительно
|
||||
|
||||
Иконка синей ленты: http://www.iconsdb.com/icon-sets/cardboard-blue-icons/ribbon-12-icon.html
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
{
|
||||
const extName = chrome.runtime.getManifest().name;
|
||||
const notify = (
|
||||
id,
|
||||
title,
|
||||
message,
|
||||
icon = 'default-128.png',
|
||||
context = extName
|
||||
) => chrome.notifications.create(
|
||||
id,
|
||||
{
|
||||
title: title,
|
||||
message: message,
|
||||
contextMessage: context,
|
||||
requireInteraction: true,
|
||||
type: 'basic',
|
||||
iconUrl: './icons/' + icon,
|
||||
}
|
||||
);
|
||||
|
||||
window.addEventListener('error', (err) => {
|
||||
|
||||
console.warn('Global error');
|
||||
notify('Unhandled error', 'Unhandled error', JSON.stringify(err),
|
||||
'ext-error-128.png');
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
|
||||
console.warn('Unhandled rejection. Throwing error.');
|
||||
event.preventDefault();
|
||||
throw event.reason;
|
||||
|
||||
});
|
||||
|
||||
chrome.proxy.onProxyError.addListener((details) => {
|
||||
|
||||
console.warn('PAC ERROR:', details);
|
||||
notify('pac-error', ' PAC !', JSON.stringify(details),
|
||||
'pac-error-128.png' );
|
||||
|
||||
});
|
||||
|
||||
chrome.proxy.settings.onChange.addListener((details) => {
|
||||
|
||||
console.log('Proxy settings changed.', details);
|
||||
// const ifOther = details.levelOfControl.startsWith('controlled_by_other');
|
||||
notify('Proxy change', 'Proxy changed', JSON.stringify(details),
|
||||
'no-control-128.png');
|
||||
|
||||
});
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
window.utils = {
|
||||
|
||||
areSettingsNotControlledFor(details) {
|
||||
|
||||
return ['controlled_by_other', 'not_controllable']
|
||||
.some( (prefix) => details.levelOfControl.startsWith(prefix) );
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
window.apis = {};
|
|
@ -0,0 +1,148 @@
|
|||
'use strict';
|
||||
|
||||
{ // Private namespace
|
||||
|
||||
const handlersState = function(key, value) {
|
||||
|
||||
console.log(key, value, '!');
|
||||
key = 'handlers-' + key;
|
||||
if (value === null) {
|
||||
return localStorage.removeItem(key);
|
||||
}
|
||||
if (value === undefined) {
|
||||
const item = localStorage.getItem(key);
|
||||
return item && JSON.parse(item);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
throw new TypeError('Converting Date format to JSON is not supported.');
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
|
||||
};
|
||||
|
||||
const ifPrefix = 'if-on-';
|
||||
|
||||
window.apis.errorHandlers = {
|
||||
|
||||
getEventsMap() {
|
||||
|
||||
return new Map([
|
||||
['pac-error', 'ошибки PAC скриптов'],
|
||||
['ext-error', 'ошибки расширения'],
|
||||
['no-control', 'утеря контроля над настройками'],
|
||||
]);
|
||||
|
||||
},
|
||||
|
||||
switch(onOffStr, eventName) {
|
||||
|
||||
if (!['on', 'off'].includes(onOffStr)) {
|
||||
throw new TypeError('First argument bust be "on" or "off".');
|
||||
}
|
||||
for(
|
||||
const name of (eventName ? [eventName] : this.getEventsMap().keys() )
|
||||
) {
|
||||
handlersState( ifPrefix + name, onOffStr === 'on' ? 'on' : null );
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
isOn(eventName) {
|
||||
|
||||
return handlersState( ifPrefix + eventName);
|
||||
|
||||
},
|
||||
|
||||
ifNotControlled: null,
|
||||
|
||||
isNotControlled(details) {
|
||||
|
||||
this.ifNotControlled = window.utils.areSettingsNotControlledFor( details );
|
||||
if (this.ifNotControlled) {
|
||||
chrome.browserAction.disable();
|
||||
} else {
|
||||
chrome.browserAction.enable();
|
||||
}
|
||||
return this.ifNotControlled;
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// INIT
|
||||
chrome.proxy.settings.get(
|
||||
{},
|
||||
(details) => window.apis.errorHandlers.isNotControlled(details)
|
||||
);
|
||||
|
||||
{
|
||||
|
||||
const extName = chrome.runtime.getManifest().name;
|
||||
|
||||
const mayNotify = function(
|
||||
id, title, message,
|
||||
icon = 'default-128.png',
|
||||
context = extName
|
||||
) {
|
||||
|
||||
if ( !window.apis.errorHandlers.isOn(id) ) {
|
||||
return;
|
||||
}
|
||||
chrome.notifications.create(
|
||||
id,
|
||||
{
|
||||
title: title,
|
||||
message: message,
|
||||
contextMessage: context,
|
||||
requireInteraction: true,
|
||||
type: 'basic',
|
||||
iconUrl: './icons/' + icon,
|
||||
isClickable: true,
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
window.addEventListener('error', (err) => {
|
||||
|
||||
console.warn('GLOBAL ERROR', err);
|
||||
mayNotify('ext-error', 'Unhandled error', JSON.stringify(err),
|
||||
'ext-error-128.png');
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
|
||||
console.warn('Unhandled rejection. Throwing error.');
|
||||
event.preventDefault();
|
||||
throw event.reason;
|
||||
|
||||
});
|
||||
|
||||
chrome.proxy.onProxyError.addListener((details) => {
|
||||
|
||||
if (window.apis.errorHandlers.ifNoControl) {
|
||||
return;
|
||||
}
|
||||
console.warn('PAC ERROR', details);
|
||||
mayNotify('pac-error', ' PAC !', JSON.stringify(details),
|
||||
'pac-error-128.png' );
|
||||
|
||||
});
|
||||
|
||||
chrome.proxy.settings.onChange.addListener((details) => {
|
||||
|
||||
console.log('Proxy settings changed.', details);
|
||||
const noCon = 'no-control';
|
||||
if ( window.apis.errorHandlers.isNotControlled(details) ) {
|
||||
mayNotify(noCon, 'Proxy changed', JSON.stringify(details),
|
||||
'no-control-128.png');
|
||||
} else {
|
||||
chrome.notifications.clear( noCon );
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
|
@ -9,39 +9,17 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
In background scripts use window.antiCensorRu public variables.
|
||||
In pages window.antiCensorRu is not accessible,
|
||||
In background scripts use window.apis.antiCensorRu public variables.
|
||||
In pages window.apis.antiCensorRu is not accessible,
|
||||
use chrome.runtime.getBackgroundPage(..),
|
||||
extension.getBackgroundPage is deprecated
|
||||
*/
|
||||
{ // Private namespace starts.
|
||||
|
||||
window.antiCensorRu = {
|
||||
window.apis.antiCensorRu = {
|
||||
|
||||
version: chrome.runtime.getManifest().version,
|
||||
|
||||
fixErrorsContext() {
|
||||
/* `setTimeout` changes context of execution from other window
|
||||
(e.g. popup) to background window, so we may catch errors
|
||||
in bg error handlers.
|
||||
More: https://bugs.chromium.org/p/chromium/issues/detail?id=357568
|
||||
*/
|
||||
for(const prop of Object.keys(this)) {
|
||||
if ( typeof(this[prop]) === 'function' ) {
|
||||
const method = this[prop];
|
||||
this[prop] = function(...args) {
|
||||
|
||||
setTimeout(method.bind(this, ...args), 0);
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
throw() {
|
||||
throw new Error('Artificial error');
|
||||
},
|
||||
|
||||
pacProviders: {
|
||||
Антизапрет: {
|
||||
pacUrl: 'https://antizapret.prostovpn.org/proxy.pac',
|
||||
|
@ -293,6 +271,7 @@
|
|||
}
|
||||
)
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -307,8 +286,7 @@
|
|||
E.g. install window may fail to open or be closed by user accidentally.
|
||||
In such case extension _should_ try to work on default parameters.
|
||||
*/
|
||||
const antiCensorRu = window.antiCensorRu;
|
||||
antiCensorRu.fixErrorsContext();
|
||||
const antiCensorRu = window.apis.antiCensorRu;
|
||||
|
||||
chrome.alarms.onAlarm.addListener(
|
||||
(alarm) => {
|
||||
|
@ -393,6 +371,7 @@
|
|||
* Changed storage.ifNotInstalled to storage.ifFirstInstall
|
||||
* Added storage.lastPacUpdateStamp
|
||||
**/
|
||||
|
||||
});
|
||||
|
||||
function asyncLogGroup(...args) {
|
||||
|
@ -406,6 +385,7 @@
|
|||
cb(...cbArgs);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function checkChromeError(betterStack) {
|
||||
|
@ -458,8 +438,7 @@
|
|||
}
|
||||
chrome.proxy.settings.get({}, (details) => {
|
||||
|
||||
const ifThis = details.levelOfControl.startsWith('controlled_by_this');
|
||||
if (!ifThis) {
|
||||
if ( window.utils.areSettingsNotControlledFor( details ) ) {
|
||||
console.warn('Failed, other extension is in control.');
|
||||
return cb({clarification: {message: 'Настройки прокси контролирует другое расширение. <a href="chrome://settings/search#proxy">Какое?</a>'}});
|
||||
}
|
||||
|
@ -500,6 +479,7 @@
|
|||
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function getIpsFor(host, cb) {
|
||||
|
@ -600,6 +580,7 @@
|
|||
}
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function setPacScriptFromProvider(provider, cb) {
|
||||
|
@ -625,6 +606,7 @@
|
|||
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
/* `setTimeout` changes context of execution from other window
|
||||
(e.g. popup) to background window, so we may catch errors
|
||||
in bg error handlers.
|
||||
More: https://bugs.chromium.org/p/chromium/issues/detail?id=357568
|
||||
*/
|
||||
// Fix error context of methods of all APIs.
|
||||
for(const api of Object.keys(window.apis)) {
|
||||
for(const prop of Object.keys(api)) {
|
||||
if ( typeof(api[prop]) !== 'function' ) {
|
||||
continue;
|
||||
}
|
||||
const method = api[prop];
|
||||
api[prop] = function(...args) {
|
||||
|
||||
setTimeout(method.bind(this, ...args), 0);
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -14,6 +14,8 @@
|
|||
Crazy parallel Chrome.
|
||||
**/
|
||||
|
||||
const antiCensorRu = window.apis.antiCensorRu;
|
||||
|
||||
window.chrome.browserAction.setBadgeBackgroundColor({
|
||||
color: '#db4b2f',
|
||||
});
|
||||
|
@ -66,7 +68,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
(title) => {
|
||||
|
||||
const ifTitleSetAlready = /\n/.test(title);
|
||||
const proxyHost = window.antiCensorRu.getPacProvider()
|
||||
const proxyHost = antiCensorRu.getPacProvider()
|
||||
.proxyIps[requestDetails.ip];
|
||||
|
||||
const hostname = new URL( requestDetails.url ).hostname;
|
||||
|
@ -141,7 +143,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
|
|||
function isProxiedAndInformed(requestDetails) {
|
||||
|
||||
if ( !(requestDetails.ip
|
||||
&& window.antiCensorRu.isProxied( requestDetails.ip )) ) {
|
||||
&& antiCensorRu.isProxied( requestDetails.ip )) ) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
|
||||
"name": "Обход блокировок Рунета 0.15",
|
||||
"description": "Аргументы против цензуры: https://git.io/vEkI9",
|
||||
"version": "0.0.0.15",
|
||||
"icons": {
|
||||
"128": "/icons/default-128.png"
|
||||
},
|
||||
"author": "ilyaigpetrov@gmail.com",
|
||||
"homepage_url": "https://github.com/anticensorship-russia/chromium-extension",
|
||||
|
||||
"permissions": [
|
||||
"proxy",
|
||||
"webRequest",
|
||||
"alarms",
|
||||
"storage",
|
||||
"<all_urls>",
|
||||
"tabs",
|
||||
"contextMenus",
|
||||
"notifications"
|
||||
],
|
||||
"background": {
|
||||
"scripts": ["0-error-handlers.js", "1-sync-pac-script-with-pac-provider.js", "2-block-informer.js", "3-context-menus.js"]
|
||||
},
|
||||
"browser_action": {
|
||||
"default_title": "Этот сайт благословлён",
|
||||
"default_popup": "/pages/choose-pac-provider/index.html"
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "/pages/choose-pac-provider/index.html",
|
||||
"chrome_style": true
|
||||
}
|
||||
}
|
||||
{
|
||||
"manifest_version": 2,
|
||||
|
||||
"name": "Обход блокировок Рунета 0.15",
|
||||
"description": "Аргументы против цензуры: https://git.io/vEkI9",
|
||||
"version": "0.0.0.15",
|
||||
"icons": {
|
||||
"128": "/icons/default-128.png"
|
||||
},
|
||||
"author": "ilyaigpetrov@gmail.com",
|
||||
"homepage_url": "https://github.com/anticensorship-russia/chromium-extension",
|
||||
|
||||
"permissions": [
|
||||
"proxy",
|
||||
"webRequest",
|
||||
"alarms",
|
||||
"storage",
|
||||
"<all_urls>",
|
||||
"tabs",
|
||||
"contextMenus",
|
||||
"notifications"
|
||||
],
|
||||
"background": {
|
||||
"scripts": ["00-init-apis.js", "11-api-error-handlers.js", "12-api-sync-pac-script-with-pac-provider.js", "20-api-fixes.js", "30-block-informer.js", "40-context-menus.js"]
|
||||
},
|
||||
"browser_action": {
|
||||
"default_title": "Этот сайт благословлён",
|
||||
"default_popup": "/pages/choose-pac-provider/index.html"
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "/pages/choose-pac-provider/index.html",
|
||||
"chrome_style": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,58 +1,73 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Выбор провайдера PAC</title>
|
||||
<style>
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
li, footer {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
li > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
input[type="radio"], label {
|
||||
cursor: pointer;
|
||||
}
|
||||
.off {
|
||||
display: none;
|
||||
}
|
||||
.link-button, .link-button:visited {
|
||||
color: #0000EE;
|
||||
text-decoration: none;
|
||||
}
|
||||
.link-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.checked-radio-panel {
|
||||
visibility: hidden;
|
||||
}
|
||||
input:checked ~ .checked-radio-panel {
|
||||
visibility: visible;
|
||||
}
|
||||
footer {
|
||||
margin: 2em 1em 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul id="list-of-providers">
|
||||
<li><input type="radio" name="pacProvider" id="none" checked> <label for="none">Отключить</label></li>
|
||||
</ul>
|
||||
<div style="white-space: nowrap">
|
||||
Обновлялись: <span class="update-date">...</span>
|
||||
</div>
|
||||
<div id="status">Загрузка...</div>
|
||||
<footer>
|
||||
<input type="button" value="Готово" class="close-button"> <a href="../debug/index.html" style="text-decoration: none; margin-left: 1em;">Отладка</a>
|
||||
</footer>
|
||||
<script src="./index.js"></script>
|
||||
<script src="./keep-links-clickable.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Выбор провайдера PAC</title>
|
||||
<style>
|
||||
label {
|
||||
user-select: none;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
li, footer {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
li > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
input[type="radio"], label {
|
||||
cursor: pointer;
|
||||
}
|
||||
.off {
|
||||
display: none;
|
||||
}
|
||||
.link-button, .link-button:visited {
|
||||
color: #0000EE;
|
||||
text-decoration: none;
|
||||
}
|
||||
.link-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.checked-radio-panel {
|
||||
visibility: hidden;
|
||||
}
|
||||
input:checked ~ .checked-radio-panel {
|
||||
visibility: visible;
|
||||
}
|
||||
footer {
|
||||
margin: 2em 1em 1em;
|
||||
}
|
||||
hr {
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
#configs-panel > header {
|
||||
margin: 0.6em 0 0.4em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ul id="list-of-providers">
|
||||
<li><input type="radio" name="pacProvider" id="none" checked> <label for="none">Отключить</label></li>
|
||||
</ul>
|
||||
<div style="white-space: nowrap">
|
||||
Обновлялись: <span class="update-date">...</span>
|
||||
</div>
|
||||
<div id="status">Загрузка...</div>
|
||||
<hr/>
|
||||
<div id="configs-panel">
|
||||
<header>Я ❤️ уведомления:</header>
|
||||
<ul id="list-of-handlers">
|
||||
</ul>
|
||||
</div>
|
||||
<footer>
|
||||
<input type="button" value="Готово" class="close-button"> <a href="../debug/index.html" style="text-decoration: none; margin-left: 1em;">Отладка</a>
|
||||
</footer>
|
||||
<script src="./index.js"></script>
|
||||
<script src="./keep-links-clickable.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -16,7 +16,7 @@ chrome.runtime.getBackgroundPage( (backgroundPage) => {
|
|||
|
||||
};
|
||||
|
||||
const antiCensorRu = backgroundPage.antiCensorRu;
|
||||
const antiCensorRu = backgroundPage.apis.antiCensorRu;
|
||||
|
||||
// SET DATE
|
||||
|
||||
|
@ -182,6 +182,25 @@ chrome://extensions</a> ›
|
|||
};
|
||||
}
|
||||
|
||||
const conpanel = document.getElementById('list-of-handlers');
|
||||
backgroundPage.apis.errorHandlers.getEventsMap().forEach( (value, name) => {
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `
|
||||
<input type="checkbox" id="if-on-${name}"/>
|
||||
<label for="if-on-${name}">${value}</label>`;
|
||||
const box = li.querySelector('input');
|
||||
box.checked = backgroundPage.apis.errorHandlers.isOn(name);
|
||||
box.onclick = function() {
|
||||
|
||||
const id = this.id.replace('if-on-', '');
|
||||
backgroundPage.apis.errorHandlers.switch(this.checked ? 'on' : 'off', id);
|
||||
|
||||
};
|
||||
conpanel.appendChild(li);
|
||||
|
||||
});
|
||||
|
||||
setStatusTo('');
|
||||
if (antiCensorRu.ifFirstInstall) {
|
||||
const id = antiCensorRu.currentPacProviderKey || 'none';
|
||||
|
|
Loading…
Reference in New Issue
Block a user