Explorar o código

refactor injected.js with ES6

Gerald %!s(int64=8) %!d(string=hai) anos
pai
achega
c718b2dab1
Modificáronse 7 ficheiros con 701 adicións e 683 borrados
  1. 7 6
      .gitignore
  2. 7 6
      package.json
  3. 58 25
      scripts/webpack.conf.js
  4. 6 11
      src/.babelrc
  5. 1 1
      src/common/index.js
  6. 620 632
      src/injected.js
  7. 2 2
      src/popup/index.html

+ 7 - 6
.gitignore

@@ -1,7 +1,8 @@
-dist/
+/dist/
 node_modules/
-*.zip
-*.nex
-*.crx
-*.log
-*.lock
+/*.zip
+/*.nex
+/*.crx
+/*.log
+/*.lock
+/*.json

+ 7 - 6
package.json

@@ -2,9 +2,9 @@
   "name": "violentmonkey",
   "version": "2.5.9",
   "scripts": {
-    "prebuild": "gulp clean",
-    "build": "gulp build",
-    "dev": "webpack --config scripts/webpack.conf.js",
+    "build": "webpack --config scripts/webpack.conf.js",
+    "analyze": "webpack --profile --json --config scripts/webpack.conf.js | webpack-bundle-size-analyzer",
+    "analyze:json": "webpack --profile --json --config scripts/webpack.conf.js > stats.json",
     "i18n": "gulp i18n",
     "lint": "gulp lint",
     "update": "node scripts/update",
@@ -17,8 +17,7 @@
     "babel-eslint": "^7.2.1",
     "babel-loader": "^6.4.1",
     "babel-plugin-transform-runtime": "^6.23.0",
-    "babel-preset-latest": "^6.24.0",
-    "babel-preset-stage-2": "^6.22.0",
+    "babel-preset-env": "^1.2.2",
     "css-loader": "^0.27.3",
     "cssnano": "^3.10.0",
     "del": "^2.2.0",
@@ -41,6 +40,7 @@
     "gulp-uglify": "^2.0.0",
     "gulp-util": "^3.0.7",
     "html-minifier": "^3.2.3",
+    "html-webpack-plugin": "^2.28.0",
     "js-yaml": "^3.5.5",
     "merge2": "^1.0.2",
     "mocha": "^3.1.2",
@@ -51,7 +51,8 @@
     "vue-loader": "^11.3.3",
     "vue-style-loader": "^2.0.4",
     "vue-template-compiler": "^2.2.5",
-    "webpack": "^2.3.2"
+    "webpack": "^2.3.2",
+    "webpack-bundle-size-analyzer": "^2.5.0"
   },
   "author": "Gerald <[email protected]>",
   "repository": {

+ 58 - 25
scripts/webpack.conf.js

@@ -2,21 +2,17 @@ const path = require('path');
 const webpack = require('webpack');
 const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
 const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
 const utils = require('./utils');
 const vueLoaderConfig = require('./vue-loader.conf');
-const IS_DEV = process.env.NODE_ENV === 'development';
+const IS_DEV = process.env.NODE_ENV !== 'production';
 const DIST = 'dist';
 
 function resolve (dir) {
   return path.join(__dirname, '..', dir)
 }
 
-module.exports = {
-  entry: {
-    'background/app': './src/background/app.js',
-    'options/app': './src/options/app.js',
-    'popup/app': './src/popup/app.js',
-  },
+const base = {
   output: {
     path: path.resolve(DIST),
     publicPath: '/',
@@ -30,15 +26,15 @@ module.exports = {
   },
   module: {
     rules: [
-      {
-        test: /\.(js|vue)$/,
-        loader: 'eslint-loader',
-        enforce: 'pre',
-        include: [resolve('src'), resolve('test')],
-        options: {
-          formatter: require('eslint-friendly-formatter')
-        }
-      },
+      // {
+      //   test: /\.(js|vue)$/,
+      //   loader: 'eslint-loader',
+      //   enforce: 'pre',
+      //   include: [resolve('src'), resolve('test')],
+      //   options: {
+      //     formatter: require('eslint-friendly-formatter')
+      //   }
+      // },
       {
         test: /\.vue$/,
         loader: 'vue-loader',
@@ -56,6 +52,16 @@ module.exports = {
   },
   // cheap-module-eval-source-map is faster for development
   devtool: IS_DEV ? '#cheap-module-eval-source-map' : false,
+};
+
+const targets = module.exports = [];
+
+targets.push(Object.assign({}, base, {
+  entry: {
+    'background/app': 'src/background/app.js',
+    'options/app': 'src/options/app.js',
+    'popup/app': 'src/popup/app.js',
+  },
   plugins: [
     new webpack.DefinePlugin({
       'process.env': {},
@@ -64,15 +70,29 @@ module.exports = {
     new webpack.optimize.CommonsChunkPlugin({
       name: 'vendor',
     }),
-    new FriendlyErrorsPlugin(),
+    new HtmlWebpackPlugin({
+      filename: 'background/index.html',
+      template: 'src/background/index.html',
+      inject: true,
+      chunks: ['vendor', 'background/app'],
+      chunksSortMode: 'dependency'
+    }),
+    new HtmlWebpackPlugin({
+      filename: 'options/index.html',
+      template: 'src/options/index.html',
+      inject: true,
+      chunks: ['vendor', 'options/app'],
+      chunksSortMode: 'dependency'
+    }),
+    new HtmlWebpackPlugin({
+      filename: 'popup/index.html',
+      template: 'src/popup/index.html',
+      inject: true,
+      chunks: ['vendor', 'popup/app'],
+      chunksSortMode: 'dependency'
+    }),
+    // new FriendlyErrorsPlugin(),
     ... IS_DEV ? [
-      // https://github.com/ampedandwired/html-webpack-plugin
-      // new HtmlWebpackPlugin({
-      //   filename: 'index.html',
-      //   template: 'src/public/index.ejs',
-      //   inject: true,
-      //   config,
-      // })
     ] : [
       // extract css into its own file
       new ExtractTextPlugin('[name].css'),
@@ -100,4 +120,17 @@ module.exports = {
       }),
     ],
   ],
-};
+}));
+
+targets.push(Object.assign({}, base, {
+  entry: {
+    injected: 'src/injected.js',
+  },
+  plugins: IS_DEV ? [] : [
+    new webpack.optimize.UglifyJsPlugin({
+      compress: {
+        warnings: false
+      }
+    }),
+  ],
+}));

+ 6 - 11
src/.babelrc

@@ -1,16 +1,11 @@
 {
   "presets": [
-    ["latest", {
-      "es2015": { "modules": false }
-    }],
-    "stage-2"
+    ["env", {
+      "targets": {
+        "browsers": ["last 2 versions"]
+      },
+    }]
   ],
   "plugins": ["transform-runtime"],
-  "comments": false,
-  "env": {
-    "test": {
-      "presets": ["latest", "stage-2"],
-      "plugins": [ "istanbul" ]
-    }
-  }
+  "comments": false
 }

+ 1 - 1
src/common/index.js

@@ -1,4 +1,4 @@
-import './polyfills';
+// import './polyfills';
 
 export function i18n(name, args) {
   return browser.i18n.getMessage(name, args) || name;

+ 620 - 632
src/injected.js

@@ -1,300 +1,273 @@
-// Avoid running repeatedly due to new `document.documentElement`
-if (window.VM) return;
-window.VM = 1;
+(function injectContent() {
+  // Avoid running repeatedly due to new `document.documentElement`
+  if (window.VM) return;
+  window.VM = 1;
 
-function getUniqId() {
-  return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
-}
-function noop() {}
-function includes(arr, item) {
-  for (var i = arr.length; i --;) {
-    if (arr[i] === item) return true;
+  function getUniqId() {
+    return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
   }
-  return false;
-}
-function forEach(arr, func) {
-  var length = arr && arr.length || 0;
-  for (var i = 0; i < length; i ++) func(arr[i], i, arr);
-}
-function map(arr, func) {
-  var comm = this;
-  var res = [];
-  comm.forEach(arr, function (item, i) {
-    res.push(func(item, i, arr));
-  });
-  return res;
-}
 
-/**
- * http://www.webtoolkit.info/javascript-utf8.html
- */
-function utf8decode(utftext) {
-  var string = '';
-  var i = 0;
-  var c = 0, c2 = 0, c3 = 0;
-  while ( i < utftext.length ) {
-    c = utftext.charCodeAt(i);
-    if (c < 128) {
-      string += String.fromCharCode(c);
-      i ++;
-    } else if (c > 191 && c < 224) {
-      c2 = utftext.charCodeAt(i + 1);
-      string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
-      i += 2;
-    } else {
-      c2 = utftext.charCodeAt(i + 1);
-      c3 = utftext.charCodeAt(i + 2);
-      string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
-      i += 3;
+  /**
+  * http://www.webtoolkit.info/javascript-utf8.html
+  */
+  function utf8decode(utftext) {
+    /* eslint-disable no-bitwise */
+    let string = '';
+    let i = 0;
+    let c1 = 0;
+    let c2 = 0;
+    let c3 = 0;
+    while (i < utftext.length) {
+      c1 = utftext.charCodeAt(i);
+      if (c1 < 128) {
+        string += String.fromCharCode(c1);
+        i += 1;
+      } else if (c1 > 191 && c1 < 224) {
+        c2 = utftext.charCodeAt(i + 1);
+        string += String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
+        i += 2;
+      } else {
+        c2 = utftext.charCodeAt(i + 1);
+        c3 = utftext.charCodeAt(i + 2);
+        string += String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+        i += 3;
+      }
     }
+    return string;
+    /* eslint-enable no-bitwise */
   }
-  return string;
-}
-
-function sendMessage(data) {
-  return browser.runtime.sendMessage(data);
-}
-function getPopup(){
-  // XXX: only scripts run in top level window are counted
-  top === window && sendMessage({
-    cmd: 'SetPopup',
-    data: {
-      ids: ids,
-      menus: menus,
-    },
-  })
-  .catch(noop);
-}
 
-var badge = {
-  number: 0,
-  ready: false,
-  willSet: false,
-};
-function getBadge() {
-  badge.willSet = true;
-  setBadge();
-}
-function setBadge() {
-  if (badge.ready && badge.willSet) {
-    // XXX: only scripts run in top level window are counted
-    top === window && sendMessage({cmd: 'SetBadge', data: badge.number});
+  function sendMessage(data) {
+    return browser.runtime.sendMessage(data);
   }
-}
-
-/**
- * @desc Wrap methods to prevent unexpected modifications.
- */
-function getWrapper() {
-  // http://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects
-  // http://developer.mozilla.org/docs/Web/API/Window
-  var comm = this;
-  var wrapper = {};
-  // `eval` should be called directly so that it is run in current scope
-  wrapper.eval = eval;
-  // Wrap methods
-  comm.forEach([
-    // 'uneval',
-    'isFinite',
-    'isNaN',
-    'parseFloat',
-    'parseInt',
-    'decodeURI',
-    'decodeURIComponent',
-    'encodeURI',
-    'encodeURIComponent',
 
-    'addEventListener',
-    'alert',
-    'atob',
-    'blur',
-    'btoa',
-    'clearInterval',
-    'clearTimeout',
-    'close',
-    'confirm',
-    'dispatchEvent',
-    'fetch',
-    'find',
-    'focus',
-    'getComputedStyle',
-    'getSelection',
-    'matchMedia',
-    'moveBy',
-    'moveTo',
-    'open',
-    'openDialog',
-    'postMessage',
-    'print',
-    'prompt',
-    'removeEventListener',
-    'requestAnimationFrame',
-    'resizeBy',
-    'resizeTo',
-    'scroll',
-    'scrollBy',
-    'scrollByLines',
-    'scrollByPages',
-    'scrollTo',
-    'setInterval',
-    'setTimeout',
-    'stop',
-  ], function (name) {
-    var method = window[name];
-    if (method) wrapper[name] = function () {
-      return method.apply(window, arguments);
-    };
-  });
-  function defineProtectedProperty(name) {
-    var modified = false;
-    var value;
-    Object.defineProperty(wrapper, name, {
-      get: function () {
-        if (!modified) value = window[name];
-        return value === window ? wrapper : value;
-      },
-      set: function (val) {
-        modified = true;
-        value = val;
-      },
-    });
-  }
-  function defineReactedProperty(name) {
-    Object.defineProperty(wrapper, name, {
-      get: function () {
-        var value = window[name];
-        return value === window ? wrapper : value;
-      },
-      set: function (val) {
-        window[name] = val;
-      },
-    });
+  const badge = {
+    number: 0,
+    ready: false,
+    willSet: false,
+  };
+  function getBadge() {
+    badge.willSet = true;
+    setBadge();
   }
-  // Wrap properties
-  comm.forEach(comm.props, function (name) {
-    if (wrapper[name]) return;
-    if (name.slice(0, 2) === 'on') defineReactedProperty(name);
-    else defineProtectedProperty(name);
-  });
-  return wrapper;
-}
-// Communicator
-var comm = {
-  vmid: 'VM_' + getUniqId(),
-  state: 0,
-  utf8decode: utf8decode,
-  getUniqId: getUniqId,
-  props: Object.getOwnPropertyNames(window),
-
-  // Array functions
-  // Notice: avoid using prototype functions since they may be changed by page scripts
-  includes: includes,
-  forEach: forEach,
-  map: map,
-
-  init: function (srcId, destId) {
-    var comm = this;
-    comm.sid = comm.vmid + srcId;
-    comm.did = comm.vmid + destId;
-    var handle = comm['handle' + srcId];
-    document.addEventListener(comm.sid, function (e) {
-      var data = JSON.parse(e.detail);
-      handle.call(comm, data);
-    }, false);
-    comm.load = comm.checkLoad = function () {};
-    // check whether the page is injectable via <script>, whether limited by CSP
-    try {
-      comm.injectable = (0, eval)('true');
-    } catch (e) {
-      comm.injectable = false;
+  function setBadge() {
+    if (badge.ready && badge.willSet) {
+      // XXX: only scripts run in top level window are counted
+      if (top === window) sendMessage({ cmd: 'SetBadge', data: badge.number });
     }
-  },
-  post: function (data) {
-    // Firefox issue: data must be stringified to avoid cross-origin problem
-    var e = new CustomEvent(this.did, {detail: JSON.stringify(data)});
-    document.dispatchEvent(e);
-  },
-  handleR: function (obj) {
-    var comm = this;
-    var maps = {
-      LoadScript: comm.loadScript.bind(comm),
-      Command: function (data) {
-        var func = comm.command[data];
-        func && func();
+  }
+  function handleWeb(obj) {
+    const comm = this;
+    const handlers = {
+      LoadScripts(data) {
+        comm.onLoadScripts(data);
+      },
+      Command(data) {
+        const func = comm.command[data];
+        if (func) func();
       },
-      GotRequestId: function (id) {
-        comm.qrequests.shift().start(id);
+      GotRequestId(id) {
+        const req = comm.qrequests.shift();
+        req.start(req, id);
       },
-      HttpRequested: function (r) {
-        var req = comm.requests[r.id];
-        if (req) req.callback(r);
+      HttpRequested(res) {
+        const req = comm.requests[res.id];
+        if (req) req.callback(req, res);
       },
-      UpdateValues: function (data) {
-        var values = comm.values;
+      UpdateValues(data) {
+        const { values } = comm.values;
         if (values && values[data.uri]) values[data.uri] = data.values;
       },
-      NotificationClicked: function (id) {
-        var options = comm.notif[id];
+      NotificationClicked(id) {
+        const options = comm.notif[id];
         if (options) {
-          var onclick = options.onclick;
-          onclick && onclick();
+          const { onclick } = options;
+          if (onclick) onclick();
         }
       },
-      NotificationClosed: function (id) {
-        var options = comm.notif[id];
+      NotificationClosed(id) {
+        const options = comm.notif[id];
         if (options) {
           delete comm.notif[id];
-          var ondone = options.ondone;
-          ondone && ondone();
+          const { ondone } = options;
+          if (ondone) ondone();
         }
       },
       // advanced inject
-      Injected: function (id) {
-        var obj = comm.ainject[id];
-        var func = window['VM_' + id];
-        delete window['VM_' + id];
+      Injected(id) {
+        const item = comm.ainject[id];
+        const func = window[`VM_${id}`];
+        delete window[`VM_${id}`];
         delete comm.ainject[id];
-        if (obj && func) comm.runCode(obj[0], func, obj[1], obj[2]);
+        if (item && func) comm.runCode(item[0], func, item[1], item[2]);
       },
     };
-    var func = maps[obj.cmd];
-    if (func) func(obj.data);
-  },
-  runCode: function (name, func, args, thisObj) {
-    try {
-      func.apply(thisObj, args);
-    } catch (e) {
-      var msg = 'Error running script: ' + name + '\n' + e;
-      if (e.message) msg += '\n' + e.message;
-      console.error(msg);
+    const handle = handlers[obj.cmd];
+    if (handle) handle(obj.data);
+  }
+
+  /**
+  * @desc Wrap methods to prevent unexpected modifications.
+  */
+  function getWrapper() {
+    // http://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects
+    // http://developer.mozilla.org/docs/Web/API/Window
+    const comm = this;
+    const wrapper = {};
+    comm.forEach([
+      // `eval` should be called directly so that it is run in current scope
+      'eval',
+    ], name => {
+      wrapper[name] = window[name];
+    });
+    comm.forEach([
+      // 'uneval',
+      'isFinite',
+      'isNaN',
+      'parseFloat',
+      'parseInt',
+      'decodeURI',
+      'decodeURIComponent',
+      'encodeURI',
+      'encodeURIComponent',
+
+      'addEventListener',
+      'alert',
+      'atob',
+      'blur',
+      'btoa',
+      'clearInterval',
+      'clearTimeout',
+      'close',
+      'confirm',
+      'dispatchEvent',
+      'fetch',
+      'find',
+      'focus',
+      'getComputedStyle',
+      'getSelection',
+      'matchMedia',
+      'moveBy',
+      'moveTo',
+      'open',
+      'openDialog',
+      'postMessage',
+      'print',
+      'prompt',
+      'removeEventListener',
+      'requestAnimationFrame',
+      'resizeBy',
+      'resizeTo',
+      'scroll',
+      'scrollBy',
+      'scrollByLines',
+      'scrollByPages',
+      'scrollTo',
+      'setInterval',
+      'setTimeout',
+      'stop',
+    ], name => {
+      const method = window[name];
+      if (method) {
+        wrapper[name] = (...args) => method.apply(window, args);
+      }
+    });
+    function defineProtectedProperty(name) {
+      let modified = false;
+      let value;
+      Object.defineProperty(wrapper, name, {
+        get() {
+          if (!modified) value = window[name];
+          return value === window ? wrapper : value;
+        },
+        set(val) {
+          modified = true;
+          value = val;
+        },
+      });
+    }
+    function defineReactedProperty(name) {
+      Object.defineProperty(wrapper, name, {
+        get() {
+          const value = window[name];
+          return value === window ? wrapper : value;
+        },
+        set(val) {
+          window[name] = val;
+        },
+      });
+    }
+    // Wrap properties
+    comm.forEach(comm.props, name => {
+      if (name in wrapper) return;
+      if (name.slice(0, 2) === 'on') defineReactedProperty(name);
+      else defineProtectedProperty(name);
+    });
+    return wrapper;
+  }
+  function initHandler(src, dest) {
+    const comm = this;
+    comm.sid = comm.vmid + src;
+    comm.did = comm.vmid + dest;
+    const handle = comm[`handle_${src}`];
+    document.addEventListener(comm.sid, e => {
+      const data = JSON.parse(e.detail);
+      handle.call(comm, data);
+    }, false);
+    comm.load = comm.noop;
+    comm.checkLoad = comm.noop;
+  }
+  function postData(data) {
+    const comm = this;
+    // Firefox issue: data must be stringified to avoid cross-origin problem
+    const e = new CustomEvent(comm.did, { detail: JSON.stringify(data) });
+    document.dispatchEvent(e);
+  }
+  function getRequest(arg) {
+    const comm = this;
+    init();
+    return comm.getRequest(arg);
+    function init() {
+      comm.requests = {};
+      comm.qrequests = [];
+      comm.getRequest = details => {
+        const req = {
+          details,
+          callback,
+          start,
+          req: {
+            abort: reqAbort,
+          },
+        };
+        details.url = getFullUrl(details.url);
+        comm.qrequests.push(req);
+        comm.post({ cmd: 'GetRequestId' });
+        return req.req;
+      };
     }
-  },
-  initRequest: function () {
-    // request functions
-    function reqAbort(){
-      comm.post({cmd: 'AbortRequest', data: this.id});
+    function reqAbort() {
+      comm.post({ cmd: 'AbortRequest', data: this.id });
     }
     function parseData(req, details) {
       if (req.resType) {
         // blob or arraybuffer
-        var data = req.data.response.split(',');
-        var mimetype = data[0].match(/^data:(.*?);base64$/);
+        let data = req.data.response.split(',');
+        const mimetype = data[0].match(/^data:(.*?);base64$/);
         if (!mimetype) {
           // invalid
           req.data.response = null;
         } else {
           data = window.atob(data[1]);
-          var arr = new window.Uint8Array(data.length);
-          for (var i = 0; i < data.length; i ++) arr[i] = data.charCodeAt(i);
-          if (details.responseType == 'blob') {
+          const arr = new window.Uint8Array(data.length);
+          for (let i = 0; i < data.length; i += 1) arr[i] = data.charCodeAt(i);
+          if (details.responseType === 'blob') {
             // blob
-            return new Blob([arr], {type: mimetype});
-          } else {
-            // arraybuffer
-            return arr.buffer;
+            return new Blob([arr], { type: mimetype });
           }
+          // arraybuffer
+          return arr.buffer;
         }
-      } else if (details.responseType == 'json') {
+      } else if (details.responseType === 'json') {
         // json
         return JSON.parse(req.data.response);
       } else {
@@ -303,106 +276,74 @@ var comm = {
       }
     }
     // request object functions
-    function callback(req) {
-      var t = this;
-      var cb = t.details['on' + req.type];
+    function callback(req, res) {
+      const cb = req.details[`on${res.type}`];
       if (cb) {
-        if (req.data.response) {
-          if (!t.data) t.data = [parseData(req, t.details)];
-          req.data.response = t.data[0];
+        if (res.data.response) {
+          if (!req.data) req.data = [parseData(res, req.details)];
+          res.data.response = req.data[0];
         }
-        cb(req.data);
+        cb(res.data);
       }
-      if (req.type == 'loadend') delete comm.requests[t.id];
+      if (res.type === 'loadend') delete comm.requests[req.id];
     }
-    function start(id) {
-      var t = this;
-      var data = {
-        id: id,
-        method: t.details.method,
-        url: t.details.url,
-        data: t.details.data,
-        //async: !t.details.synchronous,
-        user: t.details.user,
-        password: t.details.password,
-        headers: t.details.headers,
-        overrideMimeType: t.details.overrideMimeType,
+    function start(req, id) {
+      const data = {
+        id,
+        method: req.details.method,
+        url: req.details.url,
+        data: req.details.data,
+        // async: !req.details.synchronous,
+        user: req.details.user,
+        password: req.details.password,
+        headers: req.details.headers,
+        overrideMimeType: req.details.overrideMimeType,
       };
-      t.id = id;
-      comm.requests[id] = t;
-      if (comm.includes(['arraybuffer', 'blob'], t.details.responseType)) {
+      req.id = id;
+      comm.requests[id] = req;
+      if (comm.includes(['arraybuffer', 'blob'], req.details.responseType)) {
         data.responseType = 'blob';
       }
-      comm.post({cmd: 'HttpRequest', data: data});
+      comm.post({ cmd: 'HttpRequest', data });
     }
     function getFullUrl(url) {
-      var a = document.createElement('a');
+      const a = document.createElement('a');
       a.setAttribute('href', url);
       return a.href;
     }
-
-    var comm = this;
-    comm.requests = {};
-    comm.qrequests = [];
-    comm.Request = function (details) {
-      var t = {
-        details: details,
-        callback: callback,
-        start: start,
-        req: {
-          abort: reqAbort,
-        },
-      };
-      details.url = getFullUrl(details.url);
-      comm.qrequests.push(t);
-      comm.post({cmd: 'GetRequestId'});
-      return t.req;
-    };
-  },
-  getWrapper: getWrapper,
-  wrapGM: function (script, cache) {
-    function getValues() {
-      return comm.values[script.uri];
-    }
-    function propertyToString() {
-      return '[Violentmonkey property]';
-    }
-    function addProperty(name, prop, obj) {
-      if ('value' in prop) prop.writable = false;
-      prop.configurable = false;
-      Object.defineProperty(obj, name, prop);
-      if (typeof obj[name] == 'function') obj[name].toString = propertyToString;
-    }
-    function saveValues() {
-      comm.post({
-        cmd: 'SetValue',
-        data: {
-          uri: script.uri,
-          values: getValues(),
-        },
-      });
-    }
+  }
+  function wrapGM(script, cache) {
     // Add GM functions
     // Reference: http://wiki.greasespot.net/Greasemonkey_Manual:API
-    var comm = this;
-    var gm = {};
-    var grant = script.meta.grant || [];
-    var urls = {};
-    if (!grant.length || grant.length == 1 && grant[0] == 'none') {
+    const comm = this;
+    const gm = {};
+    const grant = script.meta.grant || [];
+    const urls = {};
+    if (!grant.length || (grant.length === 1 && grant[0] === 'none')) {
       // @grant none
       grant.pop();
     } else {
-      gm['window'] = comm.getWrapper();
+      gm.window = comm.getWrapper();
     }
     if (!comm.includes(grant, 'unsafeWindow')) grant.push('unsafeWindow');
     if (!comm.includes(grant, 'GM_info')) grant.push('GM_info');
-    var resources = script.meta.resources || {};
-    var gm_funcs = {
-      unsafeWindow: {value: window},
+    const resources = script.meta.resources || {};
+    const dataEncoders = {
+      o: val => JSON.stringify(val),
+      '': val => val.toString(),
+    };
+    const dataDecoders = {
+      n: val => Number(val),
+      b: val => val === 'true',
+      o: val => JSON.parse(val),
+      '': val => val,
+    };
+    const gmFunctions = {
+      unsafeWindow: { value: window },
       GM_info: {
-        get: function () {
-          var m = script.code.match(/\/\/\s+==UserScript==\s+([\s\S]*?)\/\/\s+==\/UserScript==\s/);
-          var data = {
+        get() {
+          const matches = script.code.match(/\/\/\s+==UserScript==\s+([\s\S]*?)\/\/\s+==\/UserScript==\s/);
+          const data = {
             description: script.meta.description || '',
             excludes: script.meta.exclude.concat(),
             includes: script.meta.include.concat(),
@@ -414,159 +355,146 @@ var comm = {
             unwrap: false,
             version: script.meta.version || '',
           };
-          var obj = {};
-          addProperty('scriptMetaStr', {value: m ? m[1] : ''}, obj);
+          const obj = {};
+          addProperty('scriptMetaStr', { value: matches ? matches[1] : '' }, obj);
 
           // whether update is allowed
-          addProperty('scriptWillUpdate', {value: !!script.update}, obj);
+          addProperty('scriptWillUpdate', { value: !!script.update }, obj);
 
           // Violentmonkey specific data
-          addProperty('version', {value: comm.version}, obj);
-          addProperty('scriptHandler', {value: 'Violentmonkey'}, obj);
+          addProperty('version', { value: comm.version }, obj);
+          addProperty('scriptHandler', { value: 'Violentmonkey' }, obj);
 
           // script object
-          addProperty('script', {value:{}}, obj);
-          var i;
-          for (i in data) addProperty(i, {value: data[i]}, obj.script);
-          for (i in script.meta.resources) {
-            addProperty(i, {value: script.meta.resources[i]}, obj.script.resources);
-          }
+          addProperty('script', { value: {} }, obj);
+          comm.forEach(Object.keys(resources), name => {
+            addProperty(name, { value: resources[name] }, data.resources);
+          });
+          comm.forEach(Object.keys(data), name => {
+            addProperty(name, { value: data[name] }, obj.script);
+          });
           return obj;
         },
       },
       GM_deleteValue: {
-        value: function (key) {
-          var values = getValues();
+        value(key) {
+          const values = getValues();
           delete values[key];
           saveValues();
         },
       },
       GM_getValue: {
-        value: function (key, val) {
-          var values = getValues();
-          var v = values[key];
-          if (v) {
-            var type = v[0];
-            v = v.slice(1);
-            switch (type) {
-            case 'n':
-              val = Number(v);
-              break;
-            case 'b':
-              val = v == 'true';
-              break;
-            case 'o':
-              try {
-                val = JSON.parse(v);
-              } catch (e) {
-                console.warn(e);
-              }
-              break;
-            default:
-              val = v;
+        value(key, def) {
+          const values = getValues();
+          const raw = values[key];
+          if (raw) {
+            const type = raw[0];
+            const handle = dataDecoders[type] || dataDecoders[''];
+            let value = raw.slice(1);
+            try {
+              value = handle(value);
+            } catch (e) {
+              console.warn(e);
             }
+            return value;
           }
-          return val;
+          return def;
         },
       },
       GM_listValues: {
-        value: function () {
-          return Object.getOwnPropertyNames(getValues());
+        value() {
+          return Object.keys(getValues());
         },
       },
       GM_setValue: {
-        value: function (key, val) {
-          var type = (typeof val)[0];
-          switch (type) {
-          case 'o':
-            val = type + JSON.stringify(val);
-            break;
-          default:
-            val = type + val;
-          }
-          var values = getValues();
-          values[key] = val;
+        value(key, val) {
+          const type = (typeof val)[0];
+          const handle = dataEncoders[type] || dataEncoders[''];
+          let value = val;
+          value = type + handle(value);
+          const values = getValues();
+          values[key] = value;
           saveValues();
         },
       },
       GM_getResourceText: {
-        value: function (name) {
-          for (var i in resources) if (name == i) {
-            var text = cache[resources[i]];
-            if (text) text = comm.utf8decode(window.atob(text));
+        value(name) {
+          if (name in resources) {
+            const uri = resources[name];
+            const raw = cache[uri];
+            const text = raw && comm.utf8decode(window.atob(raw));
             return text;
           }
         },
       },
       GM_getResourceURL: {
-        value: function (name) {
-          for (var k in resources) if (name == k) {
-            var key = resources[k];
-            var url = urls[key];
-            if (!url) {
-              var cc = cache[key];
-              if (cc) {
+        value(name) {
+          if (name in resources) {
+            const key = resources[name];
+            let blobUrl = urls[key];
+            if (!blobUrl) {
+              const raw = cache[key];
+              if (raw) {
                 // Binary string is not supported by blob constructor,
                 // so we have to transform it into array buffer.
-                var bin = window.atob(cc);
-                var arr = new window.Uint8Array(bin.length);
-                for (var i = 0; i < bin.length; i ++) {
-                  arr[i] = bin.charCodeAt(i);
-                }
-                var b = new Blob([arr]);
-                urls[key] = url = URL.createObjectURL(b);
+                const bin = window.atob(raw);
+                const arr = new window.Uint8Array(bin.length);
+                for (let i = 0; i < bin.length; i += 1) arr[i] = bin.charCodeAt(i);
+                const blob = new Blob([arr]);
+                blobUrl = URL.createObjectURL(blob);
+                urls[key] = blobUrl;
               } else {
-                url = key;
+                blobUrl = key;
               }
             }
-            return url;
+            return blobUrl;
           }
-        }
+        },
       },
       GM_addStyle: {
-        value: function (css) {
-          comm.post({cmd: 'AddStyle', data: css});
+        value(data) {
+          comm.post({ cmd: 'AddStyle', data });
         },
       },
       GM_log: {
-        value: function (data) {
-          console.log(data);  // eslint-disable-line no-console
+        value(data) {
+          console.log(`[Violentmonkey][${script.meta.name || 'No name'}]`, data);  // eslint-disable-line no-console
         },
       },
       GM_openInTab: {
-        value: function (url, background) {
-          comm.post({cmd: 'OpenTab', data: {url: url, active: !background}});
+        value(url, background) {
+          comm.post({ cmd: 'OpenTab', data: { url, active: !background } });
         },
       },
       GM_registerMenuCommand: {
-        value: function (cap, func, acc) {
+        value(cap, func, acc) {
           comm.command[cap] = func;
-          comm.post({cmd: 'RegisterMenu', data: [cap, acc]});
+          comm.post({ cmd: 'RegisterMenu', data: [cap, acc] });
         },
       },
       GM_xmlhttpRequest: {
-        value: function (details) {
-          if (!comm.Request) comm.initRequest();
-          return comm.Request(details);
+        value(details) {
+          return comm.getRequest(details);
         },
       },
       GM_notification: {
-        value: function (text, title, image, onclick) {
-          if (!text) {
-            throw 'Invalid parameters.';
-          }
-          var options = typeof text === 'object' ? text : {
-            text: text,
-            title: title,
-            image: image,
-            onclick: onclick,
+        value(text, title, image, onclick) {
+          const options = typeof text === 'object' ? text : {
+            text,
+            title,
+            image,
+            onclick,
           };
-          var id = comm.notif[''] = (comm.notif[''] || 0) + 1;
+          if (!options.text) {
+            throw new Error('GM_notification: `text` is required!');
+          }
+          const id = (comm.notif[''] || 0) + 1;
+          comm.notif[''] = id;
           comm.notif[id] = options;
           comm.post({
             cmd: 'Notification',
             data: {
-              id: id,
+              id,
               text: options.text,
               title: options.title,
               image: options.image,
@@ -575,255 +503,315 @@ var comm = {
         },
       },
       GM_setClipboard: {
-        value: function (text, type) {
+        value(data, type) {
           comm.post({
             cmd: 'SetClipboard',
-            data: {
-              type: type,
-              data: text,
-            },
+            data: { type, data },
           });
         },
       },
     };
-    comm.forEach(grant, function (name) {
-      var prop = gm_funcs[name];
-      prop && addProperty(name, prop, gm);
+    comm.forEach(grant, name => {
+      const prop = gmFunctions[name];
+      if (prop) addProperty(name, prop, gm);
     });
     return gm;
-  },
-  loadScript: function (data) {
-    function buildCode(script) {
-      var require = script.meta.require || [];
-      var wrapper = comm.wrapGM(script, data.cache);
-      // Must use Object.getOwnPropertyNames to list unenumerable properties
-      var wrapperKeys = Object.getOwnPropertyNames(wrapper);
-      var code = [
-        comm.map(wrapperKeys, function (name) {
-          return 'this["' + name + '"]=' + name;
-        }).join(';') + ';with(this)!function(){',
-      ];
-      comm.forEach(require, function (key) {
-        var script = data.require[key];
-        if (script) {
-          code.push(script);
-          // Add `;` to a new line in case script ends with comment lines
-          code.push(';');
-        }
-      });
-      // wrap code to make 'use strict' work
-      code.push('!function(){' + script.code + '\n}.call(this)');
-      code.push('}.call(this);');
-      code = code.join('\n');
-      var name = script.custom.name || script.meta.name || script.id;
-      var args = comm.map(wrapperKeys, function (key) {return wrapper[key];});
-      var thisObj = wrapper.window || wrapper;
-      if (comm.injectable) {
-        // normal injection
-        try {
-          var func = Function.apply(null, wrapperKeys.concat([code]));
-        } catch (e) {
-          console.error('Syntax error in script: ' + name + '\n' + e.message);
-          return;
-        }
-        comm.runCode(name, func, args, thisObj);
-      } else {
-        // advanced injection
-        var id = comm.getUniqId();
-        comm.ainject[id] = [name, args, thisObj];
-        comm.post({cmd: 'Inject', data: [id, wrapperKeys, code]});
-      }
+    function getValues() {
+      return comm.values[script.uri];
     }
-    function run(list) {
-      while (list.length) buildCode(list.shift());
+    function propertyToString() {
+      return '[Violentmonkey property]';
+    }
+    function addProperty(name, prop, obj) {
+      if ('value' in prop) prop.writable = false;
+      prop.configurable = false;
+      Object.defineProperty(obj, name, prop);
+      if (typeof obj[name] === 'function') obj[name].toString = propertyToString;
+    }
+    function saveValues() {
+      comm.post({
+        cmd: 'SetValue',
+        data: {
+          uri: script.uri,
+          values: getValues(),
+        },
+      });
     }
-    var comm = this;
-    var start = [];
-    var idle = [];
-    var end = [];
+  }
+  function onLoadScripts(data) {
+    const comm = this;
+    const start = [];
+    const idle = [];
+    const end = [];
     comm.command = {};
     comm.notif = {};
     comm.ainject = {};
     comm.version = data.version;
     comm.values = {};
     // reset load and checkLoad
-    comm.load = function () {
+    comm.load = () => {
       run(end);
       setTimeout(run, 0, idle);
     };
-    comm.checkLoad = function () {
-      if (!comm.state && comm.includes(['interactive', 'complete'], document.readyState))
-        comm.state = 1;
+    comm.checkLoad = () => {
+      if (!comm.state && comm.includes(['interactive', 'complete'], document.readyState)) comm.state = 1;
       if (comm.state) comm.load();
     };
-    var listMap = {
+    const listMap = {
       'document-start': start,
       'document-idle': idle,
       'document-end': end,
     };
-    comm.forEach(data.scripts, function (script) {
+    comm.forEach(data.scripts, script => {
       comm.values[script.uri] = data.values[script.uri] || {};
       if (script && script.enabled) {
-        var list = listMap[script.custom['run-at'] || script.meta['run-at']] || end;
+        const list = listMap[script.custom['run-at'] || script.meta['run-at']] || end;
         list.push(script);
       }
     });
     run(start);
     comm.checkLoad();
-  },
-};
-
-var menus = [];
-var ids = [];
-function injectScript(data) {
-  // data: [id, wrapperKeys, code]
-  var func = function (id, did, cb) {
-    Object.defineProperty(window, 'VM_' + id, {
-      value: cb,
-      configurable: true,
-    });
-    var e = new CustomEvent(did, {detail: {cmd: 'Injected', data: id}});
-    document.dispatchEvent(e);
-  };
-  inject('!' + func.toString() + '(' + JSON.stringify(data[0]) + ',' + JSON.stringify(comm.did) + ',function(' + data[1].join(',') + '){' + data[2] + '})');
-}
-function handleC(req) {
-  if (!req) {
-    console.error('[Violentmonkey] Invalid data! There might be unsupported data format.');
-    return;
+    function buildCode(script) {
+      const requireKeys = script.meta.require || [];
+      const wrapper = comm.wrapGM(script, data.cache);
+      // Must use Object.getOwnPropertyNames to list unenumerable properties
+      const wrapperKeys = Object.getOwnPropertyNames(wrapper);
+      const wrapperInit = comm.map(wrapperKeys, name => `this["${name}"]=${name}`).join(';');
+      const codeSlices = [`${wrapperInit};with(this)!function(){`];
+      comm.forEach(requireKeys, key => {
+        const requireCode = data.require[key];
+        if (requireCode) {
+          codeSlices.push(requireCode);
+          // Add `;` to a new line in case script ends with comment lines
+          codeSlices.push(';');
+        }
+      });
+      // wrap code to make 'use strict' work
+      codeSlices.push(`!function(){${script.code$}\n}.call(this)`);
+      codeSlices.push('}.call(this);');
+      const code = codeSlices.join('\n');
+      const name = script.custom.name || script.meta.name || script.id;
+      const args = comm.map(wrapperKeys, key => wrapper[key]);
+      const thisObj = wrapper.window || wrapper;
+      const id = comm.getUniqId();
+      comm.ainject[id] = [name, args, thisObj];
+      comm.post({ cmd: 'Inject', data: [id, wrapperKeys, code] });
+    }
+    function run(list) {
+      while (list.length) buildCode(list.shift());
+    }
   }
-  var maps = {
-    GetRequestId: getRequestId,
-    HttpRequest: httpRequest,
-    AbortRequest: abortRequest,
-    Inject: injectScript,
-    OpenTab: function (data) {
-      sendMessage({cmd: 'OpenTab', data: data});
+  // Communicator
+  const comm = {
+    vmid: `VM_${getUniqId()}`,
+    state: 0,
+    utf8decode,
+    getUniqId,
+    props: Object.getOwnPropertyNames(window),
+    noop() {},
+
+    // Array functions
+    // Notice: avoid using prototype functions since they may be changed by page scripts
+    forEach(arr, func) {
+      const length = arr && arr.length;
+      for (let i = 0; i < length; i += 1) func(arr[i], i, arr);
     },
-    SetValue: function (data) {
-      sendMessage({cmd: 'SetValue', data: data});
+    includes(arr, item) {
+      const length = arr && arr.length;
+      for (let i = 0; i < length; i += 1) {
+        if (arr[i] === item) return true;
+      }
+      return false;
     },
-    RegisterMenu: function (data) {
-      if (window.top === window) menus.push(data);
-      getPopup();
+    map(arr, func) {
+      const res = [];
+      this.forEach(arr, (item, i) => {
+        res.push(func(item, i, arr));
+      });
+      return res;
     },
-    AddStyle: function (css) {
-      if (document.head) {
-        var style = document.createElement('style');
-        style.innerHTML = css;
-        document.head.appendChild(style);
+
+    init: initHandler,
+    post: postData,
+    runCode(name, func, args, thisObj) {
+      try {
+        func.apply(thisObj, args);
+      } catch (e) {
+        let msg = `Error running script: ${name}\n${e}`;
+        if (e.message) msg = `${msg}\n${e.message}`;
+        console.error(msg);
       }
     },
-    Notification: onNotificationCreate,
-    SetClipboard: function (data) {
-      sendMessage({cmd: 'SetClipboard', data: data});
-    },
+    getRequest,
+    getWrapper,
+    wrapGM,
+    onLoadScripts,
   };
-  var func = maps[req.cmd];
-  if (func) func(req.data);
-}
 
-var notifications = {};
-function onNotificationCreate(options) {
-  sendMessage({cmd: 'Notification', data: options})
-  .then(function (nid) {
-    notifications[nid] = options.id;
-  });
-}
-function onNotificationClick(nid) {
-  var id = notifications[nid];
-  id && comm.post({cmd: 'NotificationClicked', data: id});
-}
-function onNotificationClose(nid) {
-  var id = notifications[nid];
-  if (id) {
-    comm.post({cmd: 'NotificationClosed', data: id});
-    delete notifications[nid];
+  const ids = [];
+  const menus = [];
+  function injectScript(data) {
+    // data: [id, wrapperKeys, code]
+    const func = (scriptId, destId, cb) => {
+      Object.defineProperty(window, `VM_${scriptId}`, {
+        value: cb,
+        configurable: true,
+      });
+      const e = new CustomEvent(destId, { detail: { cmd: 'Injected', data: scriptId } });
+      document.dispatchEvent(e);
+    };
+    inject(`!${func.toString()}(${JSON.stringify(data[0])},${JSON.stringify(comm.did)},function(${data[1].join(',')}){${data[2]}})`);
+  }
+  function handleContent(req) {
+    if (!req) {
+      console.error('[Violentmonkey] Invalid data! There might be unsupported data format.');
+      return;
+    }
+    const handlers = {
+      GetRequestId: getRequestId,
+      HttpRequest: httpRequest,
+      AbortRequest: abortRequest,
+      Inject: injectScript,
+      OpenTab(data) {
+        sendMessage({ cmd: 'OpenTab', data });
+      },
+      SetValue(data) {
+        sendMessage({ cmd: 'SetValue', data });
+      },
+      RegisterMenu(data) {
+        if (window.top === window) menus.push(data);
+        getPopup();
+      },
+      AddStyle(css) {
+        if (document.head) {
+          const style = document.createElement('style');
+          style.innerHTML = css;
+          document.head.appendChild(style);
+        }
+      },
+      Notification: onNotificationCreate,
+      SetClipboard(data) {
+        sendMessage({ cmd: 'SetClipboard', data });
+      },
+    };
+    const handle = handlers[req.cmd];
+    if (handle) handle(req.data);
+  }
+  function getPopup() {
+    // XXX: only scripts run in top level window are counted
+    if (top === window) {
+      sendMessage({
+        cmd: 'SetPopup',
+        data: { ids, menus },
+      })
+      .catch(comm.noop);
+    }
   }
-}
 
-// Messages
-browser.runtime.onMessage.addListener(function (req, src) {
-  var handlers = {
-    Command: function (data) {
-      comm.post({cmd: 'Command', data: data});
-    },
-    GetPopup: getPopup,
-    GetBadge: getBadge,
-    HttpRequested: httpRequested,
-    UpdateValues: function (data) {
-      comm.post({cmd: 'UpdateValues', data: data});
-    },
-    NotificationClick: onNotificationClick,
-    NotificationClose: onNotificationClose,
-  };
-  var func = handlers[req.cmd];
-  if (func) func(req.data, src);
-});
+  const notifications = {};
+  function onNotificationCreate(options) {
+    sendMessage({ cmd: 'Notification', data: options })
+    .then(nid => { notifications[nid] = options.id; });
+  }
+  function onNotificationClick(nid) {
+    const id = notifications[nid];
+    if (id) comm.post({ cmd: 'NotificationClicked', data: id });
+  }
+  function onNotificationClose(nid) {
+    const id = notifications[nid];
+    if (id) {
+      comm.post({ cmd: 'NotificationClosed', data: id });
+      delete notifications[nid];
+    }
+  }
 
-// Requests
-var requests = {};
-function getRequestId() {
-  sendMessage({cmd: 'GetRequestId'}).then(function (id) {
-    requests[id] = 1;
-    comm.post({cmd: 'GotRequestId', data: id});
+  // Messages
+  browser.runtime.onMessage.addListener((req, src) => {
+    const handlers = {
+      Command(data) {
+        comm.post({ cmd: 'Command', data });
+      },
+      GetPopup: getPopup,
+      GetBadge: getBadge,
+      HttpRequested: httpRequested,
+      UpdateValues(data) {
+        comm.post({ cmd: 'UpdateValues', data });
+      },
+      NotificationClick: onNotificationClick,
+      NotificationClose: onNotificationClose,
+    };
+    const handle = handlers[req.cmd];
+    if (handle) handle(req.data, src);
   });
-}
-function httpRequest(details) {
-  sendMessage({cmd: 'HttpRequest', data: details});
-}
-function httpRequested(data) {
-  if (requests[data.id]) {
-    if (data.type == 'loadend') delete requests[data.id];
-    comm.post({cmd: 'HttpRequested', data: data});
+
+  // Requests
+  const requests = {};
+  function getRequestId() {
+    sendMessage({ cmd: 'GetRequestId' })
+    .then(id => {
+      requests[id] = 1;
+      comm.post({ cmd: 'GotRequestId', data: id });
+    });
+  }
+  function httpRequest(details) {
+    sendMessage({ cmd: 'HttpRequest', data: details });
+  }
+  function httpRequested(data) {
+    if (requests[data.id]) {
+      if (data.type === 'loadend') delete requests[data.id];
+      comm.post({ cmd: 'HttpRequested', data });
+    }
+  }
+  function abortRequest(id) {
+    sendMessage({ cmd: 'AbortRequest', data: id });
   }
-}
-function abortRequest(id) {
-  sendMessage({cmd: 'AbortRequest', data: id});
-}
 
-function objEncode(obj) {
-  var list = [];
-  for (var i in obj) {
-    if (!obj.hasOwnProperty(i)) continue;
-    if (typeof obj[i] == 'function') {
-      list.push(i + ':' + obj[i].toString());
-    } else {
-      list.push(i + ':' + JSON.stringify(obj[i]));
+  function objEncode(obj) {
+    const list = Object.keys(obj).map(name => {
+      const value = obj[name];
+      const jsonKey = JSON.stringify(name);
+      if (typeof value === 'function') return `${jsonKey}:${value.toString()}`;
+      return `${jsonKey}:${JSON.stringify(value)}`;
+    });
+    return `{${list.join(',')}}`;
+  }
+  function inject(code) {
+    const script = document.createElement('script');
+    const doc = document.body || document.documentElement;
+    script.innerHTML = code;
+    doc.appendChild(script);
+    try {
+      doc.removeChild(script);
+    } catch (e) {
+      // ignore if body is changed and script is detached
     }
   }
-  return '{' + list.join(',') + '}';
-}
-function inject(code) {
-  var script = document.createElement('script');
-  var doc = document.body || document.documentElement;
-  script.innerHTML = code;
-  doc.appendChild(script);
-  try {
-    doc.removeChild(script);
-  } catch (e) {
-    // ignore if body is changed and script is detached
+  function loadScripts(data) {
+    comm.forEach(data.scripts, script => {
+      ids.push(script.id);
+      if (script.enabled) badge.number += 1;
+    });
+    comm.post({ cmd: 'LoadScripts', data });
+    badge.ready = true;
+    getPopup();
+    setBadge();
   }
-}
-function loadScript(data) {
-  forEach(data.scripts, function (script) {
-    ids.push(script.id);
-    if (script.enabled) badge.number ++;
-  });
-  comm.post({cmd: 'LoadScript', data: data});
-  badge.ready = true;
-  getPopup();
-  setBadge();
-}
-function initCommunicator() {
-  var C = 'C';
-  var R = 'R';
-  inject(
-    '!function(c,R,C){c.init(R,C);document.addEventListener("DOMContentLoaded",function(e){c.state=1;c.load();},false);c.checkLoad();}(' +
-    objEncode(comm) + ',"' + R + '","' + C + '")'
-  );
-  comm.handleC = handleC;
-  comm.init(C, R);
-  sendMessage({cmd: 'GetInjected', data: location.href}).then(loadScript);
-}
-initCommunicator();
+  function initCommunicator() {
+    const contentId = getUniqId();
+    const webId = getUniqId();
+    comm[`handle_${webId}`] = handleWeb;
+    const initWeb = (comm, webId, contentId) => { // eslint-disable-line no-shadow
+      comm.init(webId, contentId);
+      document.addEventListener('DOMContentLoaded', () => {
+        comm.state = 1;
+        comm.load();
+      }, false);
+      comm.checkLoad();
+    };
+    inject(`(${initWeb.toString()})(${objEncode(comm)},${JSON.stringify(webId)},${JSON.stringify(contentId)})`);
+    comm[`handle_${contentId}`] = handleContent;
+    comm.init(contentId, webId);
+    sendMessage({ cmd: 'GetInjected', data: location.href }).then(loadScripts);
+  }
+  initCommunicator();
+}());

+ 2 - 2
src/popup/index.html

@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<html>
+<!DOCTYPE html>
+<html>
 <head>
 <meta charset="utf-8">
 <title>Popup Menu - Violentmonkey</title>