Backed out 5 changesets (bug 1245916) for browser-chrome and devtools bustages
authorPhil Ringnalda <philringnalda@gmail.com>
Mon, 15 Feb 2016 13:58:27 -0800
changeset 320589 8bd1a25ac261cf03eb383264f69fb8606cabb470
parent 320588 d16bb239c89373b0cc1c4ecf40e6ffc88971867c
child 320590 25e5d5137717d453efef9412126b01641638df0c
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1245916
milestone47.0a1
backs out70eca07367f40a9b4fe8c6e23ec0aef73bf2a962
04c1740aa49904a56bd662ee0c8aed69e6f99f8b
b554c7ce41c42f16c2279ae88fd9567da7509bff
01675e4828b524c04a9057d68b41e9cc01ca1bb9
878db4caf845282f06542793bc4b5c24fa658c14
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 5 changesets (bug 1245916) for browser-chrome and devtools bustages CLOSED TREE Backed out changeset 70eca07367f4 (bug 1245916) Backed out changeset 04c1740aa499 (bug 1245916) Backed out changeset b554c7ce41c4 (bug 1245916) Backed out changeset 01675e4828b5 (bug 1245916) Backed out changeset 878db4caf845 (bug 1245916)
.eslintignore
.eslintrc
browser/base/content/browser.js
testing/eslint-plugin-mozilla/docs/components-imports.rst
testing/eslint-plugin-mozilla/docs/import-globals-from.rst
testing/eslint-plugin-mozilla/docs/import-globals.rst
testing/eslint-plugin-mozilla/docs/this-top-level-scope.rst
testing/eslint-plugin-mozilla/lib/globals.js
testing/eslint-plugin-mozilla/lib/helpers.js
testing/eslint-plugin-mozilla/lib/index.js
testing/eslint-plugin-mozilla/lib/processors/xbl-bindings.js
testing/eslint-plugin-mozilla/lib/rules/components-imports.js
testing/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
testing/eslint-plugin-mozilla/lib/rules/import-globals-from.js
testing/eslint-plugin-mozilla/lib/rules/import-globals.js
testing/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
testing/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js
testing/eslint-plugin-mozilla/lib/rules/this-top-level-scope.js
testing/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
testing/mochitest/browser.eslintrc
testing/xpcshell/xpcshell.eslintrc
toolkit/.eslintrc
toolkit/components/extensions/.eslintrc
toolkit/content/contentAreaUtils.js
toolkit/content/jar.mn
--- a/.eslintignore
+++ b/.eslintignore
@@ -182,18 +182,18 @@ toolkit/components/workerloader/tests/mo
 # Tests old non-star function generators
 toolkit/modules/tests/xpcshell/test_task.js
 
 # Not yet updated
 toolkit/components/osfile/**
 toolkit/components/passwordmgr/**
 
 # Uses preprocessing
+toolkit/content/contentAreaUtils.js
 toolkit/content/widgets/videocontrols.xml
-toolkit/content/widgets/wizard.xml
 toolkit/components/jsdownloads/src/DownloadIntegration.jsm
 toolkit/components/search/nsSearchService.js
 toolkit/components/url-classifier/**
 toolkit/components/urlformatter/nsURLFormatter.js
 toolkit/identity/FirefoxAccounts.jsm
 toolkit/modules/AppConstants.jsm
 toolkit/mozapps/downloads/nsHelperAppDlg.js
 toolkit/mozapps/extensions/internal/AddonConstants.jsm
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,12 +1,14 @@
 {
   // When adding items to this file please check for effects on sub-directories.
   "plugins": [
     "mozilla"
   ],
   "rules": {
-    "mozilla/import-globals": 1,
+    "mozilla/components-imports": 1,
+    "mozilla/import-globals-from": 1,
+    "mozilla/this-top-level-scope": 1,
   },
   "env": {
     "es6": true
   },
 }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -76,17 +76,16 @@ var gMultiProcessBrowser =
 var gAppInfo = Cc["@mozilla.org/xre/app-info;1"]
                   .getService(Ci.nsIXULAppInfo)
                   .QueryInterface(Ci.nsIXULRuntime);
 
 if (AppConstants.platform != "macosx") {
   var gEditUIVisible = true;
 }
 
-/*globals gBrowser, gNavToolbox, gURLBar, gNavigatorBundle*/
 [
   ["gBrowser",            "content"],
   ["gNavToolbox",         "navigator-toolbox"],
   ["gURLBar",             "urlbar"],
   ["gNavigatorBundle",    "bundle_browser"]
 ].forEach(function (elementGlobal) {
   var [name, id] = elementGlobal;
   window.__defineGetter__(name, function () {
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/docs/components-imports.rst
@@ -0,0 +1,21 @@
+.. _components-imports:
+
+==================
+components-imports
+==================
+
+Rule Details
+------------
+
+Adds the filename of imported files e.g.
+``Cu.import("some/path/Blah.jsm")`` adds Blah to the global scope.
+
+The following patterns are supported:
+
+-  ``Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");``
+-  ``loader.lazyImporter(this, "name1");``
+-  ``loader.lazyRequireGetter(this, "name2"``
+-  ``loader.lazyServiceGetter(this, "name3"``
+-  ``XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout", ...)``
+-  ``loader.lazyGetter(this, "toolboxStrings"``
+-  ``XPCOMUtils.defineLazyGetter(this, "clipboardHelper"``
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/docs/import-globals-from.rst
@@ -0,0 +1,18 @@
+.. _import-globals-from:
+
+===================
+import-globals-from
+===================
+
+Rule Details
+------------
+
+When the "import-globals-from <path>" comment is found in a file, then all
+globals from the file at <path> will be imported in the current scope.
+
+This is useful for tests that rely on globals defined in head.js files, or for
+scripts that are loaded as <script> tag in a window in rely on eachother's
+globals.
+
+If <path> is a relative path, then it must be relative to the file being
+checked by the rule.
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/docs/import-globals.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-.. _import-globals:
-
-==============
-import-globals
-==============
-
-Rule Details
-------------
-
-Parses a file for globals defined in various unique Mozilla ways.
-
-When a "import-globals-from <path>" comment is found in a file, then all globals
-from the file at <path> will be imported in the current scope. This will also
-operate recursively.
-
-This is useful for scripts that are loaded as <script> tag in a window and rely
-on each other's globals.
-
-If <path> is a relative path, then it must be relative to the file being
-checked by the rule.
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/docs/this-top-level-scope.rst
@@ -0,0 +1,11 @@
+.. _this-top-level-scope:
+
+====================
+this-top-level-scope
+====================
+
+Rule Details
+------------
+
+Treat top-level assignments like ``this.mumble = value`` as declaring
+a global.
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/lib/globals.js
+++ /dev/null
@@ -1,187 +0,0 @@
-/**
- * @fileoverview functions for scanning an AST for globals including
- *               traversing referenced scripts.
- * 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";
-
-const path = require("path");
-const fs = require("fs");
-const helpers = require("./helpers");
-const escope = require("escope");
-const estraverse = require("estraverse");
-
-/**
- * Parses a list of "name:boolean_value" or/and "name" options divided by comma or
- * whitespace.
- *
- * This function was copied from eslint.js
- *
- * @param {string} string The string to parse.
- * @param {Comment} comment The comment node which has the string.
- * @returns {Object} Result map object of names and boolean values
- */
-function parseBooleanConfig(string, comment) {
-  let items = {};
-
-  // Collapse whitespace around : to make parsing easier
-  string = string.replace(/\s*:\s*/g, ":");
-  // Collapse whitespace around ,
-  string = string.replace(/\s*,\s*/g, ",");
-
-  string.split(/\s|,+/).forEach(function(name) {
-    if (!name) {
-      return;
-    }
-
-    let pos = name.indexOf(":");
-    let value = undefined;
-    if (pos !== -1) {
-      value = name.substring(pos + 1, name.length);
-      name = name.substring(0, pos);
-    }
-
-    items[name] = {
-      value: (value === "true"),
-      comment: comment
-    };
-  });
-
-  return items;
-}
-
-/**
- * Global discovery can require parsing many files. This map of
- * {String} => {Object} caches what globals were discovered for a file path.
- */
-const globalCache = new Map();
-
-/**
- * An object that returns found globals for given AST node types. Each prototype
- * property should be named for a node type and accepts a node parameter and a
- * parents parameter which is a list of the parent nodes of the current node.
- * Each returns an array of globals found.
- *
- * @param  {String} path
- *         The absolute path of the file being parsed.
- */
-function GlobalsForNode(path) {
-  this.path = path;
-}
-
-GlobalsForNode.prototype = {
-  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)) {
-      let dirName = path.dirname(this.path);
-      filePath = path.resolve(dirName, filePath);
-    }
-
-    return module.exports.getGlobalsForFile(filePath);
-  },
-
-  ExpressionStatement(node, parents) {
-    let isGlobal = helpers.getIsGlobalScope(parents);
-    let name = helpers.convertExpressionToGlobal(node, isGlobal);
-    return name ? [{ name, writable: true}] : [];
-  },
-};
-
-module.exports = {
-  /**
-   * Returns all globals for a given file. Recursively searches through
-   * import-globals-from directives and also includes globals defined by
-   * standard eslint directives.
-   *
-   * @param  {String} path
-   *         The absolute path of the file to be parsed.
-   */
-  getGlobalsForFile(path) {
-    if (globalCache.has(path)) {
-      return globalCache.get(path);
-    }
-
-    let content = fs.readFileSync(path, "utf8");
-
-    // Parse the content into an AST
-    let ast = helpers.getAST(content);
-
-    // Discover global declarations
-    let scopeManager = escope.analyze(ast);
-    let globalScope = scopeManager.acquire(ast);
-
-    let globals = Object.keys(globalScope.variables).map(v => ({
-      name: globalScope.variables[v].name,
-      writable: true,
-    }));
-
-    // Walk over the AST to find any of our custom globals
-    let handler = new GlobalsForNode(path);
-
-    helpers.walkAST(ast, (type, node, parents) => {
-      // We have to discover any globals that ESLint would have defined through
-      // comment directives
-      if (type == "BlockComment") {
-        let value = node.value.trim();
-        let match = /^globals?\s+(.+)$/.exec(value);
-        if (match) {
-          let values = parseBooleanConfig(match[1].trim(), node);
-          for (let name of Object.keys(values)) {
-            globals.push({
-              name,
-              writable: values[name].value
-            })
-          }
-        }
-      }
-
-      if (type in handler) {
-        let newGlobals = handler[type](node, parents);
-        globals.push.apply(globals, newGlobals);
-      }
-    });
-
-    globalCache.set(path, globals);
-
-    return globals;
-  },
-
-  /**
-   * Intended to be used as-is for an ESLint rule that parses for globals in
-   * the current file and recurses through import-globals-from directives.
-   *
-   * @param  {Object} context
-   *         The ESLint parsing context.
-   */
-  getESLintGlobalParser(context) {
-    let globalScope;
-
-    let parser = {
-      Program(node) {
-        globalScope = context.getScope();
-      }
-    };
-
-    // Install thin wrappers around GlobalsForNode
-    let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
-
-    for (let type of Object.keys(GlobalsForNode.prototype)) {
-      parser[type] = function(node) {
-        let globals = handler[type](node, context.getAncestors());
-        helpers.addGlobals(globals, globalScope);
-      }
-    }
-
-    return parser;
-  }
-};
--- a/testing/eslint-plugin-mozilla/lib/helpers.js
+++ b/testing/eslint-plugin-mozilla/lib/helpers.js
@@ -21,17 +21,16 @@ var definitions = [
   /^XPCOMUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
   /^XPCOMUtils\.defineLazyServiceGetter\(this, "(\w+)"/,
   /^XPCOMUtils\.defineConstant\(this, "(\w+)"/,
   /^DevToolsUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
   /^DevToolsUtils\.defineLazyGetter\(this, "(\w+)"/,
   /^Object\.defineProperty\(this, "(\w+)"/,
   /^Reflect\.defineProperty\(this, "(\w+)"/,
   /^this\.__defineGetter__\("(\w+)"/,
-  /^this\.(\w+) =/
 ];
 
 var imports = [
   /^(?:Cu|Components\.utils)\.import\(".*\/(.*?)\.jsm?"(?:, this)?\)/,
 ];
 
 module.exports = {
   /**
@@ -79,91 +78,27 @@ module.exports = {
       case "ObjectExpression":
         return "{}";
       case "ExpressionStatement":
         return this.getASTSource(node.expression) + ";";
       case "FunctionExpression":
         return "function() {}";
       case "ArrowFunctionExpression":
         return "() => {}";
-      case "AssignmentExpression":
-        return this.getASTSource(node.left) + " = " + this.getASTSource(node.right);
       default:
         throw new Error("getASTSource unsupported node type: " + node.type);
     }
   },
 
   /**
-   * This walks an AST in a manner similar to ESLint passing node and comment
-   * events to the listener. The listener is expected to be a simple function
-   * which accepts node type, node and parents arguments.
-   *
-   * @param  {Object} ast
-   *         The AST to walk.
-   * @param  {Function} listener
-   *         A callback function to call for the nodes. Passed three arguments,
-   *         event type, node and an array of parent nodes for the current node.
-   */
-  walkAST(ast, listener) {
-    let parents = [];
-
-    let seenComments = new Set();
-    function sendCommentEvents(comments) {
-      if (!comments) {
-        return;
-      }
-
-      for (let comment of comments) {
-        if (seenComments.has(comment)) {
-          return;
-        }
-        seenComments.add(comment);
-
-        listener(comment.type + "Comment", comment, parents);
-      }
-    }
-
-    estraverse.traverse(ast, {
-      enter(node, parent) {
-        // Comments are held in node.comments for empty programs
-        let leadingComments = node.leadingComments;
-        if (node.type === "Program" && node.body.length == 0) {
-          leadingComments = node.comments;
-        }
-
-        sendCommentEvents(leadingComments);
-        listener(node.type, node, parents);
-        sendCommentEvents(node.trailingComments);
-
-        parents.push(node);
-      },
-
-      leave(node, parent) {
-        // TODO send comment exit events
-        listener(node.type + ":exit", node, parents);
-
-        if (parents.length == 0) {
-          throw new Error("Left more nodes than entered.");
-        }
-        parents.pop();
-      }
-    });
-    if (parents.length) {
-      throw new Error("Entered more nodes than left.");
-    }
-  },
-
-  /**
    * Attempts to convert an ExpressionStatement to a likely global variable
    * definition.
    *
    * @param  {Object} node
    *         The AST node to convert.
-   * @param  {boolean} isGlobal
-   *         True if the current node is in the global scope
    *
    * @return {String or null}
    *         The variable name defined.
    */
   convertExpressionToGlobal: function(node, isGlobal) {
     try {
       var source = this.getASTSource(node);
     }
@@ -194,71 +129,203 @@ module.exports = {
         return match[1];
       }
     }
 
     return null;
   },
 
   /**
+   * Walks over an AST and calls a callback for every ExpressionStatement found.
+   *
+   * @param  {Object} ast
+   *         The AST to traverse.
+   *
+   * @return {Function}
+   *         The callback to call for each ExpressionStatement.
+   */
+  expressionTraverse: function(ast, callback) {
+    var helpers = this;
+    var parents = new Map();
+
+    // Walk the parents of a node to see if any are functions
+    function isGlobal(node) {
+      var parent = parents.get(node);
+      while (parent) {
+        if (parent.type == "FunctionExpression" ||
+            parent.type == "FunctionDeclaration") {
+          return false;
+        }
+        parent = parents.get(parent);
+      }
+      return true;
+    }
+
+    estraverse.traverse(ast, {
+      enter: function(node, parent) {
+        parents.set(node, parent);
+
+        if (node.type == "ExpressionStatement") {
+          callback(node, isGlobal(node));
+        }
+      }
+    });
+  },
+
+  /**
+   * Get an array of globals from an AST.
+   *
+   * @param  {Object} ast
+   *         The AST for which the globals are to be returned.
+   *
+   * @return {Array}
+   *         An array of variable names.
+   */
+  getGlobals: function(ast) {
+    var scopeManager = escope.analyze(ast);
+    var globalScope = scopeManager.acquire(ast);
+    var result = [];
+
+    for (var variable in globalScope.variables) {
+      var name = globalScope.variables[variable].name;
+      result.push(name);
+    }
+
+    var helpers = this;
+    this.expressionTraverse(ast, function(node, isGlobal) {
+      var name = helpers.convertExpressionToGlobal(node, isGlobal);
+
+      if (name) {
+        result.push(name);
+      }
+    });
+
+    return result;
+  },
+
+  /**
    * Add a variable to the current scope.
    * HACK: This relies on eslint internals so it could break at any time.
    *
    * @param {String} name
-   *        The variable name to add to the scope.
-   * @param {ASTScope} scope
-   *        The scope to add to.
-   * @param {boolean} writable
-   *        Whether the global can be overwritten.
+   *        The variable name to add to the current scope.
+   * @param {ASTContext} context
+   *        The current context.
    */
-  addVarToScope: function(name, scope, writable) {
-    // If the variable is already defined then skip it
-    if (scope.set && scope.set.has(name)) {
-      return;
-    }
-
-    writable = writable === undefined ? true : writable;
+  addVarToScope: function(name, context) {
+    var scope = context.getScope();
     var variables = scope.variables;
     var variable = new escope.Variable(name, scope);
 
     variable.eslintExplicitGlobal = false;
-    variable.writeable = writable;
+    variable.writeable = true;
     variables.push(variable);
 
     // Since eslint 1.10.3, scope variables are now duplicated in the scope.set
     // map, so we need to store them there too if it exists.
     // See https://groups.google.com/forum/#!msg/eslint/Y4_oHMWwP-o/5S57U8jXd8kJ
     if (scope.set) {
       scope.set.set(name, variable);
     }
   },
 
+  // Caches globals found in a file so we only have to parse a file once.
+  globalCache: new Map(),
+
   /**
-   * Adds a set of globals to a scope.
+   * Finds all the globals defined in a given file.
+   *
+   * @param {String} fileName
+   *        The file to parse for globals.
+   */
+  getGlobalsForFile: function(fileName) {
+    // If the file can't be found, let the error go up to the caller so it can
+    // be logged as an error in the current file.
+    var content = fs.readFileSync(fileName, "utf8");
+
+    if (this.globalCache.has(fileName)) {
+      return this.globalCache.get(fileName);
+    }
+
+    // Parse the content and get the globals from the ast.
+    var ast = this.getAST(content);
+    var globalVars = this.getGlobals(ast);
+    this.globalCache.set(fileName, globalVars);
+
+    return globalVars;
+  },
+
+  /**
+   * Adds a set of globals to a context.
    *
    * @param {Array} globalVars
    *        An array of global variable names.
-   * @param {ASTScope} scope
-   *        The scope.
+   * @param {ASTContext} context
+   *        The current context.
+   */
+  addGlobals: function(globalVars, context) {
+    for (var i = 0; i < globalVars.length; i++) {
+      var varName = globalVars[i];
+      this.addVarToScope(varName, context);
+    }
+  },
+
+  /**
+   * Process comments looking for import-globals-from statements.  Add globals
+   * from those files, if any.
+   *
+   * @param {String} currentFilePath
+   *        Absolute path to the file containing the comments.
+   * @param {Array} comments
+   *        The comments to be processed.
+   * @param {Object} node
+   *        The AST node for error reporting.
+   * @param {ASTContext} context
+   *        The current context.
    */
-  addGlobals: function(globalVars, scope) {
-    globalVars.forEach(v => this.addVarToScope(v.name, scope, v.writable));
+  addGlobalsFromComments: function(currentFilePath, comments, node, context) {
+    comments.forEach(comment => {
+      var value = comment.value.trim();
+      var match = /^import-globals-from\s+(.*)$/.exec(value);
+
+      if (match) {
+        var filePath = match[1];
+
+        if (!path.isAbsolute(filePath)) {
+          var dirName = path.dirname(currentFilePath);
+          filePath = path.resolve(dirName, filePath);
+        }
+
+        try {
+          let globals = this.getGlobalsForFile(filePath);
+          this.addGlobals(globals, context);
+        } catch (e) {
+          context.report(
+            node,
+            "Could not load globals from file {{filePath}}: {{error}}",
+            {
+              filePath: filePath,
+              error: e
+            }
+          );
+        }
+      }
+    });
   },
 
   /**
    * To allow espree to parse almost any JavaScript we need as many features as
    * possible turned on. This method returns that config.
    *
    * @return {Object}
    *         Espree compatible permissive config.
    */
   getPermissiveConfig: function() {
     return {
       comment: true,
-      attachComment: true,
       range: true,
       loc: true,
       tolerant: true,
       ecmaFeatures: {
         arrowFunctions: true,
         binaryLiterals: true,
         blockBindings: true,
         classes: true,
@@ -282,62 +349,31 @@ module.exports = {
         unicodeCodePointEscapes: true,
       }
     };
   },
 
   /**
    * Check whether the context is the global scope.
    *
-   * @param {Array} ancestors
-   *        The parents of the current node.
+   * @param {ASTContext} context
+   *        The current context.
    *
    * @return {Boolean}
    *         True or false
    */
-  getIsGlobalScope: function(ancestors) {
-    for (let parent of ancestors) {
-      if (parent.type == "FunctionExpression" ||
-          parent.type == "FunctionDeclaration") {
-        return false;
-      }
-    }
-    return true;
-  },
+  getIsGlobalScope: function(context) {
+    var ancestors = context.getAncestors();
+    var parent = ancestors.pop();
 
-  /**
-   * Check whether we might be in a test head file.
-   *
-   * @param  {RuleContext} scope
-   *         You should pass this from within a rule
-   *         e.g. helpers.getIsHeadFile(this)
-   *
-   * @return {Boolean}
-   *         True or false
-   */
-  getIsHeadFile: function(scope) {
-    var pathAndFilename = scope.getFilename();
+    if (parent.type == "ExpressionStatement") {
+      parent = ancestors.pop();
+    }
 
-    return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
-  },
-
-  /**
-   * Check whether we might be in an xpcshell test.
-   *
-   * @param  {RuleContext} scope
-   *         You should pass this from within a rule
-   *         e.g. helpers.getIsXpcshellTest(this)
-   *
-   * @return {Boolean}
-   *         True or false
-   */
-  getIsXpcshellTest: function(scope) {
-    var pathAndFilename = scope.getFilename();
-
-    return /.*[\\/]test_.+\.js$/.test(pathAndFilename);
+    return parent.type == "Program";
   },
 
   /**
    * Check whether we are in a browser mochitest.
    *
    * @param  {RuleContext} scope
    *         You should pass this from within a rule
    *         e.g. helpers.getIsBrowserMochitest(this)
@@ -359,17 +395,17 @@ module.exports = {
    *         e.g. helpers.getIsTest(this)
    *
    * @return {Boolean}
    *         True or false
    */
   getIsTest: function(scope) {
     var pathAndFilename = scope.getFilename();
 
-    if (this.getIsXpcshellTest(scope)) {
+    if (/.*[\\/]test_.+\.js$/.test(pathAndFilename)) {
       return true;
     }
 
     return this.getIsBrowserMochitest(scope);
   },
 
   /**
    * Gets the root directory of the repository by walking up directories until
--- a/testing/eslint-plugin-mozilla/lib/index.js
+++ b/testing/eslint-plugin-mozilla/lib/index.js
@@ -13,29 +13,33 @@
 //------------------------------------------------------------------------------
 
 module.exports = {
   processors: {
     ".xml": require("../lib/processors/xbl-bindings"),
   },
   rules: {
     "balanced-listeners": require("../lib/rules/balanced-listeners"),
-    "import-globals": require("../lib/rules/import-globals"),
+    "components-imports": require("../lib/rules/components-imports"),
+    "import-globals-from": require("../lib/rules/import-globals-from"),
     "import-headjs-globals": require("../lib/rules/import-headjs-globals"),
     "import-browserjs-globals": require("../lib/rules/import-browserjs-globals"),
     "mark-test-function-used": require("../lib/rules/mark-test-function-used"),
     "no-aArgs": require("../lib/rules/no-aArgs"),
     "no-cpows-in-tests": require("../lib/rules/no-cpows-in-tests"),
     "reject-importGlobalProperties": require("../lib/rules/reject-importGlobalProperties"),
+    "this-top-level-scope": require("../lib/rules/this-top-level-scope.js"),
     "var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
   },
   rulesConfig: {
     "balanced-listeners": 0,
-    "import-globals": 0,
+    "components-imports": 0,
+    "import-globals-from": 0,
     "import-headjs-globals": 0,
     "import-browserjs-globals": 0,
     "mark-test-function-used": 0,
     "no-aArgs": 0,
     "no-cpows-in-tests": 0,
     "reject-importGlobalProperties": 0,
+    "this-top-level-scope": 0,
     "var-only-at-top-level": 0
   }
 };
--- a/testing/eslint-plugin-mozilla/lib/processors/xbl-bindings.js
+++ b/testing/eslint-plugin-mozilla/lib/processors/xbl-bindings.js
@@ -31,169 +31,134 @@ function parseError(err) {
 let entityRegex = /&[\w][\w-\.]*;/g;
 
 // A simple sax listener that generates a tree of element information
 function XMLParser(parser) {
   this.parser = parser;
   parser.onopentag = this.onOpenTag.bind(this);
   parser.onclosetag = this.onCloseTag.bind(this);
   parser.ontext = this.onText.bind(this);
-  parser.onopencdata = this.onOpenCDATA.bind(this);
   parser.oncdata = this.onCDATA.bind(this);
-  parser.oncomment = this.onComment.bind(this);
 
   this.document = {
     local: "#document",
     uri: null,
     children: [],
-    comments: [],
   }
   this._currentNode = this.document;
 }
 
 XMLParser.prototype = {
   parser: null,
 
   onOpenTag: function(tag) {
     let node = {
       parentNode: this._currentNode,
       local: tag.local,
       namespace: tag.uri,
       attributes: {},
       children: [],
-      comments: [],
       textContent: "",
-      textLine: this.parser.line,
-      textColumn: this.parser.column,
-      textEndLine: this.parser.line
+      textStart: { line: this.parser.line, column: this.parser.column },
     }
 
     for (let attr of Object.keys(tag.attributes)) {
       if (tag.attributes[attr].uri == "") {
         node.attributes[attr] = tag.attributes[attr].value;
       }
     }
 
     this._currentNode.children.push(node);
     this._currentNode = node;
   },
 
   onCloseTag: function(tagname) {
-    this._currentNode.textEndLine = this.parser.line;
     this._currentNode = this._currentNode.parentNode;
   },
 
   addText: function(text) {
     this._currentNode.textContent += text;
   },
 
   onText: function(text) {
     // Replace entities with some valid JS token.
     this.addText(text.replace(entityRegex, "null"));
   },
 
-  onOpenCDATA: function() {
+  onCDATA: function(text) {
     // Turn the CDATA opening tag into whitespace for indent alignment
     this.addText(" ".repeat("<![CDATA[".length));
-  },
-
-  onCDATA: function(text) {
     this.addText(text);
-  },
-
-  onComment: function(text) {
-    this._currentNode.comments.push(text);
   }
 }
 
-// -----------------------------------------------------------------------------
-// Processor Definition
-// -----------------------------------------------------------------------------
-
-const INDENT_LEVEL = 2;
-
-function indent(count) {
-  return " ".repeat(count * INDENT_LEVEL);
-}
-
-// Stores any XML parse error
-let xmlParseError = null;
+// Strips the indentation from lines of text and adds a fixed two spaces indent
+function buildCodeBlock(tag, prefix, suffix, indent) {
+  prefix = prefix === undefined ? "" : prefix;
+  suffix = suffix === undefined ? "\n}" : suffix;
+  indent = indent === undefined ? 2 : indent;
 
-// Stores the lines of JS code generated from the XBL
-let scriptLines = [];
-// Stores a map from the synthetic line number to the real line number
-// and column offset.
-let lineMap = [];
-
-function addSyntheticLine(line, linePos) {
-  lineMap[scriptLines.length] = { line: linePos, offset: null };
-  scriptLines.push(line);
-}
+  let text = tag.textContent;
+  let line = tag.textStart.line;
+  let column = tag.textStart.column;
 
-/**
- * Adds generated lines from an XBL node to the script to be passed back to eslint.
- */
-function addNodeLines(node, reindent) {
-  let lines = node.textContent.split("\n");
-  let startLine = node.textLine;
-  let startColumn = node.textColumn;
-
-  // The case where there are no whitespace lines before the first text is
-  // treated differently for indentation
-  let indentFirst = false;
+  let lines = text.split("\n");
 
   // Strip off any preceeding whitespace only lines. These are often used to
   // format the XML and CDATA blocks.
   while (lines.length && lines[0].trim() == "") {
-    indentFirst = true;
-    startLine++;
+    column = 0;
+    line++;
     lines.shift();
   }
 
   // Strip off any whitespace lines at the end. These are often used to line
   // up the closing tags
   while (lines.length && lines[lines.length - 1].trim() == "") {
     lines.pop();
   }
 
-  if (!indentFirst) {
-    let firstLine = lines.shift();
-    firstLine = " ".repeat(reindent * INDENT_LEVEL) + firstLine;
-    // ESLint counts columns starting at 1 rather than 0
-    lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (startColumn - 1) };
-    scriptLines.push(firstLine);
-    startLine++;
+  // Indent the first line with the starting position of the text block
+  if (lines.length && column) {
+    lines[0] = " ".repeat(column) + lines[0];
   }
 
   // Find the preceeding whitespace for all lines that aren't entirely whitespace
   let indents = lines.filter(s => s.trim().length > 0)
                      .map(s => s.length - s.trimLeft().length);
   // Find the smallest indent level in use
   let minIndent = Math.min.apply(null, indents);
 
-  for (let line of lines) {
-    if (line.trim().length == 0) {
-      // Don't offset lines that are only whitespace, the only possible JS error
-      // is trailing whitespace and we want it to point at the right place
-      lineMap[scriptLines.length] = { line: startLine, offset: 0 };
-    } else {
-      line = " ".repeat(reindent * INDENT_LEVEL) + line.substring(minIndent);
-      lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (minIndent - 1) };
-    }
+  // Strip off the found indent level and prepend the new indent level, but only
+  // if the string isn't already empty.
+  let indentstr = " ".repeat(indent);
+  lines = lines.map(s => s.length ? indentstr + s.substring(minIndent) : s);
 
-    scriptLines.push(line);
-    startLine++;
-  }
+  let block = {
+    script: prefix + lines.join("\n") + suffix + "\n",
+    line: line - prefix.split("\n").length,
+    indent: minIndent - indent
+  };
+  return block;
 }
 
+// -----------------------------------------------------------------------------
+// Processor Definition
+// -----------------------------------------------------------------------------
+
+// Stores any XML parse error
+let xmlParseError = null;
+
+// Stores the code blocks
+let blocks = [];
+
 module.exports = {
   preprocess: function(text, filename) {
     xmlParseError = null;
-    scriptLines = [];
-    lineMap = [];
+    blocks = [];
 
     // Non-strict allows us to ignore many errors from entities and
     // preprocessing at the expense of failing to report some XML errors.
     // Unfortunately it also throws away the case of tagnames and attributes
     let parser = sax.parser(false, {
       lowercase: true,
       xmlns: true,
     });
@@ -211,150 +176,104 @@ module.exports = {
       return [];
     }
 
     let bindings = document.children[0];
     if (bindings.local != "bindings" || bindings.namespace != NS_XBL) {
       return [];
     }
 
-    for (let comment of document.comments) {
-      addSyntheticLine(`/*`, 0);
-      for (let line of comment.split("\n")) {
-        addSyntheticLine(`${line.trim()}`, 0);
-      }
-      addSyntheticLine(`*/`, 0);
-    }
-
-    addSyntheticLine(`var bindings = {`, bindings.textLine);
+    let scripts = [];
 
     for (let binding of bindings.children) {
       if (binding.local != "binding" || binding.namespace != NS_XBL) {
         continue;
       }
 
-      addSyntheticLine(indent(1) + `"${binding.attributes.id}": {`, binding.textLine);
-
       for (let part of binding.children) {
         if (part.namespace != NS_XBL) {
           continue;
         }
 
-        if (part.local == "implementation") {
-          addSyntheticLine(indent(2) + `implementation: {`, part.textLine);
-        } else if (part.local == "handlers") {
-          addSyntheticLine(indent(2) + `handlers: [`, part.textLine);
-        } else {
+        if (part.local != "implementation" && part.local != "handlers") {
           continue;
         }
 
         for (let item of part.children) {
           if (item.namespace != NS_XBL) {
             continue;
           }
 
           switch (item.local) {
             case "field": {
-              // Fields are something like lazy getter functions
+              // Fields get converted into variable declarations
 
               // Ignore empty fields
               if (item.textContent.trim().length == 0) {
                 continue;
               }
-
-              addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, item.textLine);
-              // TODO This will probably break some style rules when. We need
-              // to inject this next to the first non-whitespace character
-              addSyntheticLine(indent(4) + `return`, item.textLine);
-              addNodeLines(item, 4);
-              addSyntheticLine(indent(3) + `},`, item.textEndLine);
+              blocks.push(buildCodeBlock(item, "", "", 0));
               break;
             }
             case "constructor":
             case "destructor": {
               // Constructors and destructors become function declarations
-              addSyntheticLine(indent(3) + `${item.local}() {`, item.textLine);
-              addNodeLines(item, 4);
-              addSyntheticLine(indent(3) + `},`, item.textEndLine);
+              blocks.push(buildCodeBlock(item, `function ${item.local}() {\n`));
               break;
             }
             case "method": {
               // Methods become function declarations with the appropriate params
-
               let params = item.children.filter(n => n.local == "parameter" && n.namespace == NS_XBL)
                                         .map(n => n.attributes.name)
                                         .join(", ");
               let body = item.children.filter(n => n.local == "body" && n.namespace == NS_XBL)[0];
-
-              addSyntheticLine(indent(3) + `${item.attributes.name}(${params}) {`, item.textLine);
-              addNodeLines(body, 4);
-              addSyntheticLine(indent(3) + `},`, item.textEndLine);
+              blocks.push(buildCodeBlock(body, `function ${item.attributes.name}(${params}) {\n`));
               break;
             }
             case "property": {
               // Properties become one or two function declarations
               for (let propdef of item.children) {
                 if (propdef.namespace != NS_XBL) {
                   continue;
                 }
 
-                if (propdef.local == "setter") {
-                  addSyntheticLine(indent(3) + `set ${item.attributes.name}(val) {`, propdef.textLine);
-                } else if (propdef.local == "getter") {
-                  addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, propdef.textLine);
-                } else {
-                  continue;
-                }
-                addNodeLines(propdef, 4);
-                addSyntheticLine(indent(3) + `},`, propdef.textEndLine);
+                let params = propdef.local == "setter" ? "val" : "";
+                blocks.push(buildCodeBlock(propdef, `function ${item.attributes.name}_${propdef.local}(${params}) {\n`));
               }
               break;
             }
             case "handler": {
               // Handlers become a function declaration with an `event` parameter
-              addSyntheticLine(indent(3) + `function(event) {`, item.textLine);
-              addNodeLines(item, 4);
-              addSyntheticLine(indent(3) + `},`, item.textEndLine);
+              blocks.push(buildCodeBlock(item, `function onevent(event) {\n`));
               break;
             }
             default:
               continue;
           }
         }
-
-        addSyntheticLine(indent(2) + (part.local == "implementation" ? `},` : `],`), part.textEndLine);
       }
-      addSyntheticLine(indent(1) + `},`, binding.textEndLine);
     }
-    addSyntheticLine(`};`, bindings.textEndLine);
 
-    let script = scriptLines.join("\n") + "\n";
-    return [script];
+    return blocks.map(b => b.script);
   },
 
   postprocess: function(messages, filename) {
     // If there was an XML parse error then just return that
     if (xmlParseError) {
       return [xmlParseError];
     }
 
     // For every message from every script block update the line to point to the
     // correct place.
     let errors = [];
     for (let i = 0; i < messages.length; i++) {
-      for (let message of messages[i]) {
-        // ESLint indexes lines starting at 1 but our arrays start at 0
-        let mapped = lineMap[message.line - 1];
+      let block = blocks[i];
 
-        message.line = mapped.line + 1;
-        if (mapped.offset) {
-          message.column -= mapped.offset;
-        } else {
-          message.column = NaN;
-        }
-
+      for (let message of messages[i]) {
+        message.line += block.line + 1;
+        message.column += block.indent;
         errors.push(message);
       }
     }
 
     return errors;
   }
 };
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/lib/rules/components-imports.js
@@ -0,0 +1,33 @@
+/**
+ * @fileoverview Adds the filename of imported files e.g.
+ * Cu.import("some/path/Blah.jsm") adds Blah to the current scope.
+ *
+ * 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";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+  // ---------------------------------------------------------------------------
+  // Public
+  // ---------------------------------------------------------------------------
+
+  return {
+    ExpressionStatement: function(node) {
+      var scope = context.getScope();
+      var name = helpers.convertExpressionToGlobal(node, scope.type == "global");
+
+      if (name) {
+        helpers.addVarToScope(name, context);
+      }
+    }
+  };
+};
--- a/testing/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
@@ -10,22 +10,18 @@
 
 // -----------------------------------------------------------------------------
 // Rule Definition
 // -----------------------------------------------------------------------------
 
 var fs = require("fs");
 var path = require("path");
 var helpers = require("../helpers");
-var globals = require("../globals");
 
 const SCRIPTS = [
-  //"browser/base/content/nsContextMenu.js",
-  "toolkit/content/contentAreaUtils.js",
-  "browser/components/places/content/editBookmarkOverlay.js",
   "toolkit/components/printing/content/printUtils.js",
   "toolkit/content/viewZoomOverlay.js",
   "browser/components/places/content/browserPlacesViews.js",
   "browser/base/content/browser.js",
   "browser/components/downloads/content/downloads.js",
   "browser/components/downloads/content/indicator.js",
   "browser/components/customizableui/content/panelUI.js",
   "toolkit/obsolete/content/inlineSpellCheckUI.js",
@@ -45,34 +41,34 @@ const SCRIPTS = [
   "browser/base/content/browser-safebrowsing.js",
   "browser/base/content/browser-sidebar.js",
   "browser/base/content/browser-social.js",
   "browser/base/content/browser-syncui.js",
   "browser/base/content/browser-tabsintitlebar.js",
   "browser/base/content/browser-thumbnails.js",
   "browser/base/content/browser-trackingprotection.js",
   "browser/base/content/browser-data-submission-info-bar.js",
-  "browser/base/content/browser-fxaccounts.js"
+  "browser/base/content/browser-fxaccounts.js",
 ];
 
 module.exports = function(context) {
   return {
     Program: function(node) {
-      if (!helpers.getIsBrowserMochitest(this) &&
-          !helpers.getIsHeadFile(this)) {
+      if (!helpers.getIsBrowserMochitest(this)) {
         return;
       }
 
       let root = helpers.getRootDir(context);
       for (let script of SCRIPTS) {
         let fileName = path.join(root, script);
         try {
-          let newGlobals = globals.getGlobalsForFile(fileName);
-          helpers.addGlobals(newGlobals, context.getScope());
-        } catch (e) {
+          let globals = helpers.getGlobalsForFile(fileName);
+          helpers.addGlobals(globals, context);
+        }
+        catch (e) {
           context.report(
             node,
             "Could not load globals from file {{filePath}}: {{error}}",
             {
               filePath: path.relative(root, fileName),
               error: e
             }
           );
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-globals-from.js
@@ -0,0 +1,33 @@
+/**
+ * @fileoverview When the "import-globals-from: <path>" comment is found in a
+ * file, then all globals from the file at <path> will be imported in the
+ * current scope.
+ *
+ * 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";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var fs = require("fs");
+var helpers = require("../helpers");
+var path = require("path");
+
+module.exports = function(context) {
+  // ---------------------------------------------------------------------------
+  // Public
+  // ---------------------------------------------------------------------------
+
+  return {
+    Program: function(node) {
+      var comments = context.getSourceCode().getAllComments();
+      var currentFilePath = helpers.getAbsoluteFilePath(context);
+      helpers.addGlobalsFromComments(currentFilePath, comments, node, context);
+    }
+  };
+};
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/lib/rules/import-globals.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @fileoverview Discovers all globals for the current file.
- *
- * 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";
-
-// -----------------------------------------------------------------------------
-// Rule Definition
-// -----------------------------------------------------------------------------
-
-module.exports = require("../globals").getESLintGlobalParser;
--- a/testing/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
@@ -11,53 +11,38 @@
 
 // -----------------------------------------------------------------------------
 // Rule Definition
 // -----------------------------------------------------------------------------
 
 var fs = require("fs");
 var path = require("path");
 var helpers = require("../helpers");
-var globals = require("../globals");
 
 module.exports = function(context) {
 
-  function importHead(path, node) {
-    try {
-      let stats = fs.statSync(path);
-      if (!stats.isFile()) {
-        return;
-      }
-    } catch (e) {
-      return;
-    }
-
-    let newGlobals = globals.getGlobalsForFile(path);
-    helpers.addGlobals(newGlobals, context.getScope());
-  }
-
   // ---------------------------------------------------------------------------
   // Public
   // ---------------------------------------------------------------------------
 
   return {
     Program: function(node) {
       if (!helpers.getIsTest(this)) {
         return;
       }
 
       var currentFilePath = helpers.getAbsoluteFilePath(context);
       var dirName = path.dirname(currentFilePath);
-      importHead(path.resolve(dirName, "head.js"), node);
-
-      if (!helpers.getIsXpcshellTest(this)) {
+      var fullHeadjsPath = path.resolve(dirName, "head.js");
+      if (!fs.existsSync(fullHeadjsPath)) {
         return;
       }
 
-      let names = fs.readdirSync(dirName);
-      for (let name of names) {
-        if (name.startsWith("head_") && name.endsWith(".js")) {
-          importHead(path.resolve(dirName, name), node);
-        }
-      }
+      let globals = helpers.getGlobalsForFile(fullHeadjsPath);
+      helpers.addGlobals(globals, context);
+
+      // Also add any globals from import-globals-from comments
+      var content = fs.readFileSync(fullHeadjsPath, "utf8");
+      var comments = helpers.getAST(content).comments;
+      helpers.addGlobalsFromComments(fullHeadjsPath, comments, node, context);
     }
   };
 };
--- a/testing/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js
@@ -51,17 +51,17 @@ module.exports = function(context) {
       // |cpows| reports one, don't report another below.
       var someCpowFound = cpows.some(function(cpow) {
         if (cpow.test(expression)) {
           showError(node, expression);
           return true;
         }
         return false;
       });
-      if (!someCpowFound && helpers.getIsGlobalScope(context.getAncestors())) {
+      if (!someCpowFound && helpers.getIsGlobalScope(context)) {
         if (/^content\./.test(expression)) {
           showError(node, expression);
           return;
         }
       }
     },
 
     Identifier: function(node) {
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/lib/rules/this-top-level-scope.js
@@ -0,0 +1,33 @@
+/**
+ * @fileoverview Marks "this.var = x" as top-level definition of "var".
+ *
+ * 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";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var helpers = require("../helpers");
+
+module.exports = function(context) {
+
+  // ---------------------------------------------------------------------------
+  // Public
+  //  --------------------------------------------------------------------------
+
+  return {
+    "AssignmentExpression": function(node) {
+      if (helpers.getIsGlobalScope(context) &&
+          node.left.type === "MemberExpression" &&
+          node.left.object.type === "ThisExpression" &&
+          node.left.property.type === "Identifier") {
+        helpers.addGlobals([node.left.property.name], context);
+      }
+    }
+  };
+};
--- a/testing/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
@@ -18,17 +18,17 @@ var helpers = require("../helpers");
 module.exports = function(context) {
   // ---------------------------------------------------------------------------
   // Public
   //  --------------------------------------------------------------------------
 
   return {
     "VariableDeclaration": function(node) {
       if (node.kind === "var") {
-        if (helpers.getIsGlobalScope(context.getAncestors())) {
+        if (helpers.getIsGlobalScope(context)) {
           return;
         }
 
         context.report(node, "Unexpected var, use let or const instead.");
       }
     }
   };
 };
--- a/testing/mochitest/browser.eslintrc
+++ b/testing/mochitest/browser.eslintrc
@@ -9,26 +9,21 @@
     "browser": true,
   },
 
   // All globals made available in the test environment.
   "globals": {
     "add_task": false,
     "Assert": false,
     "BrowserTestUtils": false,
-    "content": false,
     "ContentTask": false,
-    "ContentTaskUtils": false,
     "EventUtils": false,
     "executeSoon": false,
-    "expectUncaughtException": false,
     "export_assertions": false,
-    "extractJarToTmp": false,
     "finish": false,
-    "getJar": false,
     "getRootDirectory": false,
     "getTestFilePath": false,
     "gTestPath": false,
     "info": false,
     "is": false,
     "isnot": false,
     "ok": false,
     "registerCleanupFunction": false,
@@ -37,10 +32,15 @@
     "SpecialPowers": false,
     "thisTestLeaksUncaughtRejectionsAndShouldBeFixed": false,
     "todo": false,
     "todo_is": false,
     "todo_isnot": false,
     "waitForClipboard": false,
     "waitForExplicitFinish": false,
     "waitForFocus": false,
+    "gBrowser": false,
+    "gNavToolbox": false,
+    "gURLBar": false,
+    "gNavigatorBundle": false,
+    "content": false,
   }
 }
--- a/testing/xpcshell/xpcshell.eslintrc
+++ b/testing/xpcshell/xpcshell.eslintrc
@@ -19,28 +19,23 @@
     "do_get_cwd": false,
     "do_get_file": false,
     "do_get_idle": false,
     "do_get_profile": false,
     "do_load_module": false,
     "do_parse_document": false,
     "do_print": false,
     "do_register_cleanup": false,
-    "do_report_unexpected_exception": false,
     "do_test_finished": false,
     "do_test_pending": false,
     "do_throw": false,
     "do_timeout": false,
     "equal": false,
     "load": false,
-    "mozinfo": false,
     "notDeepEqual": false,
     "notEqual": false,
     "notStrictEqual": false,
     "ok": false,
     "run_next_test": false,
     "run_test": false,
     "strictEqual": false,
-    "todo": false,
-    "todo_check_false": false,
-    "todo_check_true": false,
   }
 }
--- a/toolkit/.eslintrc
+++ b/toolkit/.eslintrc
@@ -187,20 +187,10 @@
     // ++ and -- should not need spacing
     // "space-unary-ops": [2, { "words": true, "nonwords": false }],
 
     // No comparisons to NaN
     "use-isnan": 2,
 
     // Only check typeof against valid results
     "valid-typeof": 2,
-  },
-  "env": {
-    "es6": true,
-    "browser": true,
-  },
-  "globals": {
-    "Components": false,
-    "dump": true,
-    "openDialog": false,
-    "sizeToContent": false,
   }
 }
--- a/toolkit/components/extensions/.eslintrc
+++ b/toolkit/components/extensions/.eslintrc
@@ -24,16 +24,18 @@
     "Services": true,
     "TabManager": true,
     "XPCOMUtils": true,
   },
 
   "rules": {
     // Rules from the mozilla plugin
     "mozilla/balanced-listeners": 2,
+    "mozilla/components-imports": 1,
+    "mozilla/import-headjs-globals": 1,
     "mozilla/mark-test-function-used": 1,
     "mozilla/no-aArgs": 1,
     "mozilla/no-cpows-in-tests": 1,
     "mozilla/var-only-at-top-level": 1,
 
     // Braces only needed for multi-line arrow function blocks
     // "arrow-body-style": [2, "as-needed"],
 
--- a/toolkit/content/contentAreaUtils.js
+++ b/toolkit/content/contentAreaUtils.js
@@ -1,11 +1,12 @@
-/* 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/. */
+# -*- 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/.
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadLastDir",
@@ -19,18 +20,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                   "resource://gre/modules/Deprecated.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
-                                  "resource://gre/modules/AppConstants.jsm");
 
 var ContentAreaUtils = {
 
   // this is for backwards compatibility.
   get ioService() {
     return Services.io;
   },
 
@@ -347,17 +346,17 @@ XPCOMUtils.defineConstant(this, "kSaveAs
  *    aShouldBypassCache and aReferrer. The source, the save name and the save
  *    mode are the ones determined previously.
  *
  * @param aURL
  *        The String representation of the URL of the document being saved
  * @param aDocument
  *        The document to be saved
  * @param aDefaultFileName
- *        The caller-provided suggested filename if we don't
+ *        The caller-provided suggested filename if we don't 
  *        find a better one
  * @param aContentDisposition
  *        The caller-provided content-disposition header to use.
  * @param aContentType
  *        The caller-provided content-type to use
  * @param aShouldBypassCache
  *        If true, the document will always be refetched from the server
  * @param aFilePickerTitleKey
@@ -589,17 +588,17 @@ function AutoChosen(aFileAutoChosen, aUr
   this.file = aFileAutoChosen;
   this.uri  = aUriAutoChosen;
 }
 
 /**
  * Structure for holding info about a URL and the target filename it should be
  * saved to. This structure is populated by initFileInfo(...).
  * @param aSuggestedFileName This is used by initFileInfo(...) when it
- *        cannot 'discover' the filename from the url
+ *        cannot 'discover' the filename from the url 
  * @param aFileName The target filename
  * @param aFileBaseName The filename without the file extension
  * @param aFileExt The extension of the filename
  * @param aUri An nsIURI object for the url that is being saved
  */
 function FileInfo(aSuggestedFileName, aFileName, aFileBaseName, aFileExt, aUri) {
   this.suggestedFileName = aSuggestedFileName;
   this.fileName = aFileName;
@@ -648,40 +647,40 @@ function initFileInfo(aFI, aURL, aURLCha
     } else {
       aFI.fileExt = getDefaultExtension(aFI.fileName, aFI.uri, aContentType);
       aFI.fileBaseName = getFileBaseName(aFI.fileName);
     }
   } catch (e) {
   }
 }
 
-/**
+/** 
  * Given the Filepicker Parameters (aFpP), show the file picker dialog,
  * prompting the user to confirm (or change) the fileName.
  * @param aFpP
  *        A structure (see definition in internalSave(...) method)
  *        containing all the data used within this method.
  * @param aSkipPrompt
  *        If true, attempt to save the file automatically to the user's default
  *        download directory, thus skipping the explicit prompt for a file name,
  *        but only if the associated preference is set.
  *        If false, don't save the file automatically to the user's
  *        default download directory, even if the associated preference
  *        is set, but ask for the target explicitly.
  * @param aRelatedURI
  *        An nsIURI associated with the download. The last used
- *        directory of the picker is retrieved from/stored in the
+ *        directory of the picker is retrieved from/stored in the 
  *        Content Pref Service using this URI.
  * @return Promise
  * @resolve a boolean. When true, it indicates that the file picker dialog
  *          is accepted.
  */
 function promiseTargetFile(aFpP, /* optional */ aSkipPrompt, /* optional */ aRelatedURI)
 {
-  return Task.spawn(function*() {
+  return Task.spawn(function() {
     let downloadLastDir = new DownloadLastDir(window);
     let prefBranch = Services.prefs.getBranch("browser.download.");
     let useDownloadDir = prefBranch.getBoolPref("useDownloadDir");
 
     if (!aSkipPrompt)
       useDownloadDir = false;
 
     // Default to the user's default downloads directory configured
@@ -791,16 +790,17 @@ function uniqueFile(aLocalFile)
     else {
       // replace the last (n) in the filename with (n+1)
       aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount + 1) + ")");
     }
   }
   return aLocalFile;
 }
 
+#ifdef MOZ_JSDOWNLOADS
 /**
  * Download a URL using the new jsdownloads API.
  *
  * @param aURL
  *        the url to download
  * @param [optional] aFileName
  *        the destination file name, if omitted will be obtained from the url.
  * @param aInitiatingDocument
@@ -836,16 +836,17 @@ function DownloadURL(aURL, aFileName, aI
     download.tryToKeepPartialData = true;
     download.start();
 
     // Add the download to the list, allowing it to be managed.
     let list = yield Downloads.getList(Downloads.ALL);
     list.add(download);
   }).then(null, Components.utils.reportError);
 }
+#endif
 
 // We have no DOM, and can only save the URL as is.
 const SAVEMODE_FILEONLY      = 0x00;
 XPCOMUtils.defineConstant(this, "SAVEMODE_FILEONLY", SAVEMODE_FILEONLY);
 // We have a DOM and can save as complete.
 const SAVEMODE_COMPLETE_DOM  = 0x01;
 XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_DOM", SAVEMODE_COMPLETE_DOM);
 // We have a DOM which we can serialize as text.
@@ -1151,20 +1152,20 @@ function validateFileName(aFileName)
   return aFileName.replace(re, "_");
 }
 
 function getNormalizedLeafName(aFile, aDefaultExtension)
 {
   if (!aDefaultExtension)
     return aFile;
 
-  if (AppConstants.platform == "win") {
-    // Remove trailing dots and spaces on windows
-    aFile = aFile.replace(/[\s.]+$/, "");
-  }
+#ifdef XP_WIN
+  // Remove trailing dots and spaces on windows
+  aFile = aFile.replace(/[\s.]+$/, "");
+#endif
 
   // Remove leading dots
   aFile = aFile.replace(/^\.+/, "");
 
   // Fix up the file name we're saving to to include the default extension
   var i = aFile.lastIndexOf(".");
   if (aFile.substr(i + 1) != aDefaultExtension)
     return aFile + "." + aDefaultExtension;
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -38,17 +38,17 @@ toolkit.jar:
    content/global/aboutTelemetry.xhtml
    content/global/aboutTelemetry.css
    content/global/directionDetector.html
    content/global/plugins.html
    content/global/plugins.css
    content/global/browser-child.js
    content/global/browser-content.js
 *   content/global/buildconfig.html
-   content/global/contentAreaUtils.js
+*  content/global/contentAreaUtils.js
 #ifndef MOZ_FENNEC
    content/global/customizeToolbar.css
    content/global/customizeToolbar.js
    content/global/customizeToolbar.xul
 #endif
    content/global/devicestorage.properties
 #ifndef MOZ_FENNEC
    content/global/editMenuOverlay.js