Forráskód Böngészése

refactor: use web extension APIs

Gerald 8 éve
szülő
commit
ec6ca9974d

+ 1 - 1
.eslintrc.yml

@@ -34,10 +34,10 @@ env:
   node: true
 
 globals:
-  chrome: true
   zip: true
   Vue: true
   CodeMirror: true
   Promise: true
+  browser: true
 
 extends: 'eslint:recommended'

+ 33 - 56
src/background/app.js

@@ -3,24 +3,32 @@ var VMDB = require('./db');
 var sync = require('./sync');
 var requests = require('./requests');
 var cache = require('./utils/cache');
-var tabsUtils = require('./utils/tabs');
 var scriptUtils = require('./utils/script');
 var clipboard = require('./utils/clipboard');
 var options = require('./options');
 
 var vmdb = exports.vmdb = new VMDB;
-var VM_VER = chrome.runtime.getManifest().version;
+var VM_VER = browser.runtime.getManifest().version;
 
 options.hook(function (changes) {
   if ('isApplied' in changes) {
     setIcon(changes.isApplied);
   }
-  _.messenger.post({
+  browser.runtime.sendMessage({
     cmd: 'UpdateOptions',
     data: changes,
   });
 });
 
+function broadcast(data) {
+  browser.tabs.query({})
+  .then(function (tabs) {
+    tabs.forEach(function (tab) {
+      browser.tabs.sendMessage(tab.id, data);
+    });
+  });
+}
+
 var autoUpdate = function () {
   function check() {
     checking = true;
@@ -63,7 +71,7 @@ var commands = {
       version: VM_VER,
     };
     if (src.tab && src.url === src.tab.url) {
-      chrome.tabs.sendMessage(src.tab.id, {cmd: 'GetBadge'});
+      browser.tabs.sendMessage(src.tab.id, {cmd: 'GetBadge'});
     }
     return data.isApplied
       ? vmdb.getScriptsByURL(url).then(function (res) {
@@ -76,7 +84,7 @@ var commands = {
     })
     .then(function (script) {
       sync.sync();
-      _.messenger.post({
+      browser.runtime.sendMessage({
         cmd: 'UpdateScript',
         data: script,
       });
@@ -85,7 +93,7 @@ var commands = {
   SetValue: function (data, _src) {
     return vmdb.setValue(data.uri, data.values)
     .then(function () {
-      tabsUtils.broadcast({
+      broadcast({
         cmd: 'UpdateValues',
         data: {
           uri: data.uri,
@@ -119,7 +127,7 @@ var commands = {
           body: _.i18n('msgWarnGrant', [meta.name||_.i18n('labelNoName')]),
           isClickable: true,
         });
-      _.messenger.post(res);
+      browser.runtime.sendMessage(res);
       sync.sync();
       return res.data;
     });
@@ -144,7 +152,7 @@ var commands = {
   },
   HttpRequest: function (details, src) {
     requests.httpRequest(details, function (res) {
-      _.messenger.send(src.tab.id, {
+      browser.tabs.sendMessage(src.tab.id, {
         cmd: 'HttpRequested',
         data: res,
       });
@@ -175,12 +183,13 @@ var commands = {
   },
   Notification: function (data, _src) {
     return new Promise(function (resolve) {
-      chrome.notifications.create({
+      browser.notifications.create({
         type: 'basic',
         title: data.title || _.i18n('extName'),
         message: data.text,
         iconUrl: data.image || _.defaultImage,
-      }, function (id) {
+      })
+      .then(function (id) {
         resolve(id);
       });
     });
@@ -190,7 +199,7 @@ var commands = {
     return false;
   },
   OpenTab: function (data, _src) {
-    chrome.tabs.create({
+    browser.tabs.create({
       url: data.url,
       active: data.active,
     });
@@ -215,33 +224,10 @@ var commands = {
 };
 
 vmdb.initialized.then(function () {
-  chrome.runtime.onMessage.addListener(function (req, src, callback) {
+  browser.runtime.onMessage.addListener(function (req, src) {
     var func = commands[req.cmd];
     if (func) {
-      var res = func(req.data, src);
-      if (res === false) return;
-      var finish = function (data) {
-        try {
-          callback(data);
-        } catch (e) {
-          // callback fails if not given in content page
-        }
-      };
-      Promise.resolve(res).then(function (data) {
-        finish({
-          data: data,
-          error: null,
-        });
-      }, function (data) {
-        if (data instanceof Error) {
-          console.error(data);
-          data = data.toString();
-        }
-        finish({
-          error: data,
-        });
-      });
-      return true;
+      return func(req.data, src);
     }
   });
   setTimeout(autoUpdate, 2e4);
@@ -251,7 +237,7 @@ vmdb.initialized.then(function () {
 // Common functions
 
 function notify(options) {
-  chrome.notifications.create(options.id || 'ViolentMonkey', {
+  browser.notifications.create(options.id || 'ViolentMonkey', {
     type: 'basic',
     iconUrl: _.defaultImage,
     title: options.title + ' - ' + _.i18n('extName'),
@@ -266,12 +252,12 @@ var setBadge = function () {
     var o = badges[src.id];
     if (!o) o = badges[src.id] = {num: 0};
     o.num += num;
-    chrome.browserAction.setBadgeBackgroundColor({
+    browser.browserAction.setBadgeBackgroundColor({
       color: '#808',
       tabId: src.tab.id,
     });
     var text = (options.get('showBadge') && o.num || '').toString();
-    chrome.browserAction.setBadgeText({
+    browser.browserAction.setBadgeText({
       text: text,
       tabId: src.tab.id,
     });
@@ -282,19 +268,8 @@ var setBadge = function () {
   };
 }();
 
-_.messenger = function () {
-  return {
-    post: function (data) {
-      chrome.runtime.sendMessage(data);
-    },
-    send: function (tabId, data) {
-      chrome.tabs.sendMessage(tabId, data);
-    },
-  };
-}();
-
 function setIcon(isApplied) {
-  chrome.browserAction.setIcon({
+  browser.browserAction.setIcon({
     path: {
       19: '/images/icon19' + (isApplied ? '' : 'w') + '.png',
       38: '/images/icon38' + (isApplied ? '' : 'w') + '.png'
@@ -303,19 +278,21 @@ function setIcon(isApplied) {
 }
 setIcon(options.get('isApplied'));
 
-chrome.notifications.onClicked.addListener(function (id) {
+browser.notifications.onClicked.addListener(function (id) {
   if (id == 'VM-NoGrantWarning') {
-    tabsUtils.create('http://wiki.greasespot.net/@grant');
+    browser.tabs.create({
+      url: 'http://wiki.greasespot.net/@grant',
+    });
   } else {
-    tabsUtils.broadcast({
+    broadcast({
       cmd: 'NotificationClick',
       data: id,
     });
   }
 });
 
-chrome.notifications.onClosed.addListener(function (id) {
-  tabsUtils.broadcast({
+browser.notifications.onClosed.addListener(function (id) {
+  broadcast({
     cmd: 'NotificationClose',
     data: id,
   });

+ 8 - 8
src/background/db.js

@@ -218,7 +218,7 @@ VMDB.prototype.removeScript = function (id) {
     };
   })
   .then(function () {
-    _.messenger.post({
+    browser.runtime.sendMessage({
       cmd: 'RemoveScript',
       data: id,
     });
@@ -606,35 +606,35 @@ VMDB.prototype.checkUpdate = function () {
         return Promise.resolve();
       res.data.checking = false;
       res.data.message = _.i18n('msgNoUpdate');
-      _.messenger.post(res);
+      browser.runtime.sendMessage(res);
       return Promise.reject();
     };
     var errHandler = function (_xhr) {
       res.data.checking = false;
       res.data.message = _.i18n('msgErrorFetchingUpdateInfo');
-      _.messenger.post(res);
+      browser.runtime.sendMessage(res);
       return Promise.reject();
     };
     var update = function () {
       if (!downloadURL) {
         res.data.message = '<span class="new">' + _.i18n('msgNewVersion') + '</span>';
-        _.messenger.post(res);
+        browser.runtime.sendMessage(res);
         return Promise.reject();
       }
       res.data.message = _.i18n('msgUpdating');
-      _.messenger.post(res);
+      browser.runtime.sendMessage(res);
       return scriptUtils.fetch(downloadURL).then(function (xhr) {
         return xhr.responseText;
       }, function (_xhr) {
         res.data.checking = false;
         res.data.message = _.i18n('msgErrorFetchingScript');
-        _.messenger.post(res);
+        browser.runtime.sendMessage(res);
         return Promise.reject();
       });
     };
     if (!updateURL) return Promise.reject();
     res.data.message = _.i18n('msgCheckingForUpdate');
-    _.messenger.post(res);
+    browser.runtime.sendMessage(res);
     return scriptUtils.fetch(updateURL, null, {
       Accept: 'text/x-userscript-meta',
     }).then(okHandler, errHandler).then(update);
@@ -652,7 +652,7 @@ VMDB.prototype.checkUpdate = function () {
           code: code,
         }).then(function (res) {
           res.data.checking = false;
-          _.messenger.post(res);
+          browser.runtime.sendMessage(res);
         });
       }, function () {
         delete processes[script.id];

+ 12 - 11
src/background/index.html

@@ -1,12 +1,13 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<meta charset="utf-8">
-		<title>ViolentMonkey</title>
-    <script src="/lib/define.js"></script>
-		<script src="/common.js"></script>
-	</head>
-  <body>
-    <script src="app.js"></script>
-  </body>
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ViolentMonkey</title>
+  <script src="/lib/define.js"></script>
+  <script src="/mylib/browser.js"></script>
+  <script src="/common.js"></script>
+</head>
+<body>
+  <script src="app.js"></script>
+</body>
 </html>

+ 10 - 11
src/background/requests.js

@@ -1,4 +1,3 @@
-var tabsUtils = require('./utils/tabs');
 var cache = require('./utils/cache');
 var _ = require('../common');
 
@@ -113,7 +112,7 @@ function abortRequest(id) {
 }
 
 // Watch URL redirects
-chrome.webRequest.onBeforeRedirect.addListener(function (details) {
+browser.webRequest.onBeforeRedirect.addListener(function (details) {
   var reqId = verify[details.requestId];
   if (reqId) {
     var req = requests[reqId];
@@ -125,7 +124,7 @@ chrome.webRequest.onBeforeRedirect.addListener(function (details) {
 });
 
 // Modifications on headers
-chrome.webRequest.onBeforeSendHeaders.addListener(function (details) {
+browser.webRequest.onBeforeSendHeaders.addListener(function (details) {
   var headers = details.requestHeaders;
   var newHeaders = [];
   var vmHeaders = {};
@@ -160,7 +159,7 @@ chrome.webRequest.onBeforeSendHeaders.addListener(function (details) {
 
 // tasks are not necessary now, turned off
 // Stop redirects
-// chrome.webRequest.onHeadersReceived.addListener(function (details) {
+// browser.webRequest.onHeadersReceived.addListener(function (details) {
 //   var task = tasks[details.requestId];
 //   if (task) {
 //     delete tasks[details.requestId];
@@ -177,20 +176,20 @@ chrome.webRequest.onBeforeSendHeaders.addListener(function (details) {
 //   urls: ['<all_urls>'],
 //   types: ['xmlhttprequest'],
 // }, ['blocking', 'responseHeaders']);
-// chrome.webRequest.onCompleted.addListener(function (details) {
+// browser.webRequest.onCompleted.addListener(function (details) {
 //   delete tasks[details.requestId];
 // }, {
 //   urls: ['<all_urls>'],
 //   types: ['xmlhttprequest'],
 // });
-// chrome.webRequest.onErrorOccurred.addListener(function (details) {
+// browser.webRequest.onErrorOccurred.addListener(function (details) {
 //   delete tasks[details.requestId];
 // }, {
 //   urls: ['<all_urls>'],
 //   types: ['xmlhttprequest'],
 // });
 
-chrome.webRequest.onBeforeRequest.addListener(function (req) {
+browser.webRequest.onBeforeRequest.addListener(function (req) {
   // onBeforeRequest is fired for local files too
   if (/\.user\.js([\?#]|$)/.test(req.url)) {
     // {cancel: true} will redirect to a blocked view
@@ -205,10 +204,10 @@ chrome.webRequest.onBeforeRequest.addListener(function (req) {
     }
     if ((!x.status || x.status == 200) && !/^\s*</.test(x.responseText)) {
       cache.set(req.url, x.responseText);
-      var url = chrome.extension.getURL('/options/index.html') + '#confirm/' + encodeURIComponent(req.url);
-      if (req.tabId < 0) tabsUtils.create(url);
-      else tabsUtils.get(req.tabId).then(function (t) {
-        tabsUtils.create(url + '/' + encodeURIComponent(t.url));
+      var url = browser.extension.getURL('/options/index.html') + '#confirm/' + encodeURIComponent(req.url);
+      if (req.tabId < 0) browser.tabs.create({url: url});
+      else browser.tabs.get(req.tabId).then(function (tab) {
+        browser.tabs.create({url: url + '/' + encodeURIComponent(tab.url)});
       });
       return noredirect;
     }

+ 2 - 2
src/background/sync/base.js

@@ -175,7 +175,7 @@ var BaseService = serviceFactory({
     return this.events.fire.apply(null, arguments);
   },
   onStateChange: function () {
-    _.messenger.post({
+    browser.runtime.sendMessage({
       cmd: 'UpdateSync',
       data: getStates(),
     });
@@ -406,7 +406,7 @@ var BaseService = serviceFactory({
             }
             return app.vmdb.parseScript(data)
             .then(function (res) {
-              _.messenger.post(res);
+              browser.runtime.sendMessage(res);
             });
           });
         }),

+ 1 - 2
src/background/sync/dropbox.js

@@ -1,5 +1,4 @@
 var base = require('./base');
-var tabsUtils = require('../utils/tabs');
 var searchUtils = require('../utils/search');
 var config = {
   client_id: 'f0q12zup2uys5w8',
@@ -15,7 +14,7 @@ function authorize() {
   var url = 'https://www.dropbox.com/oauth2/authorize';
   var qs = searchUtils.dump(params);
   url += '?' + qs;
-  tabsUtils.create(url);
+  browser.tabs.create({url: url});
 }
 function checkAuth(url) {
   var redirect_uri = config.redirect_uri + '#';

+ 2 - 3
src/background/sync/index.js

@@ -1,8 +1,7 @@
-var tabs = require('../utils/tabs');
 var base = require('./base');
 
-tabs.update(function (tab) {
-  tab.url && base.checkAuthUrl(tab.url) && tabs.remove(tab.id);
+browser.tabs.onUpdated.addListener(function (tabId, changes) {
+  changes.url && base.checkAuthUrl(changes.url) && browser.tabs.remove(tabId);
 });
 
 // import sync modules

+ 1 - 2
src/background/sync/onedrive.js

@@ -2,7 +2,6 @@
 
 var _ = require('src/common');
 var base = require('./base');
-var tabsUtils = require('../utils/tabs');
 var searchUtils = require('../utils/search');
 
 var config = Object.assign({
@@ -23,7 +22,7 @@ function authorize() {
   var url = 'https://login.live.com/oauth20_authorize.srf';
   var qs = searchUtils.dump(params);
   url += '?' + qs;
-  tabsUtils.create(url);
+  browser.tabs.create({url: url});
 }
 function checkAuth(url) {
   var redirect_uri = config.redirect_uri + '?code=';

+ 0 - 30
src/background/utils/tabs.js

@@ -1,30 +0,0 @@
-module.exports = {
-  create: function (url) {
-    chrome.tabs.create({url: url});
-  },
-  update: function (cb) {
-    chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, _tab) {
-      cb({
-        id: tabId,
-        url: changeInfo.url,
-      });
-    });
-  },
-  remove: function (id) {
-    chrome.tabs.remove(id);
-  },
-  get: function (id) {
-    return new Promise(function (resolve, _reject) {
-      chrome.tabs.get(id, function (tab) {
-        resolve(tab);
-      });
-    });
-  },
-  broadcast: function (data) {
-    chrome.tabs.query({}, function (tabs) {
-      tabs.forEach(function (tab) {
-        chrome.tabs.sendMessage(tab.id, data);
-      });
-    });
-  },
-};

+ 14 - 14
src/cache.js

@@ -1,17 +1,17 @@
-function Cache(allowOverride) {
-  this.data = {};
-  this.allowOverride = allowOverride;
+function getCache() {
+  function put(key, value) {
+    if (key in data) {
+      throw 'Key {' + key + '} already exists!';
+    }
+    data[key] = value;
+  }
+  function get(key) {
+    if (key in data) return data[key];
+    throw 'Cache not found: ' + key;
+  }
+  var data = {};
+  return {get: get, put: put};
 }
-Cache.prototype.put = function (key, fn) {
-  if (key in this.data && !this.allowOverride)
-    throw 'Key {' + key + '} already exists!';
-  this.data[key] = fn;
-};
-Cache.prototype.get = function (key) {
-  var data = this.data;
-  if (key in data) return data[key];
-  throw 'Cache not found: ' + key;
-};
 
 var _ = require('./common');
 Vue.prototype.i18n = _.i18n;
@@ -29,4 +29,4 @@ Vue.prototype.i18n = _.i18n;
 }();
 
 /* eslint-disable no-unused-vars */
-var cache = module.exports = new Cache();
+var cache = module.exports = getCache();

+ 9 - 5
src/common.js

@@ -37,8 +37,9 @@ polyfill(Array.prototype, 'find', function (predicate) {
 // Polyfill end
 
 var _ = exports;
+
 _.i18n = function (name, args) {
-  return chrome.i18n.getMessage(name, args) || name;
+  return browser.i18n.getMessage(name, args) || name;
 };
 _.defaultImage = '/images/icon128.png';
 
@@ -146,10 +147,13 @@ _.initOptions = function () {
 };
 
 _.sendMessage = function (data) {
-  return new Promise(function (resolve, reject) {
-    chrome.runtime.sendMessage(data, function (res) {
-      res && res.error ? reject(res.error) : resolve(res && res.data);
-    });
+  return browser.runtime.sendMessage(data)
+  .then(function (res) {
+    if (res && res.error) throw res.error;
+    return res && res.data;
+  })
+  .catch(function (err) {
+    console.error(err);
   });
 };
 

+ 13 - 8
src/injected.js

@@ -5,11 +5,15 @@ window.VM = 1;
 function getUniqId() {
   return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
 }
+function noop() {}
 function sendMessage(data) {
-  return new Promise(function (resolve, reject) {
-    chrome.runtime.sendMessage(data, function (res) {
-      res && res.error ? reject(res.error) : resolve(res && res.data);
-    });
+  return browser.runtime.sendMessage(data)
+  .then(function (res) {
+    if (res && res.error) throw res.error;
+    return res && res.data;
+  })
+  .catch(function (err) {
+    console.error(err);
   });
 }
 function includes(arr, item) {
@@ -65,7 +69,8 @@ function getPopup(){
       ids: ids,
       menus: menus,
     },
-  });
+  })
+  .catch(noop);
 }
 
 var badge = {
@@ -742,8 +747,8 @@ function onNotificationClose(nid) {
 }
 
 // Messages
-chrome.runtime.onMessage.addListener(function (req, src) {
-  var maps = {
+browser.runtime.onMessage.addListener(function (req, src) {
+  var handlers = {
     Command: function (data) {
       comm.post({cmd: 'Command', data: data});
     },
@@ -756,7 +761,7 @@ chrome.runtime.onMessage.addListener(function (req, src) {
     NotificationClick: onNotificationClick,
     NotificationClose: onNotificationClose,
   };
-  var func = maps[req.cmd];
+  var func = handlers[req.cmd];
   if (func) func(req.data, src);
 });
 

+ 1 - 0
src/manifest.json

@@ -29,6 +29,7 @@
   "content_scripts": [
     {
       "js": [
+        "mylib/browser.js",
         "injected.js"
       ],
       "matches": [

+ 1 - 1
src/options/app.js

@@ -102,7 +102,7 @@ var handlers = {
     _.options.update(data);
   },
 };
-chrome.runtime.onMessage.addListener(function (res) {
+browser.runtime.onMessage.addListener(function (res) {
   var handle = handlers[res.cmd];
   handle && handle(res.data);
 });

+ 1 - 0
src/options/index.html

@@ -8,6 +8,7 @@
   <script src="/lib/vue.min.js"></script>
   <script src="/lib/zip.js/zip.js"></script>
   <script src="/lib/define.js"></script>
+  <script src="/mylib/browser.js"></script>
   <script src="/common.js"></script>
   <script src="/cache.js"></script>
 </head>

+ 1 - 1
src/options/views/tab-about.js

@@ -1,6 +1,6 @@
 var cache = require('../../cache');
 var data = {
-  version: chrome.runtime.getManifest().version,
+  version: browser.runtime.getManifest().version,
   language: navigator.language,
 };
 

+ 2 - 2
src/options/views/tab-installed.js

@@ -42,8 +42,8 @@ module.exports = {
     installFromURL: function () {
       var url = prompt(_.i18n('hintInputURL'));
       if (url && ~url.indexOf('://')) {
-        chrome.tabs.create({
-          url: chrome.extension.getURL('/options/index.html') + '#confirm/' + encodeURIComponent(url),
+        browser.tabs.create({
+          url: browser.extension.getURL('/options/index.html') + '#confirm/' + encodeURIComponent(url),
         });
       }
     },

+ 4 - 3
src/popup/app.js

@@ -12,7 +12,7 @@ var app = new Vue({
 
 function init() {
   var currentTab = utils.store.currentTab;
-  chrome.tabs.sendMessage(currentTab.id, {cmd: 'GetPopup'});
+  browser.tabs.sendMessage(currentTab.id, {cmd: 'GetPopup'});
   if (currentTab && /^https?:\/\//i.test(currentTab.url)) {
     var matches = currentTab.url.match(/:\/\/(?:www\.)?([^\/]*)/);
     var domain = matches[1];
@@ -42,12 +42,13 @@ var handlers = {
     _.options.update(data);
   },
 };
-chrome.runtime.onMessage.addListener(function (req, src, callback) {
+browser.runtime.onMessage.addListener(function (req, src, callback) {
   var func = handlers[req.cmd];
   if (func) func(req.data, src, callback);
 });
 
-chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {
+browser.tabs.query({currentWindow: true, active: true})
+.then(function (tabs) {
   utils.store.currentTab = {
     id: tabs[0].id,
     url: tabs[0].url,

+ 1 - 0
src/popup/index.html

@@ -6,6 +6,7 @@
   <link rel="stylesheet" href="style.css">
   <script src="/lib/vue.min.js"></script>
   <script src="/lib/define.js"></script>
+  <script src="/mylib/browser.js"></script>
   <script src="/common.js"></script>
   <script src="/cache.js"></script>
 </head>

+ 9 - 8
src/popup/views/app/index.js

@@ -66,17 +66,18 @@ module.exports = {
       _.options.set('isApplied', !this.options.isApplied);
     },
     onManage: function () {
-      var url = chrome.extension.getURL(chrome.runtime.getManifest().options_page);
-      chrome.tabs.query({
+      var url = browser.extension.getURL(browser.runtime.getManifest().options_page);
+      browser.tabs.query({
         currentWindow: true,
         url: url,
-      }, function (tabs) {
+      })
+      .then(function (tabs) {
         var tab = tabs.find(function (tab) {
           var hash = tab.url.match(/#(\w+)/);
           return !hash || hash[1] !== 'confirm';
         });
-        if (tab) chrome.tabs.update(tab.id, {active: true});
-        else chrome.tabs.create({url: url});
+        if (tab) browser.tabs.update(tab.id, {active: true});
+        else browser.tabs.create({url: url});
       });
     },
     onFindScripts: function (item) {
@@ -87,12 +88,12 @@ module.exports = {
         var matches = this.store.currentTab.url.match(/:\/\/(?:www\.)?([^\/]*)/);
         domain = matches[1];
       }
-      chrome.tabs.create({
+      browser.tabs.create({
         url: 'https://greasyfork.org/scripts/search?q=' + encodeURIComponent(domain),
       });
     },
     onCommand: function (item) {
-      chrome.tabs.sendMessage(this.store.currentTab.id, {
+      browser.tabs.sendMessage(this.store.currentTab.id, {
         cmd: 'Command',
         data: item.name,
       });
@@ -108,7 +109,7 @@ module.exports = {
       })
       .then(function () {
         item.data.enabled = !item.data.enabled;
-        _.options.get('autoReload') && chrome.tabs.reload(_this.store.currentTab.id);
+        _.options.get('autoReload') && browser.tabs.reload(_this.store.currentTab.id);
       });
     },
   },

+ 89 - 0
src/public/mylib/browser.js

@@ -0,0 +1,89 @@
+/* global chrome */
+!function (win) {
+  function wrapAsync(func) {
+    return function () {
+      var args = [];
+      for (var i = 0; i < arguments.length; i ++) args.push(arguments[i]);
+      return new Promise(function (resolve, reject) {
+        args.push(function (res) {
+          var err = chrome.runtime.lastError;
+          if (err) {
+            console.error(args);
+            reject(err);
+          } else {
+            resolve(res);
+          }
+        });
+        func.apply(null, args);
+      });
+    };
+  }
+  function wrapAPIs(source, meta) {
+    var target = {};
+    Object.keys(source).forEach(function (key) {
+      var metaVal = meta && meta[key];
+      if (metaVal) {
+        var value = source[key];
+        if (typeof metaVal === 'function') {
+          target[key] = metaVal(value);
+        } else if (typeof metaVal === 'object' && typeof value === 'object') {
+          target[key] = wrapAPIs(value, metaVal);
+        } else {
+          target[key] = value;
+        }
+      }
+    });
+    return target;
+  }
+  var meta = {
+    browserAction: true,
+    extension: true,
+    i18n: true,
+    notifications: {
+      onClicked: true,
+      onClosed: true,
+      create: wrapAsync,
+    },
+    runtime: {
+      getManifest: true,
+      onMessage: function (onMessage) {
+        function wrapListener(listener) {
+          return function onMessage(message, sender, sendResponse) {
+            var result = listener(message, sender);
+            if (result && typeof result.then === 'function') {
+              result.then(function (data) {
+                sendResponse({data: data});
+              }, function (err) {
+                console.error(err);
+                sendResponse({error: err});
+              });
+              return true;
+            } else {
+              sendResponse({data: result});
+            }
+          };
+        }
+        return {
+          addListener: function (listener) {
+            return onMessage.addListener(wrapListener(listener));
+          },
+        };
+      },
+      sendMessage: wrapAsync,
+    },
+    tabs: {
+      onUpdated: true,
+      create: wrapAsync,
+      get: wrapAsync,
+      query: wrapAsync,
+      reload: wrapAsync,
+      remove: wrapAsync,
+      sendMessage: wrapAsync,
+      update: wrapAsync,
+    },
+    webRequest: true,
+  };
+  if (typeof browser === 'undefined' && typeof chrome !== 'undefined') {
+    win.browser = wrapAPIs(chrome, meta);
+  }
+}(this);