mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 01:02:08 +01:00
sync feature (#80): draft
This commit is contained in:
parent
a7712116a7
commit
690421aead
18 changed files with 615 additions and 88 deletions
|
@ -53,12 +53,14 @@ vAPI.app.restart = function() {
|
|||
chrome.runtime.reload();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// chrome.storage.local.get(null, function(bin){ console.debug('%o', bin); });
|
||||
|
||||
vAPI.storage = chrome.storage.local;
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// https://github.com/gorhill/uMatrix/issues/234
|
||||
|
@ -101,6 +103,7 @@ vAPI.browserSettings = {
|
|||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.tabs = {};
|
||||
|
@ -499,6 +502,7 @@ vAPI.tabs.injectScript = function(tabId, details, callback) {
|
|||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
||||
|
@ -534,6 +538,7 @@ vAPI.setIcon = function(tabId, iconStatus, badge) {
|
|||
chrome.browserAction.setIcon({ tabId: tabId, path: iconPaths }, onIconReady);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.messaging = {
|
||||
|
@ -751,6 +756,7 @@ vAPI.messaging.broadcast = function(message) {
|
|||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.net = {};
|
||||
|
@ -875,6 +881,7 @@ vAPI.net.registerListeners = function() {
|
|||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.contextMenu = {
|
||||
|
@ -890,12 +897,14 @@ vAPI.contextMenu = {
|
|||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.lastError = function() {
|
||||
return chrome.runtime.lastError;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// This is called only once, when everything has been loaded in memory after
|
||||
|
@ -947,6 +956,7 @@ vAPI.onLoadAllCompleted = function() {
|
|||
chrome.tabs.query({ url: '<all_urls>' }, bindToTabs);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.punycodeHostname = function(hostname) {
|
||||
|
@ -957,6 +967,196 @@ vAPI.punycodeURL = function(url) {
|
|||
return url;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.cloud = (function() {
|
||||
var chunkCountPerFetch = 16; // Must be a power of 2
|
||||
|
||||
// Mind chrome.storage.sync.MAX_ITEMS (512 at time of writing)
|
||||
var maxChunkCountPerItem = Math.floor(512 * 0.75) & ~(chunkCountPerFetch - 1);
|
||||
|
||||
// Mind chrome.storage.sync.QUOTA_BYTES_PER_ITEM (8192 at time of writing)
|
||||
var maxChunkSize = Math.floor(chrome.storage.sync.QUOTA_BYTES_PER_ITEM * 0.75);
|
||||
|
||||
// Mind chrome.storage.sync.QUOTA_BYTES_PER_ITEM (8192 at time of writing)
|
||||
var maxStorageSize = chrome.storage.sync.QUOTA_BYTES;
|
||||
|
||||
var options = {
|
||||
deviceName: ''
|
||||
};
|
||||
|
||||
var getDeviceName = function() {
|
||||
// Assign a permanent user-friendly id to this uBlock instance if one does
|
||||
// not exist. This will allow to have some sort of identifier for a user
|
||||
// to possibly identify the source of cloud data.
|
||||
var name = window.localStorage.getItem('deviceName') || '';
|
||||
if ( name !== '' ) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return window.navigator.platform;
|
||||
};
|
||||
|
||||
// This is used to find out a rough count of how many chunks exists:
|
||||
// We "poll" at specific index in order to get a rough idea of how
|
||||
// large is the stored string.
|
||||
// This allows reading a single item with only 2 sync operations -- a
|
||||
// good thing given chrome.storage.syncMAX_WRITE_OPERATIONS_PER_MINUTE
|
||||
// and chrome.storage.syncMAX_WRITE_OPERATIONS_PER_HOUR.
|
||||
var getCoarseChunkCount = function(dataKey, callback) {
|
||||
var bin = {};
|
||||
for ( var i = 0; i < maxChunkCountPerItem; i += 16 ) {
|
||||
bin[dataKey + i.toString()] = '';
|
||||
}
|
||||
|
||||
chrome.storage.sync.get(bin, function(bin) {
|
||||
if ( chrome.runtime.lastError ) {
|
||||
callback(0, chrome.runtime.lastError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Could loop backward... let's assume for now
|
||||
// maxChunkCountPerItem could be something else than a
|
||||
// multiple of 16.
|
||||
var chunkCount = 0;
|
||||
for ( var i = 0; i < maxChunkCountPerItem; i += 16 ) {
|
||||
if ( bin[dataKey + i.toString()] === '' ) {
|
||||
break;
|
||||
}
|
||||
chunkCount = i + 16;
|
||||
}
|
||||
|
||||
callback(chunkCount);
|
||||
});
|
||||
};
|
||||
|
||||
var deleteChunks = function(dataKey, start) {
|
||||
var keys = [];
|
||||
|
||||
// No point in deleting more than:
|
||||
// - The max number of chunks per item
|
||||
// - The max number of chunks per storage limit
|
||||
var n = Math.min(
|
||||
maxChunkCountPerItem,
|
||||
Math.ceil(maxStorageSize / maxChunkSize)
|
||||
);
|
||||
for ( var i = start; i < n; i++ ) {
|
||||
keys.push(dataKey + i.toString());
|
||||
}
|
||||
chrome.storage.sync.remove(keys);
|
||||
};
|
||||
|
||||
var start = function(/* dataKeys */) {
|
||||
};
|
||||
|
||||
var push = function(dataKey, data, callback) {
|
||||
var item = JSON.stringify({
|
||||
'source': getDeviceName(),
|
||||
'tstamp': Date.now(),
|
||||
'data': data
|
||||
});
|
||||
|
||||
// Chunkify taking into account QUOTA_BYTES_PER_ITEM:
|
||||
// https://developer.chrome.com/extensions/storage#property-sync
|
||||
// "The maximum size (in bytes) of each individual item in sync
|
||||
// "storage, as measured by the JSON stringification of its value
|
||||
// "plus its key length."
|
||||
var bin = {};
|
||||
var chunkCount = Math.ceil(item.length / maxChunkSize);
|
||||
for ( var i = 0; i < chunkCount; i++ ) {
|
||||
bin[dataKey + i.toString()] = item.substr(i * maxChunkSize, maxChunkSize);
|
||||
}
|
||||
bin[dataKey + i.toString()] = ''; // Sentinel
|
||||
|
||||
chrome.storage.sync.set(bin, function() {
|
||||
var errorStr;
|
||||
if ( chrome.runtime.lastError ) {
|
||||
errorStr = chrome.runtime.lastError.message;
|
||||
}
|
||||
callback(errorStr);
|
||||
|
||||
// Remove potentially unused trailing chunks
|
||||
deleteChunks(dataKey, chunkCount);
|
||||
});
|
||||
};
|
||||
|
||||
var pull = function(dataKey, callback) {
|
||||
var assembleChunks = function(bin) {
|
||||
if ( chrome.runtime.lastError ) {
|
||||
callback(null, chrome.runtime.lastError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assemble chunks into a single string.
|
||||
var json = [], jsonSlice;
|
||||
var i = 0;
|
||||
for (;;) {
|
||||
jsonSlice = bin[dataKey + i.toString()];
|
||||
if ( jsonSlice === '' ) {
|
||||
break;
|
||||
}
|
||||
json.push(jsonSlice);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
var entry = null;
|
||||
try {
|
||||
entry = JSON.parse(json.join(''));
|
||||
} catch(ex) {
|
||||
}
|
||||
callback(entry);
|
||||
};
|
||||
|
||||
var fetchChunks = function(coarseCount, errorStr) {
|
||||
if ( coarseCount === 0 || typeof errorStr === 'string' ) {
|
||||
callback(null, errorStr);
|
||||
return;
|
||||
}
|
||||
|
||||
var bin = {};
|
||||
for ( var i = 0; i < coarseCount; i++ ) {
|
||||
bin[dataKey + i.toString()] = '';
|
||||
}
|
||||
|
||||
chrome.storage.sync.get(bin, assembleChunks);
|
||||
};
|
||||
|
||||
getCoarseChunkCount(dataKey, fetchChunks);
|
||||
};
|
||||
|
||||
var getOptions = function(callback) {
|
||||
if ( typeof callback !== 'function' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(options);
|
||||
};
|
||||
|
||||
var setOptions = function(details, callback) {
|
||||
if ( typeof details !== 'object' || details === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( typeof details.deviceName === 'string' ) {
|
||||
window.localStorage.setItem('deviceName', details.deviceName);
|
||||
}
|
||||
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback(options);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
start: start,
|
||||
push: push,
|
||||
pull: pull,
|
||||
getOptions: getOptions,
|
||||
setOptions: setOptions
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
|
|
@ -2807,6 +2807,7 @@ vAPI.contextMenu.remove = function() {
|
|||
this.onCommand = null;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
var optionsObserver = {
|
||||
|
@ -2849,6 +2850,7 @@ var optionsObserver = {
|
|||
|
||||
optionsObserver.register();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.lastError = function() {
|
||||
|
@ -2875,6 +2877,7 @@ vAPI.onLoadAllCompleted = function() {
|
|||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// Likelihood is that we do not have to punycode: given punycode overhead,
|
||||
|
@ -2894,6 +2897,120 @@ vAPI.punycodeURL = function(url) {
|
|||
return url;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.cloud = (function() {
|
||||
var extensionBranchPath = 'extensions.' + location.host;
|
||||
var cloudBranchPath = extensionBranchPath + '.cloudStorage';
|
||||
|
||||
var options = {
|
||||
deviceName: ''
|
||||
};
|
||||
|
||||
var getDeviceName = function() {
|
||||
var name = '';
|
||||
|
||||
// User-supplied device name.
|
||||
var branch = Services.prefs.getBranch(extensionBranchPath + '.');
|
||||
try {
|
||||
name = branch.getCharPref('deviceName');
|
||||
} catch(ex) {
|
||||
}
|
||||
options.deviceName = name;
|
||||
if ( name !== '' ) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// No name: try to use device name specified by user in Preferences.
|
||||
branch = Services.prefs.getBranch('services.sync.client.');
|
||||
try {
|
||||
name = branch.getCharPref('name');
|
||||
} catch(ex) {
|
||||
}
|
||||
if ( name !== '' ) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// No name: use os/cpu.
|
||||
return window.navigator.platform || window.navigator.oscpu;
|
||||
};
|
||||
|
||||
var start = function(dataKeys) {
|
||||
var extensionBranch = Services.prefs.getBranch(extensionBranchPath + '.');
|
||||
var syncBranch = Services.prefs.getBranch('services.sync.prefs.sync.');
|
||||
|
||||
// Mark config entries as syncable
|
||||
var dataKey;
|
||||
for ( var i = 0; i < dataKeys.length; i++ ) {
|
||||
dataKey = dataKeys[i];
|
||||
if ( extensionBranch.prefHasUserValue('cloudStorage.' + dataKey) === false ) {
|
||||
extensionBranch.setCharPref('cloudStorage.' + dataKey, '');
|
||||
}
|
||||
syncBranch.setBoolPref(cloudBranchPath + '.' + dataKey, true);
|
||||
}
|
||||
};
|
||||
|
||||
var push = function(datakey, data, callback) {
|
||||
var branch = Services.prefs.getBranch(cloudBranchPath + '.');
|
||||
var bin = {
|
||||
'source': getDeviceName(),
|
||||
'tstamp': Date.now(),
|
||||
'data': data
|
||||
};
|
||||
branch.setCharPref(datakey, JSON.stringify(bin));
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
var pull = function(datakey, callback) {
|
||||
var result = null;
|
||||
var branch = Services.prefs.getBranch(cloudBranchPath + '.');
|
||||
try {
|
||||
var json = branch.getCharPref(datakey);
|
||||
if ( typeof json === 'string' ) {
|
||||
result = JSON.parse(json);
|
||||
}
|
||||
} catch(ex) {
|
||||
}
|
||||
callback(result);
|
||||
};
|
||||
|
||||
var getOptions = function(callback) {
|
||||
if ( typeof callback !== 'function' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(options);
|
||||
};
|
||||
|
||||
var setOptions = function(details, callback) {
|
||||
if ( typeof details !== 'object' || details === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var branch = Services.prefs.getBranch(extensionBranchPath + '.');
|
||||
|
||||
if ( typeof details.deviceName === 'string' ) {
|
||||
branch.setCharPref('deviceName', details.deviceName);
|
||||
}
|
||||
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback(options);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
start: start,
|
||||
push: push,
|
||||
pull: pull,
|
||||
getOptions: getOptions,
|
||||
setOptions: setOptions
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
<title>uBlock — Your filters</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/1p-filters.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="cloudWidget" class="hide" data-cloud-entry="myFiltersPane"></div>
|
||||
|
||||
<p data-i18n="1pFormatHint"></p>
|
||||
<p><button id="userFiltersApply" class="important" type="button" disabled="true" data-i18n="1pApplyChanges"></button></p>
|
||||
<textarea class="userFilters" id="userFilters" dir="auto" spellcheck="false"></textarea>
|
||||
|
@ -22,6 +25,7 @@
|
|||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/1p-filters.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -5,22 +5,27 @@
|
|||
<title>uBlock — Ubiquitous rules</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/3p-filters.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<button id="buttonApply" class="important disabled" data-i18n="3pApplyChanges"></button>
|
||||
<ul id="options">
|
||||
<li><input type="checkbox" id="autoUpdate"><label data-i18n="3pAutoUpdatePrompt1" for="autoUpdate"></label> 
|
||||
<button class="important disabled" id="buttonUpdate" data-i18n="3pUpdateNow"></button>
|
||||
<button id="buttonPurgeAll" class="custom disabled" data-i18n="3pPurgeAll"></button>
|
||||
<li><input type="checkbox" id="parseCosmeticFilters"><label data-i18n="3pParseAllABPHideFiltersPrompt1" for="parseCosmeticFilters"></label>
|
||||
<button class="whatisthis"></button>
|
||||
<div class="whatisthis-expandable para" data-i18n="3pParseAllABPHideFiltersInfo"></div>
|
||||
<li><p id="listsOfBlockedHostsPrompt"></p>
|
||||
</ul>
|
||||
<ul id="lists"></ul>
|
||||
<div id="cloudWidget" class="hide" data-cloud-entry="tpFiltersPane"></div>
|
||||
|
||||
<div>
|
||||
<button id="buttonApply" class="important disabled" data-i18n="3pApplyChanges"></button>
|
||||
<ul id="options">
|
||||
<li><input type="checkbox" id="autoUpdate"><label data-i18n="3pAutoUpdatePrompt1" for="autoUpdate"></label> 
|
||||
<button class="important disabled" id="buttonUpdate" data-i18n="3pUpdateNow"></button>
|
||||
<button id="buttonPurgeAll" class="custom disabled" data-i18n="3pPurgeAll"></button>
|
||||
<li><input type="checkbox" id="parseCosmeticFilters"><label data-i18n="3pParseAllABPHideFiltersPrompt1" for="parseCosmeticFilters"></label>
|
||||
<button class="whatisthis"></button>
|
||||
<div class="whatisthis-expandable para" data-i18n="3pParseAllABPHideFiltersInfo"></div>
|
||||
<li><p id="listsOfBlockedHostsPrompt"></p>
|
||||
</ul>
|
||||
<ul id="lists"></ul>
|
||||
</div>
|
||||
|
||||
<div id="externalListsDiv">
|
||||
<p data-i18n="3pExternalListsHint" style="margin: 0 0 0.25em 0; font-size: 13px;"></p>
|
||||
|
@ -58,6 +63,7 @@
|
|||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/3p-filters.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -175,6 +175,10 @@
|
|||
"message":"Color-blind friendly",
|
||||
"description":"English: Color-blind friendly"
|
||||
},
|
||||
"settingsCloudStorageEnabledPrompt":{
|
||||
"message":"Enable cloud storage support",
|
||||
"description": ""
|
||||
},
|
||||
"settingsAdvancedUserPrompt":{
|
||||
"message":"I am an advanced user (<a href='https:\/\/github.com\/gorhill\/uBlock\/wiki\/Advanced-user-features'>Required reading<\/a>)",
|
||||
"description":"English: "
|
||||
|
@ -591,6 +595,26 @@
|
|||
"message": "Permanently",
|
||||
"description": "English: Permanently"
|
||||
},
|
||||
"cloudPush": {
|
||||
"message": "Export to cloud storage",
|
||||
"description": ""
|
||||
},
|
||||
"cloudPull": {
|
||||
"message": "Import from cloud storage",
|
||||
"description": ""
|
||||
},
|
||||
"cloudNoData": {
|
||||
"message": "...\n...",
|
||||
"description": ""
|
||||
},
|
||||
"cloudDeviceNamePrompt": {
|
||||
"message": "This device name:",
|
||||
"description": ""
|
||||
},
|
||||
"genericSubmit": {
|
||||
"message": "Submit",
|
||||
"description": ""
|
||||
},
|
||||
"dummy":{
|
||||
"message":"This entry must be the last one",
|
||||
"description":"so we dont need to deal with comma for last entry"
|
||||
|
|
|
@ -97,10 +97,8 @@ button.custom:hover {
|
|||
}
|
||||
#buttonApply {
|
||||
display: initial;
|
||||
margin: 1em 0;
|
||||
position: fixed;
|
||||
right: 1em;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
body[dir=rtl] #buttonApply {
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
<title>uBlock — Dynamic filtering rules</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dyna-rules.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="cloudWidget" class="hide" data-cloud-entry="myRulesPane"></div>
|
||||
|
||||
<p data-i18n="rulesHint"></p>
|
||||
<p data-i18n="rulesFormatHint"></p>
|
||||
<div id="diff">
|
||||
|
@ -51,6 +54,7 @@
|
|||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/dyna-rules.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -40,10 +40,8 @@ var messager = vAPI.messaging.channel('1p-filters.js');
|
|||
// This is to give a visual hint that the content of user blacklist has changed.
|
||||
|
||||
function userFiltersChanged() {
|
||||
uDom('#userFiltersApply').prop(
|
||||
'disabled',
|
||||
uDom('#userFilters').val().trim() === cachedUserFilters
|
||||
);
|
||||
uDom.nodeFromId('userFiltersApply').disabled =
|
||||
uDom('#userFilters').val().trim() === cachedUserFilters;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -54,7 +52,7 @@ function renderUserFilters() {
|
|||
return;
|
||||
}
|
||||
cachedUserFilters = details.content.trim();
|
||||
uDom('#userFilters').val(details.content);
|
||||
uDom.nodeFromId('userFilters').value = details.content;
|
||||
};
|
||||
messager.send({ what: 'readUserFilters' }, onRead);
|
||||
}
|
||||
|
@ -160,16 +158,31 @@ var userFiltersApplyHandler = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom.onLoad(function() {
|
||||
// Handle user interaction
|
||||
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile);
|
||||
uDom('#userFilters').on('input', userFiltersChanged);
|
||||
uDom('#userFiltersApply').on('click', userFiltersApplyHandler);
|
||||
var getCloudData = function() {
|
||||
return uDom.nodeFromId('userFilters').value;
|
||||
};
|
||||
|
||||
renderUserFilters();
|
||||
});
|
||||
var setCloudData = function(data) {
|
||||
if ( typeof data !== 'string' ) {
|
||||
return;
|
||||
}
|
||||
uDom.nodeFromId('userFilters').value = data;
|
||||
userFiltersChanged();
|
||||
};
|
||||
|
||||
self.cloud.onPush = getCloudData;
|
||||
self.cloud.onPull = setCloudData;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Handle user interaction
|
||||
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile);
|
||||
uDom('#userFilters').on('input', userFiltersChanged);
|
||||
uDom('#userFiltersApply').on('click', userFiltersApplyHandler);
|
||||
|
||||
renderUserFilters();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -495,7 +495,7 @@ var externalListsChangeHandler = function() {
|
|||
/******************************************************************************/
|
||||
|
||||
var externalListsApplyHandler = function() {
|
||||
externalLists = uDom('#externalLists').val();
|
||||
externalLists = uDom.nodeFromId('externalLists').value;
|
||||
messager.send({
|
||||
what: 'userSettings',
|
||||
name: 'externalLists',
|
||||
|
@ -520,21 +520,69 @@ var groupEntryClickHandler = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom.onLoad(function() {
|
||||
uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
|
||||
uDom('#parseCosmeticFilters').on('change', cosmeticSwitchChanged);
|
||||
uDom('#buttonApply').on('click', buttonApplyHandler);
|
||||
uDom('#buttonUpdate').on('click', buttonUpdateHandler);
|
||||
uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler);
|
||||
uDom('#lists').on('change', '.listEntry > input', onListCheckboxChanged);
|
||||
uDom('#lists').on('click', 'span.purge', onPurgeClicked);
|
||||
uDom('#externalLists').on('input', externalListsChangeHandler);
|
||||
uDom('#externalListsApply').on('click', externalListsApplyHandler);
|
||||
uDom('#lists').on('click', '.groupEntry > span', groupEntryClickHandler);
|
||||
var getCloudData = function() {
|
||||
var bin = {
|
||||
parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked,
|
||||
selectedLists: [],
|
||||
externalLists: externalLists
|
||||
};
|
||||
|
||||
renderFilterLists();
|
||||
renderExternalLists();
|
||||
});
|
||||
var lis = uDom('#lists .listEntry'), li;
|
||||
var i = lis.length;
|
||||
while ( i-- ) {
|
||||
li = lis.at(i);
|
||||
if ( li.descendants('input').prop('checked') ) {
|
||||
bin.selectedLists.push(li.descendants('a').attr('data-listkey'));
|
||||
}
|
||||
}
|
||||
|
||||
return bin;
|
||||
};
|
||||
|
||||
var setCloudData = function(data) {
|
||||
if ( typeof data !== 'object' || data === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var checked = data.parseCosmeticFilters === true;
|
||||
uDom.nodeFromId('parseCosmeticFilters').checked = checked;
|
||||
listDetails.cosmetic = checked;
|
||||
|
||||
var lis = uDom('#lists .listEntry'), li, input, listKey;
|
||||
var i = lis.length;
|
||||
while ( i-- ) {
|
||||
li = lis.at(i);
|
||||
input = li.descendants('input');
|
||||
listKey = li.descendants('a').attr('data-listkey');
|
||||
checked = data.selectedLists.indexOf(listKey) !== -1;
|
||||
input.prop('checked', checked);
|
||||
listDetails.available[listKey].off = !checked;
|
||||
}
|
||||
|
||||
uDom.nodeFromId('externalLists').value = data.externalLists || '';
|
||||
|
||||
renderWidgets();
|
||||
externalListsChangeHandler();
|
||||
};
|
||||
|
||||
self.cloud.onPush = getCloudData;
|
||||
self.cloud.onPull = setCloudData;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
|
||||
uDom('#parseCosmeticFilters').on('change', cosmeticSwitchChanged);
|
||||
uDom('#buttonApply').on('click', buttonApplyHandler);
|
||||
uDom('#buttonUpdate').on('click', buttonUpdateHandler);
|
||||
uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler);
|
||||
uDom('#lists').on('change', '.listEntry > input', onListCheckboxChanged);
|
||||
uDom('#lists').on('click', 'span.purge', onPurgeClicked);
|
||||
uDom('#externalLists').on('input', externalListsChangeHandler);
|
||||
uDom('#externalListsApply').on('click', externalListsApplyHandler);
|
||||
uDom('#lists').on('click', '.groupEntry > span', groupEntryClickHandler);
|
||||
|
||||
renderFilterLists();
|
||||
renderExternalLists();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -234,21 +234,39 @@ var editCancelHandler = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom.onLoad(function() {
|
||||
// Handle user interaction
|
||||
uDom('#importButton').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
uDom('#exportButton').on('click', exportUserRulesToFile);
|
||||
var getCloudData = function() {
|
||||
return rulesFromHTML('#diff .left li');
|
||||
};
|
||||
|
||||
uDom('#revertButton').on('click', revertHandler);
|
||||
uDom('#commitButton').on('click', commitHandler);
|
||||
uDom('#editEnterButton').on('click', editStartHandler);
|
||||
uDom('#diff > .pane.right > .rulesContainer').on('dblclick', editStartHandler);
|
||||
uDom('#editStopButton').on('click', editStopHandler);
|
||||
uDom('#editCancelButton').on('click', editCancelHandler);
|
||||
var setCloudData = function(data) {
|
||||
if ( typeof data !== 'string' ) {
|
||||
return;
|
||||
}
|
||||
var request = {
|
||||
'what': 'setSessionRules',
|
||||
'rules': data
|
||||
};
|
||||
messager.send(request, renderRules);
|
||||
};
|
||||
|
||||
messager.send({ what: 'getRules' }, renderRules);
|
||||
});
|
||||
self.cloud.onPush = getCloudData;
|
||||
self.cloud.onPull = setCloudData;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Handle user interaction
|
||||
uDom('#importButton').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
uDom('#exportButton').on('click', exportUserRulesToFile);
|
||||
|
||||
uDom('#revertButton').on('click', revertHandler);
|
||||
uDom('#commitButton').on('click', commitHandler);
|
||||
uDom('#editEnterButton').on('click', editStartHandler);
|
||||
uDom('#diff > .pane.right > .rulesContainer').on('dblclick', editStartHandler);
|
||||
uDom('#editStopButton').on('click', editStopHandler);
|
||||
uDom('#editCancelButton').on('click', editCancelHandler);
|
||||
|
||||
messager.send({ what: 'getRules' }, renderRules);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -33,28 +33,31 @@
|
|||
/******************************************************************************/
|
||||
|
||||
// Helper to deal with the i18n'ing of HTML files.
|
||||
vAPI.i18n.render = function(context) {
|
||||
uDom('[data-i18n]', context).forEach(function(elem) {
|
||||
elem.html(vAPI.i18n(elem.attr('data-i18n')));
|
||||
});
|
||||
|
||||
uDom('[data-i18n]').forEach(function(elem) {
|
||||
elem.html(vAPI.i18n(elem.attr('data-i18n')));
|
||||
});
|
||||
uDom('[title]', context).forEach(function(elem) {
|
||||
var title = vAPI.i18n(elem.attr('title'));
|
||||
if ( title ) {
|
||||
elem.attr('title', title);
|
||||
}
|
||||
});
|
||||
|
||||
uDom('[title]').forEach(function(elem) {
|
||||
var title = vAPI.i18n(elem.attr('title'));
|
||||
if ( title ) {
|
||||
elem.attr('title', title);
|
||||
}
|
||||
});
|
||||
uDom('[placeholder]', context).forEach(function(elem) {
|
||||
elem.attr('placeholder', vAPI.i18n(elem.attr('placeholder')));
|
||||
});
|
||||
|
||||
uDom('[placeholder]').forEach(function(elem) {
|
||||
elem.attr('placeholder', vAPI.i18n(elem.attr('placeholder')));
|
||||
});
|
||||
uDom('[data-i18n-tip]', context).forEach(function(elem) {
|
||||
elem.attr(
|
||||
'data-tip',
|
||||
vAPI.i18n(elem.attr('data-i18n-tip')).replace(/<br>/g, '\n').replace(/\n{3,}/g, '\n\n')
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
uDom('[data-i18n-tip]').forEach(function(elem) {
|
||||
elem.attr(
|
||||
'data-tip',
|
||||
vAPI.i18n(elem.attr('data-i18n-tip')).replace(/<br>/g, '\n').replace(/\n{3,}/g, '\n\n')
|
||||
);
|
||||
});
|
||||
vAPI.i18n.render();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -671,6 +671,68 @@ vAPI.messaging.listen('element-picker.js', onMessage);
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// cloud-ui.js
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var µb = µBlock;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var onMessage = function(request, sender, callback) {
|
||||
// Async
|
||||
switch ( request.what ) {
|
||||
case 'cloudGetOptions':
|
||||
vAPI.cloud.getOptions(function(options) {
|
||||
options.enabled = µb.userSettings.cloudStorageEnabled === true;
|
||||
callback(options);
|
||||
});
|
||||
return;
|
||||
|
||||
case 'cloudSetOptions':
|
||||
vAPI.cloud.setOptions(request.options, callback);
|
||||
return;
|
||||
|
||||
case 'cloudPull':
|
||||
return vAPI.cloud.pull(request.datakey, callback);
|
||||
|
||||
case 'cloudPush':
|
||||
return vAPI.cloud.push(request.datakey, request.data, callback);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Sync
|
||||
var response;
|
||||
|
||||
switch ( request.what ) {
|
||||
// For when cloud storage is disabled.
|
||||
case 'cloudPull':
|
||||
// fallthrough
|
||||
case 'cloudPush':
|
||||
break;
|
||||
|
||||
default:
|
||||
return vAPI.messaging.UNHANDLED;
|
||||
}
|
||||
|
||||
callback(response);
|
||||
};
|
||||
|
||||
vAPI.messaging.listen('cloud-ui.js', onMessage);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// 3p-filters.js
|
||||
|
||||
(function() {
|
||||
|
|
|
@ -186,6 +186,12 @@ var onUserSettingsReceived = function(details) {
|
|||
changeUserSettings('colorBlindFriendly', this.checked);
|
||||
});
|
||||
|
||||
uDom('#cloud-storage-enabled')
|
||||
.prop('checked', details.cloudStorageEnabled === true)
|
||||
.on('change', function(){
|
||||
changeUserSettings('cloudStorageEnabled', this.checked);
|
||||
});
|
||||
|
||||
uDom('#advanced-user-enabled')
|
||||
.prop('checked', details.advancedUserEnabled === true)
|
||||
.on('change', function(){
|
||||
|
|
|
@ -66,6 +66,13 @@ var onAllReady = function() {
|
|||
// for launch time.
|
||||
µb.assets.remoteFetchBarrier -= 1;
|
||||
|
||||
vAPI.cloud.start([
|
||||
'tpFiltersPane',
|
||||
'myFiltersPane',
|
||||
'myRulesPane',
|
||||
'whitelistPane'
|
||||
]);
|
||||
|
||||
//quickProfiler.stop(0);
|
||||
|
||||
vAPI.onLoadAllCompleted();
|
||||
|
|
|
@ -96,10 +96,7 @@
|
|||
/******************************************************************************/
|
||||
|
||||
µBlock.saveWhitelist = function() {
|
||||
var bin = {
|
||||
'netWhitelist': this.stringFromWhitelist(this.netWhitelist)
|
||||
};
|
||||
vAPI.storage.set(bin);
|
||||
this.keyvalSetOne('netWhitelist', this.stringFromWhitelist(this.netWhitelist));
|
||||
this.netWhitelistModifyTime = Date.now();
|
||||
};
|
||||
|
||||
|
@ -156,7 +153,7 @@
|
|||
/******************************************************************************/
|
||||
|
||||
µBlock.saveUserFilters = function(content, callback) {
|
||||
return this.assets.put(this.userFiltersPath, content, callback);
|
||||
this.assets.put(this.userFiltersPath, content, callback);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -41,7 +41,7 @@ var reUnwantedChars = /[\x00-\x09\x0b\x0c\x0e-\x1f!"$'()<>{}|\\^\[\]`~]/;
|
|||
/******************************************************************************/
|
||||
|
||||
var whitelistChanged = function() {
|
||||
var s = uDom('#whitelist').val().trim();
|
||||
var s = uDom.nodeFromId('whitelist').value.trim();
|
||||
var bad = reUnwantedChars.test(s);
|
||||
uDom('#whitelistApply').prop(
|
||||
'disabled',
|
||||
|
@ -55,7 +55,7 @@ var whitelistChanged = function() {
|
|||
var renderWhitelist = function() {
|
||||
var onRead = function(whitelist) {
|
||||
cachedWhitelist = whitelist;
|
||||
uDom('#whitelist').val(cachedWhitelist);
|
||||
uDom.nodeFromId('whitelist').value = whitelist;
|
||||
};
|
||||
messager.send({ what: 'getWhitelist' }, onRead);
|
||||
whitelistChanged();
|
||||
|
@ -122,15 +122,30 @@ var whitelistApplyHandler = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom.onLoad(function() {
|
||||
uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
|
||||
uDom('#whitelist').on('input', whitelistChanged);
|
||||
uDom('#whitelistApply').on('click', whitelistApplyHandler);
|
||||
var getCloudData = function() {
|
||||
return uDom.nodeFromId('whitelist').value;
|
||||
};
|
||||
|
||||
renderWhitelist();
|
||||
});
|
||||
var setCloudData = function(data) {
|
||||
if ( typeof data !== 'string' ) {
|
||||
return;
|
||||
}
|
||||
uDom.nodeFromId('whitelist').value = data;
|
||||
whitelistChanged();
|
||||
};
|
||||
|
||||
self.cloud.onPush = getCloudData;
|
||||
self.cloud.onPull = setCloudData;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
|
||||
uDom('#whitelist').on('input', whitelistChanged);
|
||||
uDom('#whitelistApply').on('click', whitelistApplyHandler);
|
||||
|
||||
renderWhitelist();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<li><input id="icon-badge" type="checkbox"><label data-i18n="settingsIconBadgePrompt" for="icon-badge"></label>
|
||||
<li><input id="context-menu-enabled" type="checkbox"><label data-i18n="settingsContextMenuPrompt" for="context-menu-enabled"></label>
|
||||
<li><input id="color-blind-friendly" type="checkbox"><label data-i18n="settingsColorBlindPrompt" for="color-blind-friendly"></label>
|
||||
<li><input id="cloud-storage-enabled" type="checkbox"><label data-i18n="settingsCloudStorageEnabledPrompt" for="cloud-storage-enabled"></label>
|
||||
<li><input id="advanced-user-enabled" type="checkbox"><label data-i18n="settingsAdvancedUserPrompt" for="advanced-user-enabled"></label>
|
||||
<li class="subgroup"><span data-i18n="3pGroupPrivacy"></span><ul>
|
||||
<li><input id="prefetching-disabled" type="checkbox"><label data-i18n="settingsPrefetchingDisabledPrompt" for="prefetching-disabled"></label> <a class="fa info" href="https://wikipedia.org/wiki/Link_prefetching#Issues_and_criticisms" target="_blank"></a>
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
<title>uBlock — Whitelist</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/whitelist.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="cloudWidget" class="hide" data-cloud-entry="whitelistPane"></div>
|
||||
|
||||
<p data-i18n="whitelistPrompt"></p>
|
||||
<p><button id="whitelistApply" class="important" type="button" disabled="true" data-i18n="whitelistApply"></button></p>
|
||||
<textarea id="whitelist" dir="auto" spellcheck="false"></textarea>
|
||||
|
@ -23,6 +26,7 @@
|
|||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/whitelist.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
Loading…
Reference in a new issue