From 7e3b3e507240510b5f2407b811ed207ae11c6673 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 13:25:55 +0200 Subject: [PATCH 01/17] remove obsolete FF workaround --- src/js/gui/demo.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/js/gui/demo.js b/src/js/gui/demo.js index ce6b346..5fc16dc 100644 --- a/src/js/gui/demo.js +++ b/src/js/gui/demo.js @@ -140,18 +140,6 @@ function startDemo() { tooltipClass: 'tooltip-step4', } ]; - // demo not working on Desktop Firefox 41 - if(~navigator.userAgent.indexOf('Firefox/41') && !~navigator.userAgent.indexOf('Android')) - steps = [{ - element: ".placeholder-step1", - intro: - '

Location Guard was successfully installed.

To see it in action, try this online demo.

' + - '

Note: the current page used to be a demo of Location Guard, but it was disabled in Firefox 41 due to a browser bug. ' + - 'It will be available again in Firefox 42.

Location Guard, however, is still enabled on all pages!

', - position: "floating", - tooltipClass: 'tooltip-step1', - }]; - intro = introJs(); intro .setOptions({ From 3b37461f404e61fdfae6ef6cabe62e6573087737 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 18:25:08 +0200 Subject: [PATCH 02/17] make Browser.storage.get/set async --- src/js/common/browser.js | 34 +++++++++++++++++++--------------- src/js/common/browser_base.js | 12 ++++++------ src/js/common/util.js | 2 +- src/js/content/content.js | 4 ++-- src/js/gui/options.js | 24 ++++++++++++------------ src/js/gui/popup.js | 14 +++++++------- 6 files changed, 47 insertions(+), 43 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index deb5af3..2505f85 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -124,24 +124,28 @@ Browser.rpc.call = function(tabId, name, args, cb) { // Browser.storage._key = "global"; // store everything under this key -Browser.storage.get = function(cb) { - browser.storage.local.get(Browser.storage._key, function(items) { - var st = items[Browser.storage._key]; - - // default values - if(!st) { - st = Browser.storage._default; - Browser.storage.set(st); - } - cb(st); +Browser.storage.get = async function() { + return new Promise(resolve => { + browser.storage.local.get(Browser.storage._key, function(items) { + var st = items[Browser.storage._key]; + + // default values + if(!st) { + st = Browser.storage._default; + Browser.storage.set(st); + } + resolve(st); + }); }); }; -Browser.storage.set = function(st, handler) { - Browser.log('saving st', st); - var items = {}; - items[Browser.storage._key] = st; - browser.storage.local.set(items, handler); +Browser.storage.set = function(st) { + return new Promise(resolve => { + Browser.log('saving st', st); + var items = {}; + items[Browser.storage._key] = st; + browser.storage.local.set(items, resolve); + }); }; Browser.storage.clear = function(handler) { diff --git a/src/js/common/browser_base.js b/src/js/common/browser_base.js index 4a03a71..43479d4 100644 --- a/src/js/common/browser_base.js +++ b/src/js/common/browser_base.js @@ -62,18 +62,18 @@ const Browser = { // else that needs to be stored. It is fetched and stored as a whole. // storage: { - // browser.storage.get(handler) + // browser.storage.get() // - // fetches the storage object and passes it to the handler. + // fetches the storage object. // The default object is returned if the storage is empty. // - get: function(handler) {}, + get: async function() {}, - // browser.storage.set(st, handler) + // browser.storage.set(st) // - // Stores the give storage object. Calls the handler when finished. + // Stores the give storage object. // - set: function(st, handler) {}, + set: async function(st) {}, // browser.storage.clear(handler) // diff --git a/src/js/common/util.js b/src/js/common/util.js index 2bba44b..04ca6a0 100644 --- a/src/js/common/util.js +++ b/src/js/common/util.js @@ -57,7 +57,7 @@ var Util = { state = state || { callUrl: '', apiCalls: 0 }; const Browser = require('./browser'); - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { var domain = Util.extractDomain(state.callUrl); var level = st.domainLevel[domain] || st.defaultLevel; diff --git a/src/js/content/content.js b/src/js/content/content.js index 38d4de8..d761525 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -100,7 +100,7 @@ rpc.register('getNoisyPosition', function(options, replyHandler) { // Either returns fixed pos directly, or calls the real one, then calls addNoise. // function getNoisyPosition(options, replyHandler) { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { // if level == 'fixed' and fixedPosNoAPI == true, then we return the // fixed position without calling the geolocation API at all. // @@ -146,7 +146,7 @@ function getNoisyPosition(options, replyHandler) { // gets position, returs noisy version based on the privacy options // function addNoise(position, handler) { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { var domain = Util.extractDomain(callUrl); var level = st.domainLevel[domain] || st.defaultLevel; diff --git a/src/js/gui/options.js b/src/js/gui/options.js index c01a88b..0664e8e 100644 --- a/src/js/gui/options.js +++ b/src/js/gui/options.js @@ -26,7 +26,7 @@ var currentPos = { }; Browser.init('options'); -Browser.storage.get(function(st) { +Browser.storage.get().then(st => { epsilon = st.epsilon; }); @@ -59,7 +59,7 @@ function Slider(opt) { } function saveOptions() { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { st.defaultLevel = $('#defaultLevel').val(); st.paused = $("#paused").prop('checked'); st.hideIcon = $("#hideIcon").prop('checked'); @@ -78,14 +78,14 @@ function saveOptions() { st.updateAccuracy = updateAccuracy; } - Browser.storage.set(st, function() { + Browser.storage.set(st).then(() => { Browser.gui.refreshAllIcons(); }); }); } function saveFixedPosNoAPI() { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { st.fixedPosNoAPI = $("#fixedPosNoAPI").prop('checked'); Browser.storage.set(st); @@ -93,7 +93,7 @@ function saveFixedPosNoAPI() { } function saveLevel() { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { var radius = sliderRadius.value; var ct = sliderCacheTime.value; var cacheTime = ct <= 59 ? ct : 60 * (ct-59); @@ -199,7 +199,7 @@ function initLevelMap() { } function initFixedPosMap() { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { var latlng = [st.fixedPos.latitude, st.fixedPos.longitude]; fixedPosMap = new L.map('fixedPosMap') @@ -274,7 +274,7 @@ function initFixedPosMap() { } function saveFixedPos(latlng) { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { var wrapped = latlng.wrap(); // force within normal range st.fixedPos = { latitude: wrapped.lat, longitude: wrapped.lng }; @@ -286,7 +286,7 @@ function saveFixedPos(latlng) { } function showLevelInfo() { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { // set sliders' value var radius = st.levels[activeLevel].radius; var cacheTime = st.levels[activeLevel].cacheTime; @@ -398,7 +398,7 @@ function initPages() { // page initialization // if(page == "options") { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { $('#defaultLevel').val(st.defaultLevel).selectmenu("refresh"); $('#paused').prop('checked', st.paused).checkboxradio("refresh"); $('#hideIcon').prop('checked', st.hideIcon).checkboxradio("refresh"); @@ -430,7 +430,7 @@ function initPages() { showLevelInfo(); } else if (page == "fixedPos") { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { $('#fixedPosNoAPI').prop('checked', st.fixedPosNoAPI).checkboxradio("refresh"); initFixedPosMap(); @@ -463,9 +463,9 @@ function restoreDefaults() { } function deleteCache() { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { st.cachedPos = {}; - Browser.storage.set(st, function() { + Browser.storage.set(st).then(() => { window.alert('Location cache was deleted'); }); }); diff --git a/src/js/gui/popup.js b/src/js/gui/popup.js index 0679231..25a3b00 100644 --- a/src/js/gui/popup.js +++ b/src/js/gui/popup.js @@ -34,18 +34,18 @@ function doAction() { break; case 'hideIcon': - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { st.hideIcon = true; - Browser.storage.set(st, function() { + Browser.storage.set(st).then(() => { Browser.gui.refreshAllIcons(Browser.gui.closePopup); }); }); break; case 'pause': - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { st.paused = !st.paused; - Browser.storage.set(st, function() { + Browser.storage.set(st).then(() => { Browser.gui.refreshAllIcons(Browser.gui.closePopup); }); }); @@ -58,7 +58,7 @@ function doAction() { default: // set level if(!url) throw "no url"; // just to be sure - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { var domain = Util.extractDomain(url); var level = action; if(level == st.defaultLevel) @@ -66,7 +66,7 @@ function doAction() { else st.domainLevel[domain] = level; - Browser.storage.set(st, function() { + Browser.storage.set(st).then(() => { Browser.gui.refreshAllIcons(Browser.gui.closePopup); }); }); @@ -80,7 +80,7 @@ function drawUI() { // we need storage and url Browser.gui.getCallUrl(tabId, function(callUrl) { - Browser.storage.get(function(st) { + Browser.storage.get().then(st => { Browser.log("popup: callUrl", callUrl, "settings", st); // we don't have a url if we are in chrome (browser action, visible in From 3190311fed3a7874f3f7964f7f75ea5ce864ff62 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 19:20:45 +0200 Subject: [PATCH 03/17] use await in all Browser.storage.get/set calls --- src/js/common/browser.js | 2 +- src/js/common/util.js | 39 +++-- src/js/content/content.js | 155 ++++++++++--------- src/js/content/injected.js | 7 +- src/js/gui/options.js | 297 ++++++++++++++++++------------------- src/js/gui/popup.js | 49 +++--- 6 files changed, 265 insertions(+), 284 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index 2505f85..3868410 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -139,7 +139,7 @@ Browser.storage.get = async function() { }); }; -Browser.storage.set = function(st) { +Browser.storage.set = async function(st) { return new Promise(resolve => { Browser.log('saving st', st); var items = {}; diff --git a/src/js/common/util.js b/src/js/common/util.js index 04ca6a0..5727d1b 100644 --- a/src/js/common/util.js +++ b/src/js/common/util.js @@ -43,38 +43,37 @@ var Util = { // getIconInfo: function(about, handler) { if(typeof(about) == 'object') // null or state object - Util._getStateIconInfo(about, handler); + Util._getStateIconInfo(about).then(handler); else { // tabId const Browser = require('./browser'); Browser.rpc.call(about, 'getState', [], function(state) { - Util._getStateIconInfo(state, handler); + Util._getStateIconInfo(state).then(handler); }); } }, - _getStateIconInfo: function(state, handler) { + _getStateIconInfo: async function(state) { // return info for the default icon if state is null state = state || { callUrl: '', apiCalls: 0 }; const Browser = require('./browser'); - Browser.storage.get().then(st => { - var domain = Util.extractDomain(state.callUrl); - var level = st.domainLevel[domain] || st.defaultLevel; + const st = await Browser.storage.get(); + var domain = Util.extractDomain(state.callUrl); + var level = st.domainLevel[domain] || st.defaultLevel; - var info = { - hidden: st.hideIcon, - private: !st.paused && level != 'real', - defaultPrivate: !st.paused && st.defaultLevel != 'real', - apiCalls: state.apiCalls, - title: - "Location Guard\n" + - (st.paused ? "Paused" : - level == 'real' ? "Using your real location" : - level == 'fixed'? "Using a fixed location" : - "Privacy level: " + level) - }; - handler(info); - }); + var info = { + hidden: st.hideIcon, + private: !st.paused && level != 'real', + defaultPrivate: !st.paused && st.defaultLevel != 'real', + apiCalls: state.apiCalls, + title: + "Location Guard\n" + + (st.paused ? "Paused" : + level == 'real' ? "Using your real location" : + level == 'fixed'? "Using a fixed location" : + "Privacy level: " + level) + }; + return info; }, events: { diff --git a/src/js/content/content.js b/src/js/content/content.js index d761525..e363c1e 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -82,7 +82,7 @@ rpc.register('getNoisyPosition', function(options, replyHandler) { // call back to this tab to be answered by the top window Browser.rpc.call(null, 'apiCalledInFrame', [myUrl], function(topUrl) { callUrl = Browser.capabilities.iframeGeoFromOwnDomain() ? myUrl : topUrl; - getNoisyPosition(options, replyHandler); + getNoisyPosition(options).then(replyHandler); }); } else { @@ -90,7 +90,7 @@ rpc.register('getNoisyPosition', function(options, replyHandler) { apiCalls++; callUrl = myUrl; // last call happened here Browser.gui.refreshIcon('self'); - getNoisyPosition(options, replyHandler); + getNoisyPosition(options).then(replyHandler); } return true; // will reply later @@ -99,44 +99,44 @@ rpc.register('getNoisyPosition', function(options, replyHandler) { // gets the options passed to the fake navigator.geolocation.getCurrentPosition. // Either returns fixed pos directly, or calls the real one, then calls addNoise. // -function getNoisyPosition(options, replyHandler) { - Browser.storage.get().then(st => { - // if level == 'fixed' and fixedPosNoAPI == true, then we return the - // fixed position without calling the geolocation API at all. - // - var domain = Util.extractDomain(callUrl); - var level = st.domainLevel[domain] || st.defaultLevel; - - if(!st.paused && level == 'fixed' && st.fixedPosNoAPI) { - var noisy = { - coords: { - latitude: st.fixedPos.latitude, - longitude: st.fixedPos.longitude, - accuracy: 10, - altitude: null, - altitudeAccuracy: null, - heading: null, - speed: null - }, - timestamp: new Date().getTime() - }; - replyHandler(true, noisy); - Browser.log("returning fixed", noisy); - return; - } +async function getNoisyPosition(options) { + const st = await Browser.storage.get(); + + // if level == 'fixed' and fixedPosNoAPI == true, then we return the + // fixed position without calling the geolocation API at all. + // + var domain = Util.extractDomain(callUrl); + var level = st.domainLevel[domain] || st.defaultLevel; + + if(!st.paused && level == 'fixed' && st.fixedPosNoAPI) { + var noisy = { + coords: { + latitude: st.fixedPos.latitude, + longitude: st.fixedPos.longitude, + accuracy: 10, + altitude: null, + altitudeAccuracy: null, + heading: null, + speed: null + }, + timestamp: new Date().getTime() + }; + Browser.log("returning fixed", noisy); + return { success: true, position: noisy }; + } + return new Promise(resolve => { // we call getCurrentPosition here in the content script, instead of // inside the page, because the content-script/page communication is not secure // getCurrentPosition.apply(navigator.geolocation, [ - function(position) { + async function(position) { // clone, modifying/sending the native object returns error - addNoise(Util.clone(position), function(noisy) { - replyHandler(true, noisy); - }); + const noisy = await addNoise(Util.clone(position)); + resolve({ success: true, position: noisy }); }, function(error) { - replyHandler(false, Util.clone(error)); // clone, sending the native object returns error + resolve({ success: false, position: Util.clone(error) }); // clone, sending the native object returns error }, options ]); @@ -145,60 +145,59 @@ function getNoisyPosition(options, replyHandler) { // gets position, returs noisy version based on the privacy options // -function addNoise(position, handler) { - Browser.storage.get().then(st => { - var domain = Util.extractDomain(callUrl); - var level = st.domainLevel[domain] || st.defaultLevel; +async function addNoise(position) { + const st = await Browser.storage.get(); + var domain = Util.extractDomain(callUrl); + var level = st.domainLevel[domain] || st.defaultLevel; + + if(st.paused || level == 'real') { + // do nothing, use real location + + } else if(level == 'fixed') { + position.coords = { + latitude: st.fixedPos.latitude, + longitude: st.fixedPos.longitude, + accuracy: 10, + altitude: null, + altitudeAccuracy: null, + heading: null, + speed: null + }; + + } else if(st.cachedPos[level] && ((new Date).getTime() - st.cachedPos[level].epoch)/60000 < st.levels[level].cacheTime) { + position = st.cachedPos[level].position; + Browser.log('using cached', position); - if(st.paused || level == 'real') { - // do nothing, use real location - - } else if(level == 'fixed') { - position.coords = { - latitude: st.fixedPos.latitude, - longitude: st.fixedPos.longitude, - accuracy: 10, - altitude: null, - altitudeAccuracy: null, - heading: null, - speed: null - }; - - } else if(st.cachedPos[level] && ((new Date).getTime() - st.cachedPos[level].epoch)/60000 < st.levels[level].cacheTime) { - position = st.cachedPos[level].position; - Browser.log('using cached', position); - - } else { - // add noise - var epsilon = st.epsilon / st.levels[level].radius; + } else { + // add noise + var epsilon = st.epsilon / st.levels[level].radius; - const PlanarLaplace = require('../common/laplace'); - var pl = new PlanarLaplace(); - var noisy = pl.addNoise(epsilon, position.coords); + const PlanarLaplace = require('../common/laplace'); + var pl = new PlanarLaplace(); + var noisy = pl.addNoise(epsilon, position.coords); - position.coords.latitude = noisy.latitude; - position.coords.longitude = noisy.longitude; + position.coords.latitude = noisy.latitude; + position.coords.longitude = noisy.longitude; - // update accuracy - if(position.coords.accuracy && st.updateAccuracy) - position.coords.accuracy += Math.round(pl.alphaDeltaAccuracy(epsilon, .9)); + // update accuracy + if(position.coords.accuracy && st.updateAccuracy) + position.coords.accuracy += Math.round(pl.alphaDeltaAccuracy(epsilon, .9)); - // don't know how to add noise to those, so we set to null (they're most likely null anyway) - position.altitude = null; - position.altitudeAccuracy = null; - position.heading = null; - position.speed = null; + // don't know how to add noise to those, so we set to null (they're most likely null anyway) + position.altitude = null; + position.altitudeAccuracy = null; + position.heading = null; + position.speed = null; - // cache - st.cachedPos[level] = { epoch: (new Date).getTime(), position: position }; - Browser.storage.set(st); + // cache + st.cachedPos[level] = { epoch: (new Date).getTime(), position: position }; + await Browser.storage.set(st); - Browser.log('noisy coords', position.coords); - } + Browser.log('noisy coords', position.coords); + } - // return noisy position - handler(position); - }); + // return noisy position + return position; } Browser.init('content'); diff --git a/src/js/content/injected.js b/src/js/content/injected.js index fc53332..1fb4154 100644 --- a/src/js/content/injected.js +++ b/src/js/content/injected.js @@ -16,10 +16,11 @@ module.exports = function(PostRPC) { prpc = new PostRPC('page-content', window, window, window.origin); // This PostRPC is created by the injected code! // call getNoisyPosition on the content-script - prpc.call('getNoisyPosition', [options], function(success, res) { + prpc.call('getNoisyPosition', [options], function(res) { // call cb1 on success, cb2 on failure - var f = success ? cb1 : cb2; - if(f) f(res); + var f = res.success ? cb1 : cb2; + if(f) + f(res.position); }); }; diff --git a/src/js/gui/options.js b/src/js/gui/options.js index 0664e8e..e86dac2 100644 --- a/src/js/gui/options.js +++ b/src/js/gui/options.js @@ -26,9 +26,9 @@ var currentPos = { }; Browser.init('options'); -Browser.storage.get().then(st => { - epsilon = st.epsilon; -}); +(async function() { + epsilon = (await Browser.storage.get()).epsilon; +}()); // slider wrapper class, cause sGlide interface sucks @@ -58,59 +58,55 @@ function Slider(opt) { } } -function saveOptions() { - Browser.storage.get().then(st => { - st.defaultLevel = $('#defaultLevel').val(); - st.paused = $("#paused").prop('checked'); - st.hideIcon = $("#hideIcon").prop('checked'); - - var updateAccuracy = $("#updateAccuracy").prop('checked'); - if(st.updateAccuracy != updateAccuracy) { - // update accuracy of cached positions to reflect the change - for(var level in st.cachedPos) { - var epsilon = st.epsilon / st.levels[level].radius; - var pl = new PlanarLaplace(); - - st.cachedPos[level].position.coords.accuracy += // add/remove the .9 accuracy - (updateAccuracy ? 1 : -1) * Math.round(pl.alphaDeltaAccuracy(epsilon, .9)); - } - - st.updateAccuracy = updateAccuracy; +async function saveOptions() { + const st = await Browser.storage.get(); + st.defaultLevel = $('#defaultLevel').val(); + st.paused = $("#paused").prop('checked'); + st.hideIcon = $("#hideIcon").prop('checked'); + + var updateAccuracy = $("#updateAccuracy").prop('checked'); + if(st.updateAccuracy != updateAccuracy) { + // update accuracy of cached positions to reflect the change + for(var level in st.cachedPos) { + var epsilon = st.epsilon / st.levels[level].radius; + var pl = new PlanarLaplace(); + + st.cachedPos[level].position.coords.accuracy += // add/remove the .9 accuracy + (updateAccuracy ? 1 : -1) * Math.round(pl.alphaDeltaAccuracy(epsilon, .9)); } - Browser.storage.set(st).then(() => { - Browser.gui.refreshAllIcons(); - }); - }); + st.updateAccuracy = updateAccuracy; + } + + await Browser.storage.set(st); + Browser.gui.refreshAllIcons(); } -function saveFixedPosNoAPI() { - Browser.storage.get().then(st => { - st.fixedPosNoAPI = $("#fixedPosNoAPI").prop('checked'); +async function saveFixedPosNoAPI() { + const st = await Browser.storage.get(); + st.fixedPosNoAPI = $("#fixedPosNoAPI").prop('checked'); - Browser.storage.set(st); - }); + await Browser.storage.set(st); } -function saveLevel() { - Browser.storage.get().then(st => { - var radius = sliderRadius.value; - var ct = sliderCacheTime.value; - var cacheTime = ct <= 59 ? ct : 60 * (ct-59); +async function saveLevel() { + const st = await Browser.storage.get(); + var radius = sliderRadius.value; + var ct = sliderCacheTime.value; + var cacheTime = ct <= 59 ? ct : 60 * (ct-59); - updateRadius(radius, true); + updateRadius(radius, true); - // delete cache for that level if radius changes - if(st.levels[activeLevel].radius != radius) - delete st.cachedPos[activeLevel]; + // delete cache for that level if radius changes + if(st.levels[activeLevel].radius != radius) + delete st.cachedPos[activeLevel]; - st.levels[activeLevel] = { - radius: radius, - cacheTime: cacheTime - }; + st.levels[activeLevel] = { + radius: radius, + cacheTime: cacheTime + }; - Browser.storage.set(st); - }); + await Browser.storage.set(st); } function initLevelMap() { @@ -198,108 +194,105 @@ function initLevelMap() { .addTo(levelMap); } -function initFixedPosMap() { - Browser.storage.get().then(st => { - var latlng = [st.fixedPos.latitude, st.fixedPos.longitude]; - - fixedPosMap = new L.map('fixedPosMap') - .addLayer(new L.TileLayer( - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - { attribution: 'Map data © OpenStreetMap contributors' } - )) - .setView(latlng, 14) - .on('dragstart', function() { +async function initFixedPosMap() { + const st = await Browser.storage.get(); + var latlng = [st.fixedPos.latitude, st.fixedPos.longitude]; + + fixedPosMap = new L.map('fixedPosMap') + .addLayer(new L.TileLayer( + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + { attribution: 'Map data © OpenStreetMap contributors' } + )) + .setView(latlng, 14) + .on('dragstart', function() { + fixedPosMap.closePopup(); + }) + .on('click', function(e) { + if(fixedPosMap.popup._isOpen) { // if popup is open, close it fixedPosMap.closePopup(); - }) - .on('click', function(e) { - if(fixedPosMap.popup._isOpen) { // if popup is open, close it - fixedPosMap.closePopup(); - return; - } - saveFixedPos(e.latlng); - }); + return; + } + saveFixedPos(e.latlng); + }); - // marker - fixedPosMap.marker = new L.marker(latlng, { draggable: true }) - .addTo(fixedPosMap) - .on('click', function() { - showPopup(fixedPosMap); - }) - .on('dragend', function(e) { - saveFixedPos(e.target._latlng); - }); + // marker + fixedPosMap.marker = new L.marker(latlng, { draggable: true }) + .addTo(fixedPosMap) + .on('click', function() { + showPopup(fixedPosMap); + }) + .on('dragend', function(e) { + saveFixedPos(e.target._latlng); + }); - // popup - var popupHtml = - '
' + - '

