From 690421aead701cb3045c1d8913cb85d432e494c3 Mon Sep 17 00:00:00 2001 From: gorhill Date: Tue, 11 Aug 2015 15:29:14 -0400 Subject: [PATCH] sync feature (#80): draft --- platform/chromium/vapi-background.js | 200 +++++++++++++++++++++++++++ platform/firefox/vapi-background.js | 117 ++++++++++++++++ src/1p-filters.html | 4 + src/3p-filters.html | 28 ++-- src/_locales/en/messages.json | 24 ++++ src/css/3p-filters.css | 2 - src/dyna-rules.html | 4 + src/js/1p-filters.js | 41 ++++-- src/js/3p-filters.js | 78 +++++++++-- src/js/dyna-rules.js | 44 ++++-- src/js/i18n.js | 39 +++--- src/js/messaging.js | 62 +++++++++ src/js/settings.js | 6 + src/js/start.js | 7 + src/js/storage.js | 7 +- src/js/whitelist.js | 35 +++-- src/settings.html | 1 + src/whitelist.html | 4 + 18 files changed, 615 insertions(+), 88 deletions(-) diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 62371248d..f3c7ff336 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -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: '' }, 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 + }; +})(); + +/******************************************************************************/ /******************************************************************************/ })(); diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index a3cc5b050..7c859cef7 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -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 + }; +})(); + +/******************************************************************************/ /******************************************************************************/ })(); diff --git a/src/1p-filters.html b/src/1p-filters.html index 36fe3b83a..83be0706c 100644 --- a/src/1p-filters.html +++ b/src/1p-filters.html @@ -5,11 +5,14 @@ uBlock — Your filters + +
+

@@ -22,6 +25,7 @@ + diff --git a/src/3p-filters.html b/src/3p-filters.html index c20e713b5..5831bef8f 100644 --- a/src/3p-filters.html +++ b/src/3p-filters.html @@ -5,22 +5,27 @@ uBlock — Ubiquitous rules + - - - +
+ +
+ +
    +
  •   + + +
  • + +
    +
  • +
+
    +

    @@ -58,6 +63,7 @@ + diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 5044b413f..8583df762 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -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 (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" diff --git a/src/css/3p-filters.css b/src/css/3p-filters.css index 67502c936..2533cdf2d 100644 --- a/src/css/3p-filters.css +++ b/src/css/3p-filters.css @@ -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 { diff --git a/src/dyna-rules.html b/src/dyna-rules.html index 6e955580a..3ae22cf7a 100644 --- a/src/dyna-rules.html +++ b/src/dyna-rules.html @@ -5,11 +5,14 @@ uBlock — Dynamic filtering rules + +
    +

    @@ -51,6 +54,7 @@ + diff --git a/src/js/1p-filters.js b/src/js/1p-filters.js index c38bd7823..6182e9c44 100644 --- a/src/js/1p-filters.js +++ b/src/js/1p-filters.js @@ -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(); /******************************************************************************/ diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index ebf6ac6e8..ac6abcf59 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -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(); /******************************************************************************/ diff --git a/src/js/dyna-rules.js b/src/js/dyna-rules.js index 04830611d..fa880f93d 100644 --- a/src/js/dyna-rules.js +++ b/src/js/dyna-rules.js @@ -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); /******************************************************************************/ diff --git a/src/js/i18n.js b/src/js/i18n.js index 3622e9665..0c8abd6a4 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -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(/
    /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(/
    /g, '\n').replace(/\n{3,}/g, '\n\n') - ); -}); +vAPI.i18n.render(); /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 1e22bbf92..2c747792a 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -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() { diff --git a/src/js/settings.js b/src/js/settings.js index fdf1c258a..8355197d6 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -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(){ diff --git a/src/js/start.js b/src/js/start.js index 17b3d3ff4..d33ea55e3 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -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(); diff --git a/src/js/storage.js b/src/js/storage.js index 1497811e2..f1a1e20f9 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -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); }; /******************************************************************************/ diff --git a/src/js/whitelist.js b/src/js/whitelist.js index cc4ab3e24..0c7329e14 100644 --- a/src/js/whitelist.js +++ b/src/js/whitelist.js @@ -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(); /******************************************************************************/ diff --git a/src/settings.html b/src/settings.html index bf99f2e6d..e5188135d 100644 --- a/src/settings.html +++ b/src/settings.html @@ -15,6 +15,7 @@
  • +
    • diff --git a/src/whitelist.html b/src/whitelist.html index 5efbc3bf4..8d4555aa2 100644 --- a/src/whitelist.html +++ b/src/whitelist.html @@ -5,11 +5,14 @@ uBlock — Whitelist + +
      +

      @@ -23,6 +26,7 @@ +