Bug 1338249 - Improve eslint-plugin-mozilla's handling of workers when dealing with globals and imports. r?Mossop draft
authorMark Banner <standard8@mozilla.com>
Tue, 07 Feb 2017 11:14:49 +0000
changeset 484635 ae0cacef23373841d25051e0e21fcc588f1c64a3
parent 484546 0a7831d838f793a263456df62f95a615472a7f95
child 545834 f41108d4e3e07a914b017eda935a1b0814d5fb67
push id45530
push userbmo:standard8@mozilla.com
push dateWed, 15 Feb 2017 16:28:31 +0000
reviewersMossop
bugs1338249
milestone54.0a1
Bug 1338249 - Improve eslint-plugin-mozilla's handling of workers when dealing with globals and imports. r?Mossop MozReview-Commit-ID: 7UdgstbYNlB
browser/components/originattributes/test/browser/worker_deblobify.js
devtools/server/worker.js
toolkit/components/ctypes/tests/chrome/ctypes_worker.js
toolkit/components/lz4/lz4.js
toolkit/components/lz4/lz4_internal.js
toolkit/components/lz4/tests/xpcshell/data/worker_lz4.js
toolkit/components/places/ColorAnalyzer_worker.js
toolkit/components/promiseworker/tests/xpcshell/data/worker.js
toolkit/components/promiseworker/worker/PromiseWorker.js
toolkit/components/reader/ReaderWorker.js
toolkit/components/sqlite/sqlite_internal.js
toolkit/components/sqlite/tests/xpcshell/data/worker_sqlite_internal.js
toolkit/components/thumbnails/PageThumbsWorker.js
toolkit/components/workerloader/tests/moduleA-depends.js
toolkit/components/workerloader/tests/moduleB-dependency.js
toolkit/components/workerloader/tests/moduleC-circular.js
toolkit/components/workerloader/tests/moduleD-circular.js
toolkit/components/workerloader/tests/moduleE-throws-during-require.js
toolkit/components/workerloader/tests/moduleG-throws-later.js
toolkit/components/workerloader/tests/moduleH-module-dot-exports.js
toolkit/components/workerloader/tests/worker_test_loading.js
tools/lint/docs/linters/eslint-plugin-mozilla.rst
tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-worker.js
tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
tools/lint/eslint/eslint-plugin-mozilla/package.json
--- a/browser/components/originattributes/test/browser/worker_deblobify.js
+++ b/browser/components/originattributes/test/browser/worker_deblobify.js
@@ -1,12 +1,14 @@
 // Wait for a blob URL to be posted to this worker.
 // Obtain the blob, and read the string contained in it.
 // Post back the string.
 
+/* eslint-env worker */
+
 var postStringInBlob = function(blobObject) {
   var fileReader = new FileReaderSync();
   var result = fileReader.readAsText(blobObject);
   postMessage(result);
 };
 
 self.addEventListener("message", function(message) {
   if ("error" in message.data) {
--- a/devtools/server/worker.js
+++ b/devtools/server/worker.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-/* eslint-env worker */
+/* eslint-env mozilla/chrome-worker */
 /* global worker, loadSubScript, global */
 
 // This function is used to do remote procedure calls from the worker to the
 // main thread. It is exposed as a built-in global to every module by the
 // worker loader. To make sure the worker loader can access it, it needs to be
 // defined before loading the worker loader script below.
 this.rpc = function (method, ...params) {
   let id = nextId++;
--- a/toolkit/components/ctypes/tests/chrome/ctypes_worker.js
+++ b/toolkit/components/ctypes/tests/chrome/ctypes_worker.js
@@ -1,14 +1,17 @@
 /* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+ /* eslint-env mozilla/chrome-worker */
+
 importScripts("xpcshellTestHarnessAdaptor.js");
 
 onmessage = function(event) {
   _WORKINGDIR_ = event.data.dir;
   _OS_ = event.data.os;
+  /* import-globals-from ../unit/test_jsctypes.js */
   importScripts("test_jsctypes.js");
   run_test();
   postMessage("Done!");
 }
--- a/toolkit/components/lz4/lz4.js
+++ b/toolkit/components/lz4/lz4.js
@@ -13,31 +13,29 @@ if (typeof Components != "undefined") {
   Cu.import("resource://gre/modules/lz4_internal.js");
   Cu.import("resource://gre/modules/ctypes.jsm");
 
   this.EXPORTED_SYMBOLS = [
     "Lz4"
   ];
   this.exports = {};
 } else if (typeof module != "undefined" && typeof require != "undefined") {
+  /* eslint-env commonjs */
   SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
   Primitives = require("resource://gre/modules/lz4_internal.js");
 } else {
   throw new Error("Please load this module with Component.utils.import or with require()");
 }
 
 const MAGIC_NUMBER = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); // "mozLz4a\0"
 
 const BYTES_IN_SIZE_HEADER = ctypes.uint32_t.size;
 
 const HEADER_SIZE = MAGIC_NUMBER.byteLength + BYTES_IN_SIZE_HEADER;
 
-const EXPECTED_HEADER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, HEADER_SIZE);
-const EXPECTED_SIZE_BUFFER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, BYTES_IN_SIZE_HEADER);
-
 /**
  * An error during (de)compression
  *
  * @param {string} operation The name of the operation ("compress", "decompress")
  * @param {string} reason A reason to be used when matching errors. Must start
  * with "because", e.g. "becauseInvalidContent".
  * @param {string} message A human-readable message.
  */
--- a/toolkit/components/lz4/lz4_internal.js
+++ b/toolkit/components/lz4/lz4_internal.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+ /* eslint-env commonjs */
+
 "use strict";
 
 var Primitives = {};
 
 var SharedAll;
 if (typeof Components != "undefined") {
   let Cu = Components.utils;
   SharedAll = {};
--- a/toolkit/components/lz4/tests/xpcshell/data/worker_lz4.js
+++ b/toolkit/components/lz4/tests/xpcshell/data/worker_lz4.js
@@ -1,8 +1,10 @@
+/* eslint-env mozilla/chrome-worker */
+
 importScripts("resource://gre/modules/workers/require.js");
 importScripts("resource://gre/modules/osfile.jsm");
 
 
 function do_print(x) {
   // self.postMessage({kind: "do_print", args: [x]});
   dump("TEST-INFO: " + x + "\n");
 }
--- a/toolkit/components/places/ColorAnalyzer_worker.js
+++ b/toolkit/components/places/ColorAnalyzer_worker.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* eslint-env mozilla/chrome-worker */
+
 "use strict";
 
 importScripts("ClusterLib.js", "ColorConversion.js");
 
 // Offsets in the ImageData pixel array to reach pixel colors
 const PIXEL_RED = 0;
 const PIXEL_GREEN = 1;
 const PIXEL_BLUE = 2;
--- a/toolkit/components/promiseworker/tests/xpcshell/data/worker.js
+++ b/toolkit/components/promiseworker/tests/xpcshell/data/worker.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env mozilla/chrome-worker */
+
 "use strict";
 
 // Trivial worker definition
 
 importScripts("resource://gre/modules/workers/require.js");
 var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
 
 var worker = new PromiseWorker.AbstractWorker();
--- a/toolkit/components/promiseworker/worker/PromiseWorker.js
+++ b/toolkit/components/promiseworker/worker/PromiseWorker.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* eslint-env commonjs, mozilla/chrome-worker */
+
 /**
  * A wrapper around `self` with extended capabilities designed
  * to simplify main thread-to-worker thread asynchronous function calls.
  *
  * This wrapper:
  * - groups requests and responses as a method `post` that returns a `Promise`;
  * - ensures that exceptions thrown on the worker thread are correctly serialized;
  * - provides some utilities for benchmarking various operations.
--- a/toolkit/components/reader/ReaderWorker.js
+++ b/toolkit/components/reader/ReaderWorker.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* eslint-env mozilla/chrome-worker */
+
 "use strict";
 
 /**
  * A worker dedicated to handle parsing documents for reader view.
  */
 
 importScripts("resource://gre/modules/workers/require.js",
               "resource://gre/modules/reader/JSDOMParser.js",
--- a/toolkit/components/sqlite/sqlite_internal.js
+++ b/toolkit/components/sqlite/sqlite_internal.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* eslint-env commonjs, mozilla/chrome-worker */
+
 /**
  * This file defines an Sqlite object containing js-ctypes bindings for
  * sqlite3. It should be included from a worker thread using require.
  *
  * It serves the following purposes:
  * - opens libxul;
  * - defines sqlite3 API functions;
  * - defines the necessary sqlite3 types.
--- a/toolkit/components/sqlite/tests/xpcshell/data/worker_sqlite_internal.js
+++ b/toolkit/components/sqlite/tests/xpcshell/data/worker_sqlite_internal.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env mozilla/chrome-worker */
+
 importScripts("worker_sqlite_shared.js",
   "resource://gre/modules/workers/require.js");
 
 self.onmessage = function onmessage(msg) {
   try {
     run_test();
   } catch (ex) {
     let {message, moduleStack, moduleName, lineNumber} = ex;
--- a/toolkit/components/thumbnails/PageThumbsWorker.js
+++ b/toolkit/components/thumbnails/PageThumbsWorker.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* eslint-env mozilla/chrome-worker */
+
 /**
  * A worker dedicated for the I/O component of PageThumbs storage.
  *
  * Do not rely on the API of this worker. In a future version, it might be
  * fully replaced by a OS.File global I/O worker.
  */
 
 "use strict";
@@ -168,9 +170,8 @@ var Agent = {
       iterator.close();
     }
   },
 
   exists: function Agent_exists(path) {
     return File.exists(path);
   },
 };
-
--- a/toolkit/components/workerloader/tests/moduleA-depends.js
+++ b/toolkit/components/workerloader/tests/moduleA-depends.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env commonjs */
+
 // A trivial module that depends on an equally trivial module
 var B = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleB-dependency.js");
 
 // Ensure that the initial set of exports is empty
 if (Object.keys(exports).length) {
   throw new Error("exports should be empty, initially");
 }
 
--- a/toolkit/components/workerloader/tests/moduleB-dependency.js
+++ b/toolkit/components/workerloader/tests/moduleB-dependency.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env commonjs */
+
 exports.B = true;
 exports.foo = "foo";
 
 // Side-effect to detect if we attempt to re-execute this module.
 if ("loadedB" in self) {
   throw new Error("B has been evaluted twice");
 }
 self.loadedB = true;
--- a/toolkit/components/workerloader/tests/moduleC-circular.js
+++ b/toolkit/components/workerloader/tests/moduleC-circular.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env commonjs */
+
 // Module C and module D have circular dependencies.
 // This should not prevent from loading them.
 
 // This value is set before any circular dependency, it should be visible
 // in D.
 exports.enteredC = true;
 
 var D = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleD-circular.js");
--- a/toolkit/components/workerloader/tests/moduleD-circular.js
+++ b/toolkit/components/workerloader/tests/moduleD-circular.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env commonjs */
+
 // Module C and module D have circular dependencies.
 // This should not prevent from loading them.
 
 exports.enteredD = true;
 var C = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleC-circular.js");
 exports.copiedFromC = JSON.parse(JSON.stringify(C));
 exports.exportedFromC = C;
 exports.finishedD = true;
--- a/toolkit/components/workerloader/tests/moduleE-throws-during-require.js
+++ b/toolkit/components/workerloader/tests/moduleE-throws-during-require.js
@@ -1,10 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env commonjs */
+
 // Skip a few lines
 // 5
 // 6
 // 7
 // 8
 // 9
 throw new Error("Let's see if this error is obtained with the right origin");
--- a/toolkit/components/workerloader/tests/moduleG-throws-later.js
+++ b/toolkit/components/workerloader/tests/moduleG-throws-later.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env commonjs */
+
 // Skip a few lines
 // 5
 // 6
 // 7
 // 8
 // 9
 exports.doThrow = function doThrow() {
   Array.prototype.sort.apply("foo"); // This will raise a native TypeError
--- a/toolkit/components/workerloader/tests/moduleH-module-dot-exports.js
+++ b/toolkit/components/workerloader/tests/moduleH-module-dot-exports.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env commonjs */
+
 // This should be overwritten by module.exports
 exports.key = "wrong value";
 
 module.exports = {
   key: "value"
 };
 
 // This should also be overwritten by module.exports
--- a/toolkit/components/workerloader/tests/worker_test_loading.js
+++ b/toolkit/components/workerloader/tests/worker_test_loading.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env mozilla/chrome-worker */
+
 "use strict";
 
 importScripts("utils_worker.js"); // Test suite code
 info("Test suite configured");
 
 importScripts("resource://gre/modules/workers/require.js");
 info("Loader imported");
 
@@ -60,29 +62,29 @@ add_test(function test_exceptions() {
   ok(!!exn, "Attempting to load a module that doesn't exist raises an error");
 
   exn = should_throw(() => require(PATH + "moduleE-throws-during-require.js"));
   ok(!!exn, "Attempting to load a module that throws at toplevel raises an error");
   is(exn.moduleName, PATH + "moduleE-throws-during-require.js",
     "moduleName is correct");
   isnot(exn.moduleStack.indexOf("moduleE-throws-during-require.js"), -1,
     "moduleStack contains the name of the module");
-  is(exn.lineNumber, 10, "The error comes with the right line number");
+  is(exn.lineNumber, 12, "The error comes with the right line number");
 
   exn = should_throw(() => require(PATH + "moduleF-syntaxerror.xml"));
   ok(!!exn, "Attempting to load a non-well formatted module raises an error");
 
   exn = should_throw(() => require(PATH + "moduleG-throws-later.js").doThrow());
   ok(!!exn, "G.doThrow() has raised an error");
   info(exn);
   ok(exn.toString().startsWith("TypeError"), "The exception is a TypeError.");
   is(exn.moduleName, PATH + "moduleG-throws-later.js", "The name of the module is correct");
   isnot(exn.moduleStack.indexOf("moduleG-throws-later.js"), -1,
     "The name of the right file appears somewhere in the stack");
-  is(exn.lineNumber, 11, "The error comes with the right line number");
+  is(exn.lineNumber, 13, "The error comes with the right line number");
 });
 
 function get_exn(f) {
   try {
     f();
     return undefined;
   } catch (ex) {
     return ex;
@@ -111,11 +113,8 @@ self.onmessage = function(message) {
       ok(false, "Test " + test.name + " failed");
       info(ex);
       info(ex.stack);
     }
     info("Leaving " + test.name);
   }
   finish();
 };
-
-
-
--- a/tools/lint/docs/linters/eslint-plugin-mozilla.rst
+++ b/tools/lint/docs/linters/eslint-plugin-mozilla.rst
@@ -1,12 +1,24 @@
 =====================
 Mozilla ESLint Plugin
 =====================
 
+Environments
+============
+
+chrome-worker
+-------------
+
+Defines the environment for chrome workers. This differs from normal workers by
+the fact that `ctypes` can be accessed as well.
+
+Rules
+=====
+
 avoid-removeChild
 -----------------
 
 Rejects using element.parentNode.removeChild(element) when element.remove()
 can be used instead.
 
 balanced-listeners
 ------------------
new file mode 100644
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-worker.js
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Defines the environment for chrome workers. This differs
+ *               from normal workers by the fact that `ctypes` can be accessed
+ *               as well.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var globals = require('globals');
+var util = require('util');
+
+var workerGlobals = util._extend({
+  ctypes: false
+}, globals.worker);
+
+module.exports = {
+  globals: workerGlobals
+};
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
@@ -67,64 +67,48 @@ const globalCache = new Map();
  *
  * @param  {String} filePath
  *         The absolute path of the file being parsed.
  */
 function GlobalsForNode(filePath) {
   this.path = filePath;
   this.dirname = path.dirname(this.path)
   this.root = helpers.getRootDir(this.path);
-  this.isWorker = helpers.getIsWorker(this.path);
 }
 
 GlobalsForNode.prototype = {
-  Program(node) {
-    if (!this.isWorker) {
-      return [];
-    }
-
-    return [
-      {name: "importScripts", writable: false},
-      // Only available to workers.
-      {name: "FileReaderSync", writable: false},
-      {name: "onmessage", writable: true},
-      // Only available to chrome workers, but since we don't know which is which,
-      // we make it available anyway.
-      {name: "ctypes", writable: false}
-    ];
-  },
-
   BlockComment(node, parents) {
     let value = node.value.trim();
     let match = /^import-globals-from\s+(.+)$/.exec(value);
     if (!match) {
       return [];
     }
 
     let filePath = match[1].trim();
 
     if (!path.isAbsolute(filePath)) {
       filePath = path.resolve(this.dirname, filePath);
     }
 
     return module.exports.getGlobalsForFile(filePath);
   },
 
-  ExpressionStatement(node, parents) {
+  ExpressionStatement(node, parents, globalScope) {
     let isGlobal = helpers.getIsGlobalScope(parents);
     let globals = helpers.convertExpressionToGlobals(node, isGlobal, this.root);
     // Map these globals now, as getGlobalsForFile is pre-mapped.
     globals = globals.map(name => { return { name, writable: true }});
 
-    if (this.isWorker) {
+    // Here we assume that if importScripts is set in the global scope, then
+    // this is a worker. It would be nice if eslint gave us a way of getting
+    // the environment directly.
+    if (globalScope && globalScope.set.get("importScripts")) {
       let workerDetails = helpers.convertWorkerExpressionToGlobals(node,
         isGlobal, this.root, this.dirname);
-      globals = globals.concat(workerDetails.map(name => {
-        return { name, writable: true };
-      }));
+      globals = globals.concat(workerDetails);
     }
 
     return globals;
   },
 };
 
 module.exports = {
   /**
@@ -177,17 +161,17 @@ module.exports = {
               name,
               writable: values[name].value
             })
           }
         }
       }
 
       if (type in handler) {
-        let newGlobals = handler[type](node, parents);
+        let newGlobals = handler[type](node, parents, globalScope);
         globals.push.apply(globals, newGlobals);
       }
     });
 
     globalCache.set(path, globals);
 
     return globals;
   },
@@ -211,16 +195,16 @@ module.exports = {
     // Install thin wrappers around GlobalsForNode
     let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
 
     for (let type of Object.keys(GlobalsForNode.prototype)) {
       parser[type] = function(node) {
         if (type === "Program") {
           globalScope = context.getScope();
         }
-        let globals = handler[type](node, context.getAncestors());
+        let globals = handler[type](node, context.getAncestors(), globalScope);
         helpers.addGlobals(globals, globalScope);
       }
     }
 
     return parser;
   }
 };
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
@@ -165,17 +165,21 @@ module.exports = {
    * @param  {Object} node
    *         The AST node to convert.
    * @param  {boolean} isGlobal
    *         True if the current node is in the global scope.
    * @param  {String} repository
    *         The root of the repository.
    *
    * @return {Array}
-   *         An array of global variable names defined.
+   *         An array of objects that contain details about the globals:
+   *         - {String} name
+   *                    The name of the global.
+   *         - {Boolean} writable
+   *                     If the global is writeable or not.
    */
   convertWorkerExpressionToGlobals: function(node, isGlobal, repository, dirname) {
     var getGlobalsForFile = require("./globals").getGlobalsForFile;
 
     if (!modules) {
       modules = require(path.join(repository, "tools", "lint", "eslint", "modules.json"));
     }
 
@@ -191,21 +195,24 @@ module.exports = {
         if (match) {
           if (!match[1]) {
             let filePath = path.resolve(dirname, match[2]);
             if (fs.existsSync(filePath)) {
               let additionalGlobals = getGlobalsForFile(filePath);
               results = results.concat(additionalGlobals);
             }
           } else if (match[2] in modules) {
-            results.push(modules[match[2]]);
+            results = results.concat(modules[match[2]].map(name => {
+              return { name, writable: true };
+            }));
           }
         }
       }
     }
+
     return results;
   },
 
   convertExpressionToGlobals: function(node, isGlobal, repository) {
     if (!modules) {
       modules = require(path.join(repository, "tools", "lint", "eslint", "modules.json"));
     }
 
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
@@ -8,16 +8,19 @@
 
 "use strict";
 
 //------------------------------------------------------------------------------
 // Plugin Definition
 //------------------------------------------------------------------------------
 
 module.exports = {
+  environments: {
+    "chrome-worker": require("../lib/environments/chrome-worker.js"),
+  },
   processors: {
     ".xml": require("../lib/processors/xbl-bindings"),
     ".js": require("../lib/processors/self-hosted"),
   },
   rules: {
     "avoid-removeChild": require("../lib/rules/avoid-removeChild"),
     "balanced-listeners": require("../lib/rules/balanced-listeners"),
     "import-browserjs-globals": require("../lib/rules/import-browserjs-globals"),
--- a/tools/lint/eslint/eslint-plugin-mozilla/package.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -1,11 +1,11 @@
 {
   "name": "eslint-plugin-mozilla",
-  "version": "0.2.20",
+  "version": "0.2.21",
   "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
   "keywords": [
     "eslint",
     "eslintplugin",
     "eslint-plugin",
     "mozilla",
     "firefox"
   ],
@@ -14,16 +14,17 @@
   },
   "homepage": "https://bugzilla.mozilla.org/show_bug.cgi?id=1203520",
   "author": "Mike Ratcliffe",
   "main": "lib/index.js",
   "dependencies": {
     "escope": "^3.6.0",
     "espree": "^3.2.0",
     "estraverse": "^4.2.0",
+    "globals": "^9.14.0",
     "ini-parser": "^0.0.2",
     "sax": "^1.1.4"
   },
   "engines": {
     "node": ">=0.10.0"
   },
   "scripts": {
     "test": "node tests/test-run-all.js"