This is the location reported when the privacy level is set to "Use fixed location".

' + - '

Click on the map or drag the marker to set a new fixed location.

' + - '
'; - - fixedPosMap.popup = L.popup({ - autoPan: false, - closeOnClick: false, // we'll close it ourselves - maxWidth: Math.min($("#fixedPosMap").width() - 50, 300), - }) - .setContent(popupHtml); - - showPopup(fixedPosMap); - - // locate control - L.control.locate({ - drawCircle: false, - follow: false, - icon: 'icon-trans ui-btn-icon-notext ui-icon-location', // use jqm's icons to avoid loading - iconLoading: 'icon-trans ui-btn-icon-notext ui-icon-location', // font awesome - }).addTo(fixedPosMap); + // popup + var popupHtml = + '
' + + '

This is the location reported when the privacy level is set to "Use fixed location".

' + + '

Click on the map or drag the marker to set a new fixed location.

' + + '
'; - // geocoder control - if(!Browser.capabilities.isAndroid()) // not enough space on smartphones, better have a cleaner interface - L.control.geocoder(geocoderKey, { - url: geocoderUrl, - markers: false, - autocomplete: false - }).on('results', function(e) { - // directly set position if the text is a latlon - var res = e.params.text.match(/^([-+]?[0-9]+\.[0-9]+)\s*,?\s*([-+]?[0-9]+\.[0-9]+)$/); - if(!res) return; - - var latlng = L.latLng(parseFloat(res[1]), parseFloat(res[2])); - saveFixedPos(latlng); - fixedPosMap.setView(latlng, 14) - this.collapse(); // close the geocoder search - - }).addTo(fixedPosMap); - }); + fixedPosMap.popup = L.popup({ + autoPan: false, + closeOnClick: false, // we'll close it ourselves + maxWidth: Math.min($("#fixedPosMap").width() - 50, 300), + }) + .setContent(popupHtml); + + showPopup(fixedPosMap); + + // locate control + L.control.locate({ + drawCircle: false, + follow: false, + icon: 'icon-trans ui-btn-icon-notext ui-icon-location', // use jqm's icons to avoid loading + iconLoading: 'icon-trans ui-btn-icon-notext ui-icon-location', // font awesome + }).addTo(fixedPosMap); + + // geocoder control + if(!Browser.capabilities.isAndroid()) // not enough space on smartphones, better have a cleaner interface + L.control.geocoder(geocoderKey, { + url: geocoderUrl, + markers: false, + autocomplete: false + }).on('results', function(e) { + // directly set position if the text is a latlon + var res = e.params.text.match(/^([-+]?[0-9]+\.[0-9]+)\s*,?\s*([-+]?[0-9]+\.[0-9]+)$/); + if(!res) return; + + var latlng = L.latLng(parseFloat(res[1]), parseFloat(res[2])); + saveFixedPos(latlng); + fixedPosMap.setView(latlng, 14) + this.collapse(); // close the geocoder search + + }).addTo(fixedPosMap); } -function saveFixedPos(latlng) { - Browser.storage.get().then(st => { - var wrapped = latlng.wrap(); // force within normal range - st.fixedPos = { latitude: wrapped.lat, longitude: wrapped.lng }; +async function saveFixedPos(latlng) { + const st = await Browser.storage.get(); + var wrapped = latlng.wrap(); // force within normal range + st.fixedPos = { latitude: wrapped.lat, longitude: wrapped.lng }; - fixedPosMap.marker.setLatLng(latlng); + fixedPosMap.marker.setLatLng(latlng); - Browser.log('saving st', st); - Browser.storage.set(st); - }); + Browser.log('saving st', st); + await Browser.storage.set(st); } -function showLevelInfo() { - Browser.storage.get().then(st => { - // set sliders' value - var radius = st.levels[activeLevel].radius; - var cacheTime = st.levels[activeLevel].cacheTime; - var ct = cacheTime <= 59 // 0-59 are mins, 60 and higher are hours - ? cacheTime - : 59 + Math.floor(cacheTime/59); +async function showLevelInfo() { + const st = await Browser.storage.get(); + // set sliders' value + var radius = st.levels[activeLevel].radius; + var cacheTime = st.levels[activeLevel].cacheTime; + var ct = cacheTime <= 59 // 0-59 are mins, 60 and higher are hours + ? cacheTime + : 59 + Math.floor(cacheTime/59); - sliderRadius.set(radius); - sliderCacheTime.set(ct); + sliderRadius.set(radius); + sliderCacheTime.set(ct); - updateRadius(radius, true); - updateCache(ct); - }); + updateRadius(radius, true); + updateCache(ct); } // change current pos as a reaction to a Leaflet event @@ -384,7 +377,7 @@ function initPages() { $('#hideIcon').parent().toggle(!Browser.capabilities.permanentIcon()); // hiding the icon only works with page action (not browser action) }); - $(document).on("pagecontainershow", function(e, ui) { + $(document).on("pagecontainershow", async function(e, ui) { var page = ui.toPage[0].id; if(inited[page]) { @@ -398,12 +391,11 @@ function initPages() { // page initialization // if(page == "options") { - Browser.storage.get().then(st => { - $('#defaultLevel').val(st.defaultLevel).selectmenu("refresh"); - $('#paused').prop('checked', st.paused).checkboxradio("refresh"); - $('#hideIcon').prop('checked', st.hideIcon).checkboxradio("refresh"); - $('#updateAccuracy').prop('checked', st.updateAccuracy).checkboxradio("refresh"); - }); + const st = await Browser.storage.get(); + $('#defaultLevel').val(st.defaultLevel).selectmenu("refresh"); + $('#paused').prop('checked', st.paused).checkboxradio("refresh"); + $('#hideIcon').prop('checked', st.hideIcon).checkboxradio("refresh"); + $('#updateAccuracy').prop('checked', st.updateAccuracy).checkboxradio("refresh"); } else if (page == "levels") { sliderRadius = new Slider({ @@ -430,11 +422,10 @@ function initPages() { showLevelInfo(); } else if (page == "fixedPos") { - Browser.storage.get().then(st => { - $('#fixedPosNoAPI').prop('checked', st.fixedPosNoAPI).checkboxradio("refresh"); + const st = await Browser.storage.get(); + $('#fixedPosNoAPI').prop('checked', st.fixedPosNoAPI).checkboxradio("refresh"); - initFixedPosMap(); - }); + initFixedPosMap(); } }); } @@ -462,13 +453,11 @@ function restoreDefaults() { } } -function deleteCache() { - Browser.storage.get().then(st => { - st.cachedPos = {}; - Browser.storage.set(st).then(() => { - window.alert('Location cache was deleted'); - }); - }); +async function deleteCache() { + const st = await Browser.storage.get(); + st.cachedPos = {}; + await Browser.storage.set(st); + window.alert('Location cache was deleted'); } // set page events before "ready" @@ -484,7 +473,7 @@ $(document).ready(function() { $("#left-panel").panel("open"); }); - $("#options input, #options select, #fixedPos input").change(saveOptions); + $("#options input, #options select").change(saveOptions); $("#fixedPosNoAPI").change(saveFixedPosNoAPI); $("#restoreDefaults").click(restoreDefaults); diff --git a/src/js/gui/popup.js b/src/js/gui/popup.js index 25a3b00..a877d5e 100644 --- a/src/js/gui/popup.js +++ b/src/js/gui/popup.js @@ -16,7 +16,7 @@ $(document).ready(drawUI); var url; -function doAction() { +async function doAction() { var action = $(this).attr("id"); switch(action) { @@ -34,21 +34,17 @@ function doAction() { break; case 'hideIcon': - Browser.storage.get().then(st => { - st.hideIcon = true; - Browser.storage.set(st).then(() => { - Browser.gui.refreshAllIcons(Browser.gui.closePopup); - }); - }); + var st = await Browser.storage.get(); + st.hideIcon = true; + await Browser.storage.set(st); + Browser.gui.refreshAllIcons(Browser.gui.closePopup); break; case 'pause': - Browser.storage.get().then(st => { - st.paused = !st.paused; - Browser.storage.set(st).then(() => { - Browser.gui.refreshAllIcons(Browser.gui.closePopup); - }); - }); + var st = await Browser.storage.get(); + st.paused = !st.paused; + await Browser.storage.set(st); + Browser.gui.refreshAllIcons(Browser.gui.closePopup); break; case 'setLevel': @@ -58,18 +54,16 @@ function doAction() { default: // set level if(!url) throw "no url"; // just to be sure - Browser.storage.get().then(st => { - var domain = Util.extractDomain(url); - var level = action; - if(level == st.defaultLevel) - delete st.domainLevel[domain]; - else - st.domainLevel[domain] = level; - - Browser.storage.set(st).then(() => { - Browser.gui.refreshAllIcons(Browser.gui.closePopup); - }); - }); + var st = await Browser.storage.get(); + var domain = Util.extractDomain(url); + var level = action; + if(level == st.defaultLevel) + delete st.domainLevel[domain]; + else + st.domainLevel[domain] = level; + + await Browser.storage.set(st); + Browser.gui.refreshAllIcons(Browser.gui.closePopup); break; } } @@ -79,8 +73,8 @@ function drawUI() { var tabId = res ? parseInt(res[1]) : null; // we need storage and url - Browser.gui.getCallUrl(tabId, function(callUrl) { - Browser.storage.get().then(st => { + Browser.gui.getCallUrl(tabId, async function(callUrl) { + const st = await Browser.storage.get(); Browser.log("popup: callUrl", callUrl, "settings", st); // we don't have a url if we are in chrome (browser action, visible in @@ -138,7 +132,6 @@ function drawUI() { }); } }); - }); } From d22479ad0e16c1c208f2c5e447411c78e224c2bb Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 19:24:26 +0200 Subject: [PATCH 04/17] make Browser.storage.clear async --- src/js/common/browser.js | 6 ++++-- src/js/common/browser_base.js | 6 +++--- src/js/gui/options.js | 9 ++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index 3868410..582e7b8 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -148,8 +148,10 @@ Browser.storage.set = async function(st) { }); }; -Browser.storage.clear = function(handler) { - browser.storage.local.clear(handler); +Browser.storage.clear = async function() { + return new Promise(resolve => { + browser.storage.local.clear(resolve); + }); }; diff --git a/src/js/common/browser_base.js b/src/js/common/browser_base.js index 43479d4..b351507 100644 --- a/src/js/common/browser_base.js +++ b/src/js/common/browser_base.js @@ -75,11 +75,11 @@ const Browser = { // set: async function(st) {}, - // browser.storage.clear(handler) + // browser.storage.clear() // - // Clears the storage. Calls the handler when finished. + // Clears the storage. // - clear: function(handler) {}, + clear: async function() {}, // default storage object // diff --git a/src/js/gui/options.js b/src/js/gui/options.js index e86dac2..c5b7608 100644 --- a/src/js/gui/options.js +++ b/src/js/gui/options.js @@ -443,12 +443,11 @@ function showCurrentPosition() { ); } -function restoreDefaults() { +async function restoreDefaults() { if(window.confirm('Are you sure you want to restore the default options?')) { - Browser.storage.clear(function() { - Browser.gui.refreshAllIcons(function() { - location.reload(); - }); + await Browser.storage.clear(); + Browser.gui.refreshAllIcons(function() { + location.reload(); }); } } From c3bdb38271417e45e0b1939f0885f7cc8e518916 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 19:43:55 +0200 Subject: [PATCH 05/17] make Browser.rpc.call async --- src/js/common/browser.js | 25 ++++++++++++------------- src/js/common/browser_base.js | 7 +++---- src/js/common/util.js | 13 ++++++------- src/js/content/content.js | 4 ++-- src/js/gui/options.js | 2 +- src/js/gui/popup.js | 2 +- src/js/main.js | 6 ++---- 7 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index 582e7b8..60610e1 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -103,14 +103,14 @@ Browser.rpc._listener = function(message, sender, replyHandler) { return handler.apply(null, args); }; -Browser.rpc.call = function(tabId, name, args, cb) { - var message = { method: name, args: args }; - if(!cb) cb = function() {}; // we get error of not cb is passed - - if(tabId) - browser.tabs.sendMessage(tabId, message, cb); - else - browser.runtime.sendMessage(null, message, cb); +Browser.rpc.call = async function(tabId, name, args) { + return new Promise(resolve => { + var message = { method: name, args: args }; + if(tabId) + browser.tabs.sendMessage(tabId, message, resolve); + else + browser.runtime.sendMessage(null, message, resolve); + }); } @@ -166,7 +166,7 @@ Browser.gui.refreshIcon = function(tabId, cb) { if(Browser._script == 'content' || (Browser._script != 'main' && Browser.capabilities.needsPAManualHide()) ) { - Browser.rpc.call(null, 'refreshIcon', [tabId], cb); + Browser.rpc.call(null, 'refreshIcon', [tabId]).then(cb); return; } @@ -260,12 +260,11 @@ Browser.gui.showPage = function(name) { }; Browser.gui.getCallUrl = function(tabId, handler) { - function fetch(tabId) { + async function fetch(tabId) { // we call getState from the content script // - Browser.rpc.call(tabId, 'getState', [], function(state) { - handler(state && state.callUrl); // state might be null if no content script runs in the tab - }); + const state = await Browser.rpc.call(tabId, 'getState', []); + handler(state && state.callUrl); // state might be null if no content script runs in the tab } if(tabId) diff --git a/src/js/common/browser_base.js b/src/js/common/browser_base.js index b351507..2fdeb8e 100644 --- a/src/js/common/browser_base.js +++ b/src/js/common/browser_base.js @@ -42,17 +42,16 @@ const Browser = { // register: function(name, handler) {}, - // Browser.rpc.call(tabId, name, args, handler) + // Browser.rpc.call(tabId, name, args) // // Calls a remote method. // tabId: tab id of the script to call, or null to call the main script // name: method name // args: array of arguments to pass - // handler: function(res), will be called when the result is received // - // If the call cannot be made to the specific tabId, handler will be called with no arguments. + // If the call cannot be made to the specific tabId, null is returned. // - call: function(tabId, name, args, handler) {} + call: async function(tabId, name, args) {} }, // Browser.storage diff --git a/src/js/common/util.js b/src/js/common/util.js index 5727d1b..a02e0bc 100644 --- a/src/js/common/util.js +++ b/src/js/common/util.js @@ -41,15 +41,14 @@ var Util = { // title: icon's title } // // - getIconInfo: function(about, handler) { - if(typeof(about) == 'object') // null or state object - Util._getStateIconInfo(about).then(handler); - else { // tabId + getIconInfo: async function(about, handler) { + if(typeof(about) != 'object') { // tabId const Browser = require('./browser'); - Browser.rpc.call(about, 'getState', [], function(state) { - Util._getStateIconInfo(state).then(handler); - }); + about = await Browser.rpc.call(about, 'getState', []); } + + const info = await Util._getStateIconInfo(about); + handler(info); }, _getStateIconInfo: async function(state) { diff --git a/src/js/content/content.js b/src/js/content/content.js index e363c1e..625339f 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -80,7 +80,7 @@ rpc.register('getNoisyPosition', function(options, replyHandler) { // (which might be either the iframe url, or the top window url, depending on how the browser handles permissions). // To avoid cross-origin issues, we call apiCalledInFrame in the main script, which echoes the // call back to this tab to be answered by the top window - Browser.rpc.call(null, 'apiCalledInFrame', [myUrl], function(topUrl) { + Browser.rpc.call(null, 'apiCalledInFrame', [myUrl]).then(topUrl => { callUrl = Browser.capabilities.iframeGeoFromOwnDomain() ? myUrl : topUrl; getNoisyPosition(options).then(replyHandler); }); @@ -242,7 +242,7 @@ if(Browser.testing) { }); Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', [], function(res) { + Browser.rpc.call(null, 'nestedTestMain', []).then(res => { Browser.log('got from nestedTestMain', res); }); } diff --git a/src/js/gui/options.js b/src/js/gui/options.js index c5b7608..4da1a68 100644 --- a/src/js/gui/options.js +++ b/src/js/gui/options.js @@ -505,7 +505,7 @@ if(Browser.testing) { }); Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', [], function(res) { + Browser.rpc.call(null, 'nestedTestMain', []).then(res => { Browser.log('got from nestedTestMain', res); }); } diff --git a/src/js/gui/popup.js b/src/js/gui/popup.js index a877d5e..661999e 100644 --- a/src/js/gui/popup.js +++ b/src/js/gui/popup.js @@ -144,7 +144,7 @@ if(Browser.testing) { }); Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', [], function(res) { + Browser.rpc.call(null, 'nestedTestMain', []).then(res => { Browser.log('got from nestedTestMain', res); }); } diff --git a/src/js/main.js b/src/js/main.js index 1448d61..a3ee3dc 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -19,9 +19,7 @@ Browser.init('main'); // of the top-window. We just echo the call back to the tab. // Browser.rpc.register('apiCalledInFrame', function(url, tabId, replyHandler) { - Browser.rpc.call(tabId, 'apiCalledInFrame', [url], function(res) { - replyHandler(res); - }); + Browser.rpc.call(tabId, 'apiCalledInFrame', [url]).then(replyHandler); return true; // reply later }); @@ -31,7 +29,7 @@ if(Browser.testing) { Browser.rpc.register('nestedTestMain', function(tabId, replyHandler) { Browser.log("in nestedTestMain, call from ", tabId, "calling back nestedTestTab"); - Browser.rpc.call(tabId, 'nestedTestTab', [], function(res) { + Browser.rpc.call(tabId, 'nestedTestTab', []).then(res => { Browser.log("got from nestedTestTab", res, "adding '_foo' and sending back"); replyHandler(res + '_foo'); }); From 1586718253f29a21d0e66f74b4e4e0e63ebcdc7b Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 20:23:04 +0200 Subject: [PATCH 06/17] Browser.rpc.register: use async handlers --- src/js/common/browser.js | 24 +++++++++++++++++------- src/js/common/browser_base.js | 8 ++------ src/js/content/content.js | 14 +++++++------- src/js/gui/faq.js | 4 ++-- src/js/gui/options.js | 4 ++-- src/js/gui/popup.js | 4 ++-- src/js/main.js | 20 ++++++-------------- 7 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index 60610e1..dee8ae5 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -35,10 +35,11 @@ Browser._main_script = function() { // some operations cannot be done by other scripts, so we set // handlers to do them in the main script // - Browser.rpc.register('refreshIcon', function(tabId, callerTabId, replyHandler) { - // 'self' tabId in the content script means refresh its own tab - Browser.gui.refreshIcon(tabId == 'self' ? callerTabId : tabId, replyHandler); - return true; // will reply later + Browser.rpc.register('refreshIcon', async function(tabId, callerTabId) { + return new Promise(resolve => { + // 'self' tabId in the content script means refresh its own tab + Browser.gui.refreshIcon(tabId == 'self' ? callerTabId : tabId, resolve); + }); }); Browser.rpc.register('closeTab', function(tabId) { @@ -98,9 +99,18 @@ Browser.rpc._listener = function(message, sender, replyHandler) { // add tabId and replyHandler to the arguments var args = message.args || []; var tabId = sender.tab ? sender.tab.id : null; - args.push(tabId, replyHandler); - - return handler.apply(null, args); + args.push(tabId); + + const res = handler.apply(null, args); + if(res instanceof Promise) { + // handler returned a Promise. We return true here, to indicate that the response will come later. + res.then(replyHandler); + return true; + } else { + // We have a response right now + replyHandler(res); + return false; + } }; Browser.rpc.call = async function(tabId, name, args) { diff --git a/src/js/common/browser_base.js b/src/js/common/browser_base.js index 2fdeb8e..3f94b5b 100644 --- a/src/js/common/browser_base.js +++ b/src/js/common/browser_base.js @@ -30,15 +30,11 @@ const Browser = { // // Registers a method to be callable from other scripts. // handler should be a function - // function(...args..., tabId, replyHandler) + // async function(...args..., tabId) // // The function receives any arguments passed during the call (see Browser.rpc.call) - // Moreover, two extra arguments are automatically added: + // Moreover, one extra arguments are automatically added: // tabId: the tabId of the caller, or null if the call is made from the main script - // replyHandler: function for asynchronously returning a result by calling replyHandler(result) - // - // IMPORTANT: If handler does not immediately return a result but stores replyHandler to do it asynchronously later, - // it should return a true value to keep replyHandler open. // register: function(name, handler) {}, diff --git a/src/js/content/content.js b/src/js/content/content.js index 625339f..da9e689 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -216,29 +216,29 @@ if(Browser.capabilities.permanentIcon() && !inFrame) { // only the top frame handles getState and apiCalledInFrame requests if(!inFrame) { - Browser.rpc.register('getState', function(tabId, replyHandler) { - replyHandler({ + Browser.rpc.register('getState', function(tabId) { + return { callUrl: callUrl, apiCalls: apiCalls - }); + }; }); - Browser.rpc.register('apiCalledInFrame', function(iframeUrl, tabId, replyHandler) { + Browser.rpc.register('apiCalledInFrame', function(iframeUrl, tabId) { apiCalls++; if(Browser.capabilities.iframeGeoFromOwnDomain()) callUrl = iframeUrl; Browser.gui.refreshIcon('self'); - replyHandler(myUrl); + return myUrl; }); } if(Browser.testing) { // test for nested calls, and for correct passing of tabId // - Browser.rpc.register('nestedTestTab', function(tabId, replyHandler) { + Browser.rpc.register('nestedTestTab', function(tabId) { Browser.log("in nestedTestTab, returning 'content'"); - replyHandler("content"); + return "content"; }); Browser.log("calling nestedTestMain"); diff --git a/src/js/gui/faq.js b/src/js/gui/faq.js index 4957d64..7e1b9b2 100644 --- a/src/js/gui/faq.js +++ b/src/js/gui/faq.js @@ -4,8 +4,8 @@ Browser.log("faq loading"); // send an empty reply to getState (see options.js) Browser.init('options'); -Browser.rpc.register('getState', function(tabId, replyHandler) { - replyHandler(); +Browser.rpc.register('getState', function(tabId) { + return null; }); const $ = require('jquery'); diff --git a/src/js/gui/options.js b/src/js/gui/options.js index 4da1a68..ae3c19c 100644 --- a/src/js/gui/options.js +++ b/src/js/gui/options.js @@ -499,9 +499,9 @@ $(document).ready(function() { if(Browser.testing) { // test for nested calls, and for correct passing of tabId // - Browser.rpc.register('nestedTestTab', function(tabId, replyHandler) { + Browser.rpc.register('nestedTestTab', function(tabId) { Browser.log("in nestedTestTab, returning 'options'"); - replyHandler("options"); + return "options"; }); Browser.log("calling nestedTestMain"); diff --git a/src/js/gui/popup.js b/src/js/gui/popup.js index 661999e..add4904 100644 --- a/src/js/gui/popup.js +++ b/src/js/gui/popup.js @@ -138,9 +138,9 @@ function drawUI() { if(Browser.testing) { // test for nested calls, and for correct passing of tabId // - Browser.rpc.register('nestedTestTab', function(tabId, replyHandler) { + Browser.rpc.register('nestedTestTab', function(tabId) { Browser.log("in nestedTestTab, returning 'popup'"); - replyHandler("popup"); + return "popup"; }); Browser.log("calling nestedTestMain"); diff --git a/src/js/main.js b/src/js/main.js index a3ee3dc..6b6aaf0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -18,26 +18,18 @@ Browser.init('main'); // this is used from the content-script of an iframe, to communicate with the content-script // of the top-window. We just echo the call back to the tab. // -Browser.rpc.register('apiCalledInFrame', function(url, tabId, replyHandler) { - Browser.rpc.call(tabId, 'apiCalledInFrame', [url]).then(replyHandler); - return true; // reply later +Browser.rpc.register('apiCalledInFrame', async function(url, tabId) { + return await Browser.rpc.call(tabId, 'apiCalledInFrame', [url]); }); if(Browser.testing) { // test for nested calls, and for correct passing of tabId // - Browser.rpc.register('nestedTestMain', function(tabId, replyHandler) { + Browser.rpc.register('nestedTestMain', async function(tabId) { Browser.log("in nestedTestMain, call from ", tabId, "calling back nestedTestTab"); - Browser.rpc.call(tabId, 'nestedTestTab', []).then(res => { - Browser.log("got from nestedTestTab", res, "adding '_foo' and sending back"); - replyHandler(res + '_foo'); - }); - - // we MUST return true to signal that replyHandler will be used at a later - // time (when we get the reply of nestedTestTab). Returning false will - // fail in FF and some versions of Chrome. We mention this in the - // specification of Browser.rpc.register - return true; + const res = Browser.rpc.call(tabId, 'nestedTestTab', []); + Browser.log("got from nestedTestTab", res, "adding '_foo' and sending back"); + return res + '_foo'; }); } From 8fb03e3bb25b3f03e95ead1f73760ea45d5aab32 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 20:41:27 +0200 Subject: [PATCH 07/17] make PostRPC async --- src/js/common/post-rpc.js | 36 +++++++++++------------------------- src/js/content/content.js | 13 +++++-------- src/js/content/injected.js | 13 ++++++------- 3 files changed, 22 insertions(+), 40 deletions(-) diff --git a/src/js/common/post-rpc.js b/src/js/common/post-rpc.js index d0f6573..d95890d 100644 --- a/src/js/common/post-rpc.js +++ b/src/js/common/post-rpc.js @@ -24,15 +24,14 @@ function _code() { // include all code here to inject easily PostRPC.prototype.register = function(name, fun) { this._methods[name] = fun; }; - PostRPC.prototype.call = function(method, args, handler) { - var callId; - if(handler) { - callId = Math.floor(Math.random()*1000000); - this._calls[callId] = handler; - } - if(!args) args = []; + PostRPC.prototype.call = function(method, args) { + return new Promise(resolve => { + var callId = Math.floor(Math.random()*1000000); + this._calls[callId] = resolve; + if(!args) args = []; - this._sendMessage({ method: method, args: args, callId: callId, from: this._id }); + this._sendMessage({ method: method, args: args, callId: callId, from: this._id }); + }); }; // private methods for sending/receiving messages @@ -43,7 +42,7 @@ function _code() { // include all code here to inject easily this._sendObj.postMessage(temp, this._targetOrigin); } - PostRPC.prototype._receiveMessage = function(event) { + PostRPC.prototype._receiveMessage = async function(event) { var data = event.data && event.data[this._ns]; // everything is inside ns, to minimize conflicts with other message if(!data) return; @@ -56,22 +55,9 @@ function _code() { // include all code here to inject easily return; } - // pass returnHandler, used to send back the result - var replyHandler; - if(data.callId) { - var _this = this; - replyHandler = function() { - var args = Array.prototype.slice.call(arguments); // arguments in real array - _this._sendMessage({ callId: data.callId, value: args }); - }; - } else { - replyHandler = function() {}; // no result expected, use dummy handler - } - - var dataArgs = Array.prototype.slice.call(data.args); // cannot modify data.args in Firefox 32, clone as workaround - dataArgs.push(replyHandler); - - this._methods[data.method].apply(null, dataArgs); + // call the handler, send back the result + const res = await this._methods[data.method].apply(null, data.args); + this._sendMessage({ callId: data.callId, value: [res] }); } else { // return value diff --git a/src/js/content/content.js b/src/js/content/content.js index da9e689..62d204c 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -74,26 +74,23 @@ var callUrl = myUrl; // the url from which the last call is _shown_ to be ma // methods called by the page // var rpc = new PostRPC('page-content', window, window, window.origin); -rpc.register('getNoisyPosition', function(options, replyHandler) { +rpc.register('getNoisyPosition', async function(options) { if(inFrame) { // we're in a frame, we need to notify the top window, and get back the *url used in the permission dialog* // (which might be either the iframe url, or the top window url, depending on how the browser handles permissions). // To avoid cross-origin issues, we call apiCalledInFrame in the main script, which echoes the // call back to this tab to be answered by the top window - Browser.rpc.call(null, 'apiCalledInFrame', [myUrl]).then(topUrl => { - callUrl = Browser.capabilities.iframeGeoFromOwnDomain() ? myUrl : topUrl; - getNoisyPosition(options).then(replyHandler); - }); + const topUrl = await Browser.rpc.call(null, 'apiCalledInFrame', [myUrl]); + callUrl = Browser.capabilities.iframeGeoFromOwnDomain() ? myUrl : topUrl; + return await getNoisyPosition(options); } else { // refresh icon before fetching the location apiCalls++; callUrl = myUrl; // last call happened here Browser.gui.refreshIcon('self'); - getNoisyPosition(options).then(replyHandler); + return await getNoisyPosition(options); } - - return true; // will reply later }); // gets the options passed to the fake navigator.geolocation.getCurrentPosition. diff --git a/src/js/content/injected.js b/src/js/content/injected.js index 1fb4154..454cbc5 100644 --- a/src/js/content/injected.js +++ b/src/js/content/injected.js @@ -9,19 +9,18 @@ module.exports = function(PostRPC) { // the real methods will be called by the content script (not by the page) // so we dont need to keep them at all. - navigator.geolocation.getCurrentPosition = function(cb1, cb2, options) { + navigator.geolocation.getCurrentPosition = async function(cb1, cb2, options) { // create a PostRPC object only when getCurrentPosition is called. This // avoids having our own postMessage handler on every page if(!prpc) prpc = new PostRPC('page-content', window, window, window.origin); // This PostRPC is created by the injected code! // call getNoisyPosition on the content-script - prpc.call('getNoisyPosition', [options], function(res) { - // call cb1 on success, cb2 on failure - var f = res.success ? cb1 : cb2; - if(f) - f(res.position); - }); + // call cb1 on success, cb2 on failure + const res = await prpc.call('getNoisyPosition', [options]); + var f = res.success ? cb1 : cb2; + if(f) + f(res.position); }; navigator.geolocation.watchPosition = function(cb1, cb2, options) { From 3d36df6306500e0def658c30f3059d42bb7802fc Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 20:52:53 +0200 Subject: [PATCH 08/17] make Browser.gui.getCallUrl async --- src/js/common/browser.js | 31 ++++----- src/js/common/browser_base.js | 6 +- src/js/gui/popup.js | 119 +++++++++++++++++----------------- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index dee8ae5..dd11b30 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -269,23 +269,24 @@ Browser.gui.showPage = function(name) { browser.tabs.create({ url: browser.extension.getURL(name) }); }; -Browser.gui.getCallUrl = function(tabId, handler) { - async function fetch(tabId) { - // we call getState from the content script - // - const state = await Browser.rpc.call(tabId, 'getState', []); - handler(state && state.callUrl); // state might be null if no content script runs in the tab +Browser.gui.getCallUrl = async function(tabId) { + async function getActiveTabId() { + return new Promise(resolve => { + browser.tabs.query({ + active: true, // Select active tabs + lastFocusedWindow: true // In the current window + }, async function(tabs) { + resolve(tabs[0].id); + }); + }) } - if(tabId) - fetch(tabId); - else - browser.tabs.query({ - active: true, // Select active tabs - lastFocusedWindow: true // In the current window - }, function(tabs) { - fetch(tabs[0].id) - }); + if(!tabId) + tabId = await getActiveTabId(); + + // we call getState from the content script + const state = await Browser.rpc.call(tabId, 'getState', []); + return state && state.callUrl; // state might be null if no content script runs in the tab }; Browser.gui.closePopup = function() { diff --git a/src/js/common/browser_base.js b/src/js/common/browser_base.js index 3f94b5b..71ee43f 100644 --- a/src/js/common/browser_base.js +++ b/src/js/common/browser_base.js @@ -146,11 +146,11 @@ const Browser = { // showPage: function(name) {}, - // Browser.gui.getCallUrl(tabId, handler) + // Browser.gui.getCallUrl(tabId) // - // Gets the callUrl of given tab and passes it to 'handler' + // Gets the callUrl of given tab. // - getActiveCallUrl: function(tabId, handler) {}, + getCallUrl: async function(tabId) {}, // Browser.gui.closePopup() // diff --git a/src/js/gui/popup.js b/src/js/gui/popup.js index add4904..19fb4d3 100644 --- a/src/js/gui/popup.js +++ b/src/js/gui/popup.js @@ -68,70 +68,69 @@ async function doAction() { } } -function drawUI() { +async function drawUI() { var res = window.location.href.match(/tabId=(\d+)/); var tabId = res ? parseInt(res[1]) : null; // we need storage and url - Browser.gui.getCallUrl(tabId, async function(callUrl) { - const st = await Browser.storage.get(); - Browser.log("popup: callUrl", callUrl, "settings", st); - - // we don't have a url if we are in chrome (browser action, visible in - // all tabs), and the active tab has no content-script running (eg. new - // tab page) - // - if(callUrl) { - url = callUrl; - var domain = Util.extractDomain(url); - var level = st.domainLevel[domain] || st.defaultLevel; - - $("#title").text( - st.paused ? "Location Guard is paused" : - level == 'real' ? "Using your real location" : - level == 'fixed'? "Using a fixed location" : - "Privacy level: " + level - ); - $("#setLevel b").text(domain); - $("#"+level).attr("checked", true); - - } else { - $("#title").parent().hide(); - } - - $("#pause").text((st.paused ? "Resume" : "Pause") + " Location Guard"); - $("#pause").parent().attr("data-icon", st.paused ? "play" : "pause"); - - $("#setLevel").toggle(callUrl && !st.paused); - $("#hideIcon").toggle(callUrl && !st.paused && !Browser.capabilities.permanentIcon()); // hiding the icon only works with page action (not browser action) - - $("a, input").on("click", doAction); - - // we're ready, init - $.mobile.initializePage(); - - if(Browser.capabilities.popupAsTab()) { - // the popup is displayed as a normal tab - // set 100% width/height - $("html, body, #container").css({ - width: "100%", - height: "100%" - }); - // show the close button - $("#close").css({ display: "block" }) - .on("click", Browser.gui.closePopup); - - } else { - // normal popup, resize body to match #container - var width = $("#container").width(); - var height = $("#container").height(); - - $("html, body").css({ - width: width, - height: height, - }); - } - }); + const callUrl = await Browser.gui.getCallUrl(tabId); + const st = await Browser.storage.get(); + Browser.log("popup: callUrl", callUrl, "settings", st); + + // we don't have a url if we are in chrome (browser action, visible in + // all tabs), and the active tab has no content-script running (eg. new + // tab page) + // + if(callUrl) { + url = callUrl; + var domain = Util.extractDomain(url); + var level = st.domainLevel[domain] || st.defaultLevel; + + $("#title").text( + st.paused ? "Location Guard is paused" : + level == 'real' ? "Using your real location" : + level == 'fixed'? "Using a fixed location" : + "Privacy level: " + level + ); + $("#setLevel b").text(domain); + $("#"+level).attr("checked", true); + + } else { + $("#title").parent().hide(); + } + + $("#pause").text((st.paused ? "Resume" : "Pause") + " Location Guard"); + $("#pause").parent().attr("data-icon", st.paused ? "play" : "pause"); + + $("#setLevel").toggle(callUrl && !st.paused); + $("#hideIcon").toggle(callUrl && !st.paused && !Browser.capabilities.permanentIcon()); // hiding the icon only works with page action (not browser action) + + $("a, input").on("click", doAction); + + // we're ready, init + $.mobile.initializePage(); + + if(Browser.capabilities.popupAsTab()) { + // the popup is displayed as a normal tab + // set 100% width/height + $("html, body, #container").css({ + width: "100%", + height: "100%" + }); + // show the close button + $("#close").css({ display: "block" }) + .on("click", Browser.gui.closePopup); + + } else { + // normal popup, resize body to match #container + var width = $("#container").width(); + var height = $("#container").height(); + + $("html, body").css({ + width: width, + height: height, + }); + } } From 7ab1d5f552cff316a12309091488d5d4bf1848b9 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 20:56:59 +0200 Subject: [PATCH 09/17] make Util.getIconInfo async --- src/js/common/browser.js | 2 +- src/js/common/util.js | 5 ++--- src/js/content/content.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index dd11b30..6e76393 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -180,7 +180,7 @@ Browser.gui.refreshIcon = function(tabId, cb) { return; } - Util.getIconInfo(tabId, function(info) { + Util.getIconInfo(tabId).then(info => { if(Browser.capabilities.permanentIcon()) Browser.gui._refreshBrowserAction(tabId, info, cb); else diff --git a/src/js/common/util.js b/src/js/common/util.js index a02e0bc..db8e875 100644 --- a/src/js/common/util.js +++ b/src/js/common/util.js @@ -41,14 +41,13 @@ var Util = { // title: icon's title } // // - getIconInfo: async function(about, handler) { + getIconInfo: async function(about) { if(typeof(about) != 'object') { // tabId const Browser = require('./browser'); about = await Browser.rpc.call(about, 'getState', []); } - const info = await Util._getStateIconInfo(about); - handler(info); + return await Util._getStateIconInfo(about); }, _getStateIconInfo: async function(state) { diff --git a/src/js/content/content.js b/src/js/content/content.js index 62d204c..839fa14 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -205,7 +205,7 @@ Browser.init('content'); // we only call it if the icon is different than the default icon! // if(Browser.capabilities.permanentIcon() && !inFrame) { - Util.getIconInfo({ callUrl: myUrl, apiCalls: 0 }, function(info) { + Util.getIconInfo({ callUrl: myUrl, apiCalls: 0 }).then(info => { if(info.private != info.defaultPrivate) // the icon for myUrl is different than the default Browser.gui.refreshIcon('self'); }) From 0bdad87426533b769d6a416f786e11c2f7b6a21f Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 21:12:40 +0200 Subject: [PATCH 10/17] make Browser.gui.refreshIcon/refreshAllIcons async --- src/js/common/browser.js | 113 ++++++++++++++++------------------ src/js/common/browser_base.js | 4 +- src/js/gui/options.js | 5 +- 3 files changed, 58 insertions(+), 64 deletions(-) diff --git a/src/js/common/browser.js b/src/js/common/browser.js index 6e76393..d721b3c 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -36,10 +36,8 @@ Browser._main_script = function() { // handlers to do them in the main script // Browser.rpc.register('refreshIcon', async function(tabId, callerTabId) { - return new Promise(resolve => { - // 'self' tabId in the content script means refresh its own tab - Browser.gui.refreshIcon(tabId == 'self' ? callerTabId : tabId, resolve); - }); + // 'self' tabId in the content script means refresh its own tab + await Browser.gui.refreshIcon(tabId == 'self' ? callerTabId : tabId); }); Browser.rpc.register('closeTab', function(tabId) { @@ -168,7 +166,7 @@ Browser.storage.clear = async function() { //////////////////// gui /////////////////////////// // // -Browser.gui.refreshIcon = function(tabId, cb) { +Browser.gui.refreshIcon = async function(tabId) { // delegate the call to the 'main' script if: // - we're in 'content': browser.pageAction/browserAction is not available there // - we use the FF pageAction workaround: we need to update Browser.gui.iconShown in 'main' @@ -176,16 +174,15 @@ Browser.gui.refreshIcon = function(tabId, cb) { if(Browser._script == 'content' || (Browser._script != 'main' && Browser.capabilities.needsPAManualHide()) ) { - Browser.rpc.call(null, 'refreshIcon', [tabId]).then(cb); + await Browser.rpc.call(null, 'refreshIcon', [tabId]); return; } - Util.getIconInfo(tabId).then(info => { - if(Browser.capabilities.permanentIcon()) - Browser.gui._refreshBrowserAction(tabId, info, cb); - else - Browser.gui._refreshPageAction(tabId, info, cb); - }); + const info = await Util.getIconInfo(tabId); + if(Browser.capabilities.permanentIcon()) + await Browser.gui._refreshBrowserAction(tabId, info); + else + await Browser.gui._refreshPageAction(tabId, info); }; Browser.gui._icons = function(private) { @@ -196,24 +193,22 @@ Browser.gui._icons = function(private) { return ret; } -Browser.gui._refreshPageAction = function(tabId, info, cb) { +Browser.gui._refreshPageAction = function(tabId, info) { if(info.hidden || info.apiCalls == 0) { browser.pageAction.hide(tabId); - if(cb) cb(); return; } - if(Browser.gui.iconShown) - Browser.gui.iconShown[tabId] = 1; + return new Promise(resolve => { + if(Browser.gui.iconShown) + Browser.gui.iconShown[tabId] = 1; - browser.pageAction.setPopup({ - tabId: tabId, - popup: "popup.html?tabId=" + tabId // pass tabId in the url - }); - browser.pageAction.show(tabId); + browser.pageAction.setPopup({ + tabId: tabId, + popup: "popup.html?tabId=" + tabId // pass tabId in the url + }); + browser.pageAction.show(tabId); - // Firefox on Android (version 56) doesn't support pageAction.setIcon/setTitle so we try/catch - try { browser.pageAction.setTitle({ tabId: tabId, title: info.title @@ -221,47 +216,47 @@ Browser.gui._refreshPageAction = function(tabId, info, cb) { browser.pageAction.setIcon({ tabId: tabId, path: Browser.gui._icons(info.private) - }, cb); // setIcon is the only pageAction.set* method with a callback - } catch(e) { - if(cb) cb(); - } + }, resolve); // setIcon is the only pageAction.set* method with a callback + }); } -Browser.gui._refreshBrowserAction = function(tabId, info, cb) { - browser.browserAction.setTitle({ - tabId: tabId, - title: info.title - }); - browser.browserAction.setBadgeText({ - tabId: tabId, - text: (info.apiCalls || "").toString() - }); - browser.browserAction.setBadgeBackgroundColor({ - tabId: tabId, - color: "#b12222" - }); - browser.browserAction.setPopup({ - tabId: tabId, - popup: "popup.html" + (tabId ? "?tabId="+tabId : "") // pass tabId in the url +Browser.gui._refreshBrowserAction = function(tabId, info) { + return new Promise(resolve => { + browser.browserAction.setTitle({ + tabId: tabId, + title: info.title + }); + browser.browserAction.setBadgeText({ + tabId: tabId, + text: (info.apiCalls || "").toString() + }); + browser.browserAction.setBadgeBackgroundColor({ + tabId: tabId, + color: "#b12222" + }); + browser.browserAction.setPopup({ + tabId: tabId, + popup: "popup.html" + (tabId ? "?tabId="+tabId : "") // pass tabId in the url + }); + browser.browserAction.setIcon({ + tabId: tabId, + path: Browser.gui._icons(info.private) + }, resolve); // setIcon is the only browserAction.set* method with a callback }); - browser.browserAction.setIcon({ - tabId: tabId, - path: Browser.gui._icons(info.private) - }, cb); // setIcon is the only browserAction.set* method with a callback } -Browser.gui.refreshAllIcons = function(cb) { - browser.tabs.query({}, function(tabs) { - // for browser action, also refresh default state (null tabId) - if(Browser.capabilities.permanentIcon()) - tabs.push({ id: null }); - - var done = 0; - for(var i = 0; i < tabs.length; i++) - Browser.gui.refreshIcon(tabs[i].id, function() { - if(++done == tabs.length && cb) - cb(); - }); +Browser.gui.refreshAllIcons = async function() { + return new Promise(resolve => { + browser.tabs.query({}, async function(tabs) { + // for browser action, also refresh default state (null tabId) + if(Browser.capabilities.permanentIcon()) + tabs.push({ id: null }); + + for(var i = 0; i < tabs.length; i++) + await Browser.gui.refreshIcon(tabs[i].id); + + resolve(); + }); }); }; diff --git a/src/js/common/browser_base.js b/src/js/common/browser_base.js index 71ee43f..188dea3 100644 --- a/src/js/common/browser_base.js +++ b/src/js/common/browser_base.js @@ -130,14 +130,14 @@ const Browser = { // If called from a content script and tabId = 'self' it refreshes the icon of the content script's tab. // getIconInfo should be called to get the icon's info // - refreshIcon: function(tabId) {}, + refreshIcon: async function(tabId) {}, // Browser.gui.refreshAllIcons() // // Refreshes the icons of all tabs. // getIconInfo should be called to get the icon's info // - refreshAllIcons: function() {}, + refreshAllIcons: async function() {}, // Browser.gui.showPage(name) // diff --git a/src/js/gui/options.js b/src/js/gui/options.js index ae3c19c..243ffe1 100644 --- a/src/js/gui/options.js +++ b/src/js/gui/options.js @@ -446,9 +446,8 @@ function showCurrentPosition() { async function restoreDefaults() { if(window.confirm('Are you sure you want to restore the default options?')) { await Browser.storage.clear(); - Browser.gui.refreshAllIcons(function() { - location.reload(); - }); + await Browser.gui.refreshAllIcons(); + location.reload(); } } From 3054ced714feb2689c1e0e79b688aa3d397a9aff Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Wed, 16 Mar 2022 21:18:46 +0200 Subject: [PATCH 11/17] some more await uses --- src/js/content/content.js | 76 +++++++++++++++++++-------------------- src/js/gui/options.js | 10 +++--- src/js/gui/popup.js | 10 +++--- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/js/content/content.js b/src/js/content/content.js index 839fa14..1125edd 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -197,49 +197,49 @@ async function addNoise(position) { return position; } -Browser.init('content'); +(async function() { + Browser.init('content'); -// if a browser action (always visible button) is used, we need to refresh the -// icon immediately (before the API is called). HOWEVER: Browser.gui.refreshIcon -// causes the background script to be awaken. To avoid doing this on every page, -// we only call it if the icon is different than the default icon! -// -if(Browser.capabilities.permanentIcon() && !inFrame) { - Util.getIconInfo({ callUrl: myUrl, apiCalls: 0 }).then(info => { + // if a browser action (always visible button) is used, we need to refresh the + // icon immediately (before the API is called). HOWEVER: Browser.gui.refreshIcon + // causes the background script to be awaken. To avoid doing this on every page, + // we only call it if the icon is different than the default icon! + // + if(Browser.capabilities.permanentIcon() && !inFrame) { + const info = await Util.getIconInfo({ callUrl: myUrl, apiCalls: 0 }); if(info.private != info.defaultPrivate) // the icon for myUrl is different than the default Browser.gui.refreshIcon('self'); - }) -} - -// only the top frame handles getState and apiCalledInFrame requests -if(!inFrame) { - Browser.rpc.register('getState', function(tabId) { - return { - callUrl: callUrl, - apiCalls: apiCalls - }; - }); + } - Browser.rpc.register('apiCalledInFrame', function(iframeUrl, tabId) { - apiCalls++; - if(Browser.capabilities.iframeGeoFromOwnDomain()) - callUrl = iframeUrl; - Browser.gui.refreshIcon('self'); + // only the top frame handles getState and apiCalledInFrame requests + if(!inFrame) { + Browser.rpc.register('getState', function(tabId) { + return { + callUrl: callUrl, + apiCalls: apiCalls + }; + }); + + Browser.rpc.register('apiCalledInFrame', function(iframeUrl, tabId) { + apiCalls++; + if(Browser.capabilities.iframeGeoFromOwnDomain()) + callUrl = iframeUrl; + Browser.gui.refreshIcon('self'); - return myUrl; - }); -} + return myUrl; + }); + } -if(Browser.testing) { - // test for nested calls, and for correct passing of tabId - // - Browser.rpc.register('nestedTestTab', function(tabId) { - Browser.log("in nestedTestTab, returning 'content'"); - return "content"; - }); + if(Browser.testing) { + // test for nested calls, and for correct passing of tabId + // + Browser.rpc.register('nestedTestTab', function(tabId) { + Browser.log("in nestedTestTab, returning 'content'"); + return "content"; + }); - Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', []).then(res => { + Browser.log("calling nestedTestMain"); + const res = await Browser.rpc.call(null, 'nestedTestMain', []); Browser.log('got from nestedTestMain', res); - }); -} + } +}()) \ No newline at end of file diff --git a/src/js/gui/options.js b/src/js/gui/options.js index 243ffe1..eecefa4 100644 --- a/src/js/gui/options.js +++ b/src/js/gui/options.js @@ -494,8 +494,9 @@ $(document).ready(function() { $(document).on("click", "#levelMapCurrentPos", showCurrentPosition); // this doesn't exist yet (it's inside the popup), so we set in document }); +(async function() { + if(!Browser.testing) return; -if(Browser.testing) { // test for nested calls, and for correct passing of tabId // Browser.rpc.register('nestedTestTab', function(tabId) { @@ -504,7 +505,6 @@ if(Browser.testing) { }); Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', []).then(res => { - Browser.log('got from nestedTestMain', res); - }); -} + const res = await Browser.rpc.call(null, 'nestedTestMain', []); + Browser.log('got from nestedTestMain', res); +}()); \ No newline at end of file diff --git a/src/js/gui/popup.js b/src/js/gui/popup.js index 19fb4d3..6e51ff6 100644 --- a/src/js/gui/popup.js +++ b/src/js/gui/popup.js @@ -133,8 +133,9 @@ async function drawUI() { } } +(async function() { + if(!Browser.testing) return; -if(Browser.testing) { // test for nested calls, and for correct passing of tabId // Browser.rpc.register('nestedTestTab', function(tabId) { @@ -143,7 +144,6 @@ if(Browser.testing) { }); Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', []).then(res => { - Browser.log('got from nestedTestMain', res); - }); -} + const res = await Browser.rpc.call(null, 'nestedTestMain', []); + Browser.log('got from nestedTestMain', res); +}()); \ No newline at end of file From d418de6b0d5f93ea641e3700cebed7f07abc6d7e Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Thu, 17 Mar 2022 12:43:05 +0200 Subject: [PATCH 12/17] make demo message depend on the current config --- src/js/gui/demo.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/js/gui/demo.js b/src/js/gui/demo.js index 5fc16dc..b2ab0ae 100644 --- a/src/js/gui/demo.js +++ b/src/js/gui/demo.js @@ -7,7 +7,7 @@ require('leaflet.locatecontrol'); const Browser = require('../common/browser'); Browser.inDemo = true; // used in content.js -var intro; +var intro, steps; var demoMap; var showPressed, geoDone; @@ -76,7 +76,7 @@ function showCurrentPosition() { intro.goToStep(3); } -function drawPosition(pos) { +async function drawPosition(pos) { var latlng = [pos.coords.latitude, pos.coords.longitude]; var acc = pos.coords.accuracy; @@ -101,13 +101,25 @@ function drawPosition(pos) { demoMap.fitBounds(demoMap.accuracy.getBounds()); geoDone = true; - if(intro._currentStep + 1 <= 3) + if(intro._currentStep + 1 <= 3) { + const st = await Browser.storage.get(); + var level = st.paused ? 'real' : (st.domainLevel['demo-page'] || st.defaultLevel); + + var s = + level == 'fixed' ? 'Location Guard replaced it with your configured fixed location.' : + level == 'real' ? 'Location Guard did not modify it.' : + 'Location Guard added "noise" to it so that it\'s not very accurate.'; + + // hacky way to modify the step's message + intro._introItems[3].intro = steps[3].intro.replace("%s", s); + intro.goToStep(4); + } } function startDemo() { var iconClass = Browser.capabilities.permanentIcon() ? 'lg-icon-browseraction' : 'lg-icon-pageaction'; - var steps = [ { + steps = [ { element: ".placeholder-step1", intro: '

Location Guard was successfully installed.

This demo illustrates its use.

', position: "floating", @@ -133,8 +145,8 @@ function startDemo() { }, { element: ".placeholder-step4", intro: - '

This is the location disclosed by your browser. Location Guard added "noise" to it so that it\'s not ' + - 'very accurate.

Click on the icon to try more options.

', + '

This is the location disclosed by your browser. %s

Click on the icon to try more options.

', position: "bottom-middle-aligned", highlightClass: 'highlight-step4', tooltipClass: 'tooltip-step4', From bb3613c89c815096dd7f5b7b11f84c2b8b15aeb9 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Thu, 17 Mar 2022 19:55:03 +0200 Subject: [PATCH 13/17] cleanup code in content.js --- src/js/content/content.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/js/content/content.js b/src/js/content/content.js index 1125edd..7022f54 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -75,22 +75,21 @@ var callUrl = myUrl; // the url from which the last call is _shown_ to be ma // var rpc = new PostRPC('page-content', window, window, window.origin); rpc.register('getNoisyPosition', async function(options) { + callUrl = myUrl; // last call happened here if(inFrame) { // we're in a frame, we need to notify the top window, and get back the *url used in the permission dialog* // (which might be either the iframe url, or the top window url, depending on how the browser handles permissions). // To avoid cross-origin issues, we call apiCalledInFrame in the main script, which echoes the // call back to this tab to be answered by the top window - const topUrl = await Browser.rpc.call(null, 'apiCalledInFrame', [myUrl]); - callUrl = Browser.capabilities.iframeGeoFromOwnDomain() ? myUrl : topUrl; - return await getNoisyPosition(options); - + if(!Browser.capabilities.iframeGeoFromOwnDomain()) + callUrl = await Browser.rpc.call(null, 'apiCalledInFrame', [myUrl]); } else { // refresh icon before fetching the location apiCalls++; - callUrl = myUrl; // last call happened here Browser.gui.refreshIcon('self'); - return await getNoisyPosition(options); } + + return await getNoisyPosition(options); }); // gets the options passed to the fake navigator.geolocation.getCurrentPosition. From aa6e14ab001b9b928b2aa7d00c6deab9ad22f647 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Thu, 17 Mar 2022 21:28:53 +0200 Subject: [PATCH 14/17] fix async use of refreshAllIcons in popup --- src/js/gui/popup.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js/gui/popup.js b/src/js/gui/popup.js index 6e51ff6..d1d739d 100644 --- a/src/js/gui/popup.js +++ b/src/js/gui/popup.js @@ -37,14 +37,16 @@ async function doAction() { var st = await Browser.storage.get(); st.hideIcon = true; await Browser.storage.set(st); - Browser.gui.refreshAllIcons(Browser.gui.closePopup); + await Browser.gui.refreshAllIcons(); + Browser.gui.closePopup(); break; case 'pause': var st = await Browser.storage.get(); st.paused = !st.paused; await Browser.storage.set(st); - Browser.gui.refreshAllIcons(Browser.gui.closePopup); + await Browser.gui.refreshAllIcons(); + Browser.gui.closePopup(); break; case 'setLevel': @@ -63,7 +65,8 @@ async function doAction() { st.domainLevel[domain] = level; await Browser.storage.set(st); - Browser.gui.refreshAllIcons(Browser.gui.closePopup); + await Browser.gui.refreshAllIcons(); + Browser.gui.closePopup(); break; } } From ce9ecc6854d20e49453a6efe758e55fdf72017e6 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Thu, 17 Mar 2022 21:40:21 +0200 Subject: [PATCH 15/17] enable watchPosition when paused/using real location --- src/js/content/content.js | 6 +++++ src/js/content/injected.js | 48 +++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/js/content/content.js b/src/js/content/content.js index 7022f54..158014e 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -91,6 +91,12 @@ rpc.register('getNoisyPosition', async function(options) { return await getNoisyPosition(options); }); +rpc.register('isActive', async function() { + // Returns true if privacy-protection is active (not paused, not level == 'real') + const st = await Browser.storage.get(); + var domain = Util.extractDomain(callUrl); + return !st.paused && (st.domainLevel[domain] || st.defaultLevel) != 'real'; +}); // gets the options passed to the fake navigator.geolocation.getCurrentPosition. // Either returns fixed pos directly, or calls the real one, then calls addNoise. diff --git a/src/js/content/injected.js b/src/js/content/injected.js index 454cbc5..8167e9a 100644 --- a/src/js/content/injected.js +++ b/src/js/content/injected.js @@ -4,34 +4,56 @@ module.exports = function(PostRPC) { if(navigator.geolocation) { // the geolocation API exists var prpc; - - // we replace geolocation methods with our own - // the real methods will be called by the content script (not by the page) - // so we dont need to keep them at all. - - navigator.geolocation.getCurrentPosition = async function(cb1, cb2, options) { + function getPostRPC() { // create a PostRPC object only when getCurrentPosition is called. This // avoids having our own postMessage handler on every page if(!prpc) prpc = new PostRPC('page-content', window, window, window.origin); // This PostRPC is created by the injected code! + return prpc; + } + // We replace geolocation methods with our own. + // getCurrentPosition will be called by the content script (not by the page) + // so we dont need to keep it at all. + + navigator.geolocation.getCurrentPosition = async function(cb1, cb2, options) { // call getNoisyPosition on the content-script // call cb1 on success, cb2 on failure - const res = await prpc.call('getNoisyPosition', [options]); + const res = await getPostRPC().call('getNoisyPosition', [options]); var f = res.success ? cb1 : cb2; if(f) f(res.position); }; + const watchPosition = navigator.geolocation.watchPosition; + const handlers = {}; navigator.geolocation.watchPosition = function(cb1, cb2, options) { - // we don't install a real watch, just return the position once - // TODO: implement something closer to a watch - this.getCurrentPosition(cb1, cb2, options); - return Math.floor(Math.random()*10000); // return random id, it's not really used + // We need to return a handler synchronously, but decide whether we'll use the real watchPosition or not + // asynchronously. So we create our own handler, and we'll associate it with the real one later. + const handler = Math.floor(Math.random()*10000); + + (async () => { + if(await getPostRPC().call('isActive')) { + // Protection is active, we don't install a real watch, just return the position once + this.getCurrentPosition(cb1, cb2, options); + } else { + // Protection inactive, call the real watchPosition (and associate the real handler) + handlers[handler] = watchPosition.apply(navigator.geolocation, [ + async position => await prpc.call('isActive') || cb1(position), // ignore the call if privacy protection + async error => await prpc.call('isActive') || cb2(error), // becomes active later! + options + ]); + } + })(); + return handler; }; - navigator.geolocation.clearWatch = function () { - // nothing to do + const clearWatch = navigator.geolocation.clearWatch; + navigator.geolocation.clearWatch = function (handler) { + if(handler in handlers) { + clearWatch.apply(navigator.geolocation, [handlers[handler]]); + delete handlers[handler]; + } }; } From c56455e7b5e47431309667d0a01eb3110b70d152 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Fri, 18 Mar 2022 13:44:19 +0200 Subject: [PATCH 16/17] improve handling of watchPosition --- src/js/content/content.js | 15 +++++++++++---- src/js/content/injected.js | 23 ++++++++++++----------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/js/content/content.js b/src/js/content/content.js index 158014e..8ac9308 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -91,11 +91,18 @@ rpc.register('getNoisyPosition', async function(options) { return await getNoisyPosition(options); }); -rpc.register('isActive', async function() { - // Returns true if privacy-protection is active (not paused, not level == 'real') +rpc.register('watchAllowed', async function(firstCall) { + // Returns true if using the real watch is allowed. Only if paused or level == 'real'. + // Also don't allow in iframes (to simplify the code). const st = await Browser.storage.get(); - var domain = Util.extractDomain(callUrl); - return !st.paused && (st.domainLevel[domain] || st.defaultLevel) != 'real'; + var level = st.domainLevel[Util.extractDomain(myUrl)] || st.defaultLevel; + var allowed = !inFrame && (st.paused || level == 'real'); + + if(allowed && !firstCall) { + apiCalls++; + Browser.gui.refreshIcon('self'); + } + return allowed; }); // gets the options passed to the fake navigator.geolocation.getCurrentPosition. diff --git a/src/js/content/injected.js b/src/js/content/injected.js index 8167e9a..7bb5db2 100644 --- a/src/js/content/injected.js +++ b/src/js/content/injected.js @@ -11,6 +11,10 @@ module.exports = function(PostRPC) { prpc = new PostRPC('page-content', window, window, window.origin); // This PostRPC is created by the injected code! return prpc; } + async function callCb(cb, pos, checkAllowed) { + if(cb && (!checkAllowed || await getPostRPC().call('watchAllowed', [false]))) + cb(pos); + } // We replace geolocation methods with our own. // getCurrentPosition will be called by the content script (not by the page) @@ -20,9 +24,7 @@ module.exports = function(PostRPC) { // call getNoisyPosition on the content-script // call cb1 on success, cb2 on failure const res = await getPostRPC().call('getNoisyPosition', [options]); - var f = res.success ? cb1 : cb2; - if(f) - f(res.position); + callCb(res.success ? cb1 : cb2, res.position, false); }; const watchPosition = navigator.geolocation.watchPosition; @@ -33,17 +35,16 @@ module.exports = function(PostRPC) { const handler = Math.floor(Math.random()*10000); (async () => { - if(await getPostRPC().call('isActive')) { - // Protection is active, we don't install a real watch, just return the position once - this.getCurrentPosition(cb1, cb2, options); - } else { - // Protection inactive, call the real watchPosition (and associate the real handler) + if(await getPostRPC().call('watchAllowed', [true])) + // We're allowed to call the real watchPosition (note: remember the handler) handlers[handler] = watchPosition.apply(navigator.geolocation, [ - async position => await prpc.call('isActive') || cb1(position), // ignore the call if privacy protection - async error => await prpc.call('isActive') || cb2(error), // becomes active later! + position => callCb(cb1, position, true), // ignore the call if privacy protection + error => callCb(cb2, error, true), // becomes active later! options ]); - } + else + // Not allowed, we don't install a real watch, just return the position once + this.getCurrentPosition(cb1, cb2, options); })(); return handler; }; From c40064fabe881ad2473d2cb17adbb4f4fa41f267 Mon Sep 17 00:00:00 2001 From: Kostas Chatzikokolakis Date: Fri, 18 Mar 2022 14:05:27 +0200 Subject: [PATCH 17/17] bump version to 2.5.0 --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index a849a55..3c3456b 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -11,7 +11,7 @@ "64": "images/pin_64.png", "128": "images/pin_128.png" }, - "version": "2.4.3", + "version": "2.5.0", #if is_firefox // Needed for Firefox on Android, same as the previous jetpack id