diff --git a/src/js/common/browser.js b/src/js/common/browser.js index cfc5b2f..0cb53c5 100644 --- a/src/js/common/browser.js +++ b/src/js/common/browser.js @@ -35,10 +35,9 @@ 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) { + Browser.rpc.register('refreshIcon', async function(tabId, callerTabId) { // 'self' tabId in the content script means refresh its own tab - Browser.gui.refreshIcon(tabId == 'self' ? callerTabId : tabId, replyHandler); - return true; // will reply later + await Browser.gui.refreshIcon(tabId == 'self' ? callerTabId : tabId); }); Browser.rpc.register('closeTab', function(tabId) { @@ -98,19 +97,28 @@ 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 = 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); + }); } @@ -124,35 +132,41 @@ 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 = async 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) { - browser.storage.local.clear(handler); +Browser.storage.clear = async function() { + return new Promise(resolve => { + browser.storage.local.clear(resolve); + }); }; //////////////////// 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' @@ -160,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], cb); + await Browser.rpc.call(null, 'refreshIcon', [tabId]); return; } - Util.getIconInfo(tabId, function(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) { @@ -180,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 @@ -205,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(); + }); }); }; @@ -253,24 +264,24 @@ Browser.gui.showPage = function(name) { browser.tabs.create({ url: browser.extension.getURL(name) }); }; -Browser.gui.getCallUrl = function(tabId, handler) { - 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 - }); +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 2b2f6ae..3c41241 100644 --- a/src/js/common/browser_base.js +++ b/src/js/common/browser_base.js @@ -30,29 +30,24 @@ 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) {}, - // 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 @@ -62,24 +57,24 @@ 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) + // browser.storage.clear() // - // Clears the storage. Calls the handler when finished. + // Clears the storage. // - clear: function(handler) {}, + clear: async function() {}, // default storage object // @@ -135,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) // @@ -151,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/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/common/util.js b/src/js/common/util.js index 2bba44b..db8e875 100644 --- a/src/js/common/util.js +++ b/src/js/common/util.js @@ -41,40 +41,37 @@ var Util = { // title: icon's title } // // - getIconInfo: function(about, handler) { - if(typeof(about) == 'object') // null or state object - Util._getStateIconInfo(about, handler); - else { // tabId + getIconInfo: async function(about) { + if(typeof(about) != 'object') { // tabId const Browser = require('./browser'); - Browser.rpc.call(about, 'getState', [], function(state) { - Util._getStateIconInfo(state, handler); - }); + about = await Browser.rpc.call(about, 'getState', []); } + + return await Util._getStateIconInfo(about); }, - _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(function(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 38d4de8..8ac9308 100644 --- a/src/js/content/content.js +++ b/src/js/content/content.js @@ -74,69 +74,78 @@ 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) { + 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 - Browser.rpc.call(null, 'apiCalledInFrame', [myUrl], function(topUrl) { - callUrl = Browser.capabilities.iframeGeoFromOwnDomain() ? myUrl : topUrl; - getNoisyPosition(options, replyHandler); - }); - + 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'); - getNoisyPosition(options, replyHandler); } - return true; // will reply later + return await getNoisyPosition(options); +}); +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 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. // Either returns fixed pos directly, or calls the real one, then calls addNoise. // -function getNoisyPosition(options, replyHandler) { - Browser.storage.get(function(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,105 +154,104 @@ function getNoisyPosition(options, replyHandler) { // gets position, returs noisy version based on the privacy options // -function addNoise(position, handler) { - Browser.storage.get(function(st) { - 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); +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); - } 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'); +(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 }, function(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, replyHandler) { - replyHandler({ - callUrl: callUrl, - apiCalls: apiCalls + // 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, replyHandler) { - apiCalls++; - if(Browser.capabilities.iframeGeoFromOwnDomain()) - callUrl = iframeUrl; - Browser.gui.refreshIcon('self'); + 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.log("in nestedTestTab, returning 'content'"); - replyHandler("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', [], function(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/content/injected.js b/src/js/content/injected.js index fc53332..7bb5db2 100644 --- a/src/js/content/injected.js +++ b/src/js/content/injected.js @@ -4,34 +4,57 @@ 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 = 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; + } + 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) + // so we dont need to keep it at all. + + navigator.geolocation.getCurrentPosition = async function(cb1, cb2, options) { // call getNoisyPosition on the content-script - prpc.call('getNoisyPosition', [options], function(success, res) { - // call cb1 on success, cb2 on failure - var f = success ? cb1 : cb2; - if(f) f(res); - }); + // call cb1 on success, cb2 on failure + const res = await getPostRPC().call('getNoisyPosition', [options]); + callCb(res.success ? cb1 : cb2, res.position, false); }; + 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('watchAllowed', [true])) + // We're allowed to call the real watchPosition (note: remember the handler) + handlers[handler] = watchPosition.apply(navigator.geolocation, [ + 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; }; - 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]; + } }; } diff --git a/src/js/gui/demo.js b/src/js/gui/demo.js index 7c87cad..f224aff 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,25 +145,13 @@ 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', } ]; - // 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({ 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 e6b76b3..c4a373d 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(function(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(function(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, function() { - Browser.gui.refreshAllIcons(); - }); - }); + st.updateAccuracy = updateAccuracy; + } + + await Browser.storage.set(st); + Browser.gui.refreshAllIcons(); } -function saveFixedPosNoAPI() { - Browser.storage.get(function(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(function(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(function(st) { - var latlng = [st.fixedPos.latitude, st.fixedPos.longitude]; - - fixedPosMap = new L.map('fixedPosMap') - .addLayer(new L.TileLayer( - Browser.gui.mapTiles().url, - Browser.gui.mapTiles().info, - )) - .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( + Browser.gui.mapTiles().url, + Browser.gui.mapTiles().info, + )) + .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(function(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(function(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(function(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(function(st) { - $('#fixedPosNoAPI').prop('checked', st.fixedPosNoAPI).checkboxradio("refresh"); + const st = await Browser.storage.get(); + $('#fixedPosNoAPI').prop('checked', st.fixedPosNoAPI).checkboxradio("refresh"); - initFixedPosMap(); - }); + initFixedPosMap(); } }); } @@ -452,23 +443,19 @@ 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(); + await Browser.gui.refreshAllIcons(); + location.reload(); } } -function deleteCache() { - Browser.storage.get(function(st) { - st.cachedPos = {}; - Browser.storage.set(st, function() { - 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 +471,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); @@ -507,17 +494,17 @@ $(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, replyHandler) { + Browser.rpc.register('nestedTestTab', function(tabId) { Browser.log("in nestedTestTab, returning 'options'"); - replyHandler("options"); + return "options"; }); Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', [], function(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 0679231..d1d739d 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,19 @@ function doAction() { break; case 'hideIcon': - Browser.storage.get(function(st) { - st.hideIcon = true; - Browser.storage.set(st, function() { - Browser.gui.refreshAllIcons(Browser.gui.closePopup); - }); - }); + var st = await Browser.storage.get(); + st.hideIcon = true; + await Browser.storage.set(st); + await Browser.gui.refreshAllIcons(); + Browser.gui.closePopup(); break; case 'pause': - Browser.storage.get(function(st) { - st.paused = !st.paused; - Browser.storage.set(st, function() { - Browser.gui.refreshAllIcons(Browser.gui.closePopup); - }); - }); + var st = await Browser.storage.get(); + st.paused = !st.paused; + await Browser.storage.set(st); + await Browser.gui.refreshAllIcons(); + Browser.gui.closePopup(); break; case 'setLevel': @@ -58,100 +56,97 @@ function doAction() { default: // set level if(!url) throw "no url"; // just to be sure - Browser.storage.get(function(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, function() { - 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); + await Browser.gui.refreshAllIcons(); + Browser.gui.closePopup(); break; } } -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, function(callUrl) { - Browser.storage.get(function(st) { - 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, + }); + } } +(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, replyHandler) { + Browser.rpc.register('nestedTestTab', function(tabId) { Browser.log("in nestedTestTab, returning 'popup'"); - replyHandler("popup"); + return "popup"; }); Browser.log("calling nestedTestMain"); - Browser.rpc.call(null, 'nestedTestMain', [], function(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/main.js b/src/js/main.js index 1448d61..6b6aaf0 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -18,28 +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], function(res) { - replyHandler(res); - }); - 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', [], function(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'; }); } 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