Bug 968896 - Add tern support to source editor to provide autocompletion and type inference. r=vp, r=anton, r=fitzgen
☠☠ backed out by 09847ac2ab60 ☠ ☠
authorBrandon Benvie <bbenvie@mozilla.com>
Wed, 16 Apr 2014 10:46:55 -0700
changeset 179334 59f4f732f6d7969faff8894cbb97949f38d76aab
parent 179333 be14df51e362a840d94fc7826edcfc4639dc300d
child 179335 711ddfbb8bbf006ce4acdffe0c81454cc1548b69
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersvp, anton, fitzgen
bugs968896
milestone31.0a1
Bug 968896 - Add tern support to source editor to provide autocompletion and type inference. r=vp, r=anton, r=fitzgen
browser/app/profile/firefox.js
browser/devtools/jar.mn
browser/devtools/scratchpad/scratchpad.js
browser/devtools/scratchpad/test/browser.ini
browser/devtools/scratchpad/test/browser_scratchpad_autocomplete.js
browser/devtools/scratchpad/test/head.js
browser/devtools/sourceeditor/autocomplete.js
browser/devtools/sourceeditor/codemirror/mozilla.css
browser/devtools/sourceeditor/codemirror/show-hint.js
browser/devtools/sourceeditor/codemirror/tern.js
browser/devtools/sourceeditor/editor.js
browser/devtools/styleeditor/StyleSheetEditor.jsm
browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties
browser/themes/shared/devtools/common.css
browser/themes/shared/devtools/dark-theme.css
browser/themes/shared/devtools/light-theme.css
toolkit/devtools/Loader.jsm
toolkit/devtools/moz.build
toolkit/devtools/tern/browser.js
toolkit/devtools/tern/comment.js
toolkit/devtools/tern/condense.js
toolkit/devtools/tern/def.js
toolkit/devtools/tern/ecma5.js
toolkit/devtools/tern/infer.js
toolkit/devtools/tern/moz.build
toolkit/devtools/tern/signal.js
toolkit/devtools/tern/tern.js
toolkit/devtools/tern/tests/unit/head_tern.js
toolkit/devtools/tern/tests/unit/test_autocompletion.js
toolkit/devtools/tern/tests/unit/test_import_tern.js
toolkit/devtools/tern/tests/unit/xpcshell.ini
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1188,19 +1188,21 @@ pref("devtools.tilt.outro_transition", t
 
 // Scratchpad settings
 // - recentFileMax: The maximum number of recently-opened files
 //                  stored. Setting this preference to 0 will not
 //                  clear any recent files, but rather hide the
 //                  'Open Recent'-menu.
 // - showTrailingSpace: Whether to highlight trailing space or not.
 // - enableCodeFolding: Whether to enable code folding or not.
+// - enableAutocompletion: Whether to enable JavaScript autocompletion.
 pref("devtools.scratchpad.recentFilesMax", 10);
 pref("devtools.scratchpad.showTrailingSpace", false);
 pref("devtools.scratchpad.enableCodeFolding", true);
+pref("devtools.scratchpad.enableAutocompletion", true);
 
 // Enable the Style Editor.
 pref("devtools.styleeditor.enabled", true);
 pref("devtools.styleeditor.source-maps-enabled", false);
 pref("devtools.styleeditor.autocompletion-enabled", true);
 
 // Enable the Shader Editor.
 pref("devtools.shadereditor.enabled", false);
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -48,16 +48,18 @@ browser.jar:
     content/browser/devtools/codemirror/emacs.js                       (sourceeditor/codemirror/keymap/emacs.js)
     content/browser/devtools/codemirror/sublime.js                     (sourceeditor/codemirror/keymap/sublime.js)
     content/browser/devtools/codemirror/vim.js                         (sourceeditor/codemirror/keymap/vim.js)
     content/browser/devtools/codemirror/foldcode.js                    (sourceeditor/codemirror/fold/foldcode.js)
     content/browser/devtools/codemirror/brace-fold.js                  (sourceeditor/codemirror/fold/brace-fold.js)
     content/browser/devtools/codemirror/comment-fold.js                (sourceeditor/codemirror/fold/comment-fold.js)
     content/browser/devtools/codemirror/xml-fold.js                    (sourceeditor/codemirror/fold/xml-fold.js)
     content/browser/devtools/codemirror/foldgutter.js                  (sourceeditor/codemirror/fold/foldgutter.js)
+    content/browser/devtools/codemirror/tern.js                        (sourceeditor/codemirror/tern.js)
+    content/browser/devtools/codemirror/show-hint.js                   (sourceeditor/codemirror/show-hint.js)
     content/browser/devtools/codemirror/mozilla.css                    (sourceeditor/codemirror/mozilla.css)
     content/browser/devtools/debugger.xul                              (debugger/debugger.xul)
     content/browser/devtools/debugger.css                              (debugger/debugger.css)
     content/browser/devtools/debugger-controller.js                    (debugger/debugger-controller.js)
     content/browser/devtools/debugger-view.js                          (debugger/debugger-view.js)
     content/browser/devtools/debugger-toolbar.js                       (debugger/debugger-toolbar.js)
     content/browser/devtools/debugger-panes.js                         (debugger/debugger-panes.js)
     content/browser/devtools/shadereditor.xul                          (shadereditor/shadereditor.xul)
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -30,16 +30,17 @@ const MAXIMUM_FONT_SIZE = 96;
 const MINIMUM_FONT_SIZE = 6;
 const NORMAL_FONT_SIZE = 12;
 
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
 const SHOW_TRAILING_SPACE = "devtools.scratchpad.showTrailingSpace";
 const ENABLE_CODE_FOLDING = "devtools.scratchpad.enableCodeFolding";
+const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
 
 const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
 
 const require   = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 
 const Telemetry = require("devtools/shared/telemetry");
 const Editor    = require("devtools/sourceeditor/editor");
 const TargetFactory = require("devtools/framework/target").TargetFactory;
@@ -1600,39 +1601,41 @@ var Scratchpad = {
     } else {
       this._instanceId = ScratchpadManager.createUid();
     }
 
     let config = {
       mode: Editor.modes.js,
       value: initialText,
       lineNumbers: true,
+      contextMenu: "scratchpad-text-popup",
       showTrailingSpace: Services.prefs.getBoolPref(SHOW_TRAILING_SPACE),
       enableCodeFolding: Services.prefs.getBoolPref(ENABLE_CODE_FOLDING),
-      contextMenu: "scratchpad-text-popup"
+      autocomplete: Services.prefs.getBoolPref(ENABLE_AUTOCOMPLETION),
     };
 
     this.editor = new Editor(config);
     this.editor.appendTo(document.querySelector("#scratchpad-editor")).then(() => {
       var lines = initialText.split("\n");
 
+      this.editor.setupAutoCompletion();
       this.editor.on("change", this._onChanged);
       this.editor.on("save", () => this.saveFile());
       this.editor.focus();
       this.editor.setCursor({ line: lines.length, ch: lines.pop().length });
 
       if (state)
         this.dirty = !state.saved;
 
       this.initialized = true;
       this._triggerObservers("Ready");
       this.populateRecentFilesMenu();
       PreferenceObserver.init();
       CloseObserver.init();
-    }).then(null, (err) => console.log(err.message));
+    }).then(null, (err) => console.error(err));
     this._setupCommandListeners();
     this._setupPopupShowingListeners();
   },
 
   /**
    * The Source Editor "change" event handler. This function updates the
    * Scratchpad window title to show an asterisk when there are unsaved changes.
    *
--- a/browser/devtools/scratchpad/test/browser.ini
+++ b/browser/devtools/scratchpad/test/browser.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files = head.js
 
+[browser_scratchpad_autocomplete.js]
 [browser_scratchpad_browser_last_window_closing.js]
 [browser_scratchpad_reset_undo.js]
 [browser_scratchpad_display_outputs_errors.js]
 [browser_scratchpad_eval_func.js]
 [browser_scratchpad_goto_line_ui.js]
 [browser_scratchpad_reload_and_run.js]
 [browser_scratchpad_display_non_error_exceptions.js]
 [browser_scratchpad_modeline.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_autocomplete.js
@@ -0,0 +1,64 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* Bug 968896 */
+
+// Test the completions using numbers.
+const source = "0x1.";
+const completions = ["toExponential", "toFixed", "toString"];
+
+function test() {
+  const options = { tabContent: "test scratchpad autocomplete" };
+  openTabAndScratchpad(options)
+    .then(Task.async(runTests))
+    .then(finish, console.error);
+}
+
+
+function* runTests([win, sp]) {
+  const {editor} = sp;
+  const editorWin = editor.container.contentWindow;
+
+  // Show the completions popup.
+  sp.setText(source);
+  sp.editor.setCursor({ line: 0, ch: source.length });
+  yield keyOnce("suggestion-entered", " ", { ctrlKey: true });
+
+  // Get the hints popup container.
+  const hints = editorWin.document.querySelector(".CodeMirror-hints");
+  ok(hints,
+     "The hint container should exist.")
+  is(hints.childNodes.length, 3,
+     "The hint container should have the completions.");
+
+  let i = 0;
+  for (let completion of completions) {
+    let active = hints.querySelector(".CodeMirror-hint-active");
+    is(active.textContent, completion,
+       "Check that completion " + i++ + " is what is expected.");
+    yield keyOnce("suggestion-entered", "VK_DOWN");
+  }
+
+  // We should have looped around to the first suggestion again. Accept it.
+  yield keyOnce("after-suggest", "VK_RETURN");
+
+  is(sp.getText(), source + completions[0],
+     "Autocompletion should work and select the right element.");
+
+  // Check that the information tooltips work.
+  sp.setText("5");
+  yield keyOnce("show-information", " ", { shiftKey: true });
+
+  // Get the information container.
+  const info = editorWin.document.querySelector(".CodeMirror-Tern-information");
+  ok(info,
+     "Info tooltip should appear.");
+  is(info.textContent.slice(0, 6), "number",
+     "Info tooltip should have expected contents.");
+
+  function keyOnce(event, key, opts = {}) {
+    const p = editor.once(event);
+    EventUtils.synthesizeKey(key, opts, editorWin);
+    return p;
+  }
+}
--- a/browser/devtools/scratchpad/test/head.js
+++ b/browser/devtools/scratchpad/test/head.js
@@ -2,16 +2,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+const {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 
 let gScratchpadWindow; // Reference to the Scratchpad chrome window object
 
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
 
@@ -31,20 +33,18 @@ SimpleTest.registerCleanupFunction(() =>
  *        - noFocus
  *          Boolean that tells you do not want the opened window to receive
  *          focus.
  * @return nsIDOMWindow
  *         The new window object that holds Scratchpad. Note that the
  *         gScratchpadWindow global is also updated to reference the new window
  *         object.
  */
-function openScratchpad(aReadyCallback, aOptions)
+function openScratchpad(aReadyCallback, aOptions = {})
 {
-  aOptions = aOptions || {};
-
   let win = aOptions.window ||
             Scratchpad.ScratchpadManager.openScratchpad(aOptions.state);
   if (!win) {
     return;
   }
 
   let onLoad = function() {
     win.removeEventListener("load", onLoad, false);
@@ -66,16 +66,40 @@ function openScratchpad(aReadyCallback, 
     win.addEventListener("load", onLoad, false);
   }
 
   gScratchpadWindow = win;
   return gScratchpadWindow;
 }
 
 /**
+ * Open a new tab and then open a scratchpad.
+ * @param object aOptions
+ *        Optional. Options for opening the tab and scratchpad. In addition
+ *        to the options supported by openScratchpad, the following options
+ *        are supported:
+ *        - tabContent
+ *          A string providing the html content of the tab.
+ * @return Promise
+ */
+function openTabAndScratchpad(aOptions = {})
+{
+  waitForExplicitFinish();
+  return new promise(resolve => {
+    gBrowser.selectedTab = gBrowser.addTab();
+    let {selectedBrowser} = gBrowser;
+    selectedBrowser.addEventListener("load", function onLoad() {
+      selectedBrowser.removeEventListener("load", onLoad, true);
+      openScratchpad((win, sp) => resolve([win, sp]), aOptions);
+    }, true);
+    content.location = "data:text/html;charset=utf8," + (aOptions.tabContent || "");
+  });
+}
+
+/**
  * Create a temporary file, write to it and call a callback
  * when done.
  *
  * @param string aName
  *        Name of your temporary file.
  * @param string aContent
  *        Temporary file's contents.
  * @param function aCallback
@@ -175,17 +199,16 @@ function runAsyncCallbackTests(aScratchp
     } else {
       deferred.resolve();
     }
   })();
 
   return deferred.promise;
 }
 
-
 function cleanup()
 {
   if (gScratchpadWindow) {
     gScratchpadWindow.close();
     gScratchpadWindow = null;
   }
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
--- a/browser/devtools/sourceeditor/autocomplete.js
+++ b/browser/devtools/sourceeditor/autocomplete.js
@@ -1,45 +1,89 @@
 /* vim:set ts=2 sw=2 sts=2 et tw=80:
  * 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/. */
 
 const cssAutoCompleter = require("devtools/sourceeditor/css-autocompleter");
 const { AutocompletePopup } = require("devtools/shared/autocomplete-popup");
 
+const CM_TERN_SCRIPTS = [
+  "chrome://browser/content/devtools/codemirror/tern.js",
+  "chrome://browser/content/devtools/codemirror/show-hint.js"
+];
+
 const privates = new WeakMap();
 
 /**
- * Prepares an editor instance for autocompletion, setting up the popup and the
- * CSS completer instance.
+ * Prepares an editor instance for autocompletion.
  */
-function setupAutoCompletion(ctx, walker) {
+function setupAutoCompletion(ctx, options = {}) {
   let { cm, ed, Editor } = ctx;
 
+  if (!ed.config.autocomplete) {
+    return;
+  }
+
   let win = ed.container.contentWindow.wrappedJSObject;
+  let {CodeMirror} = win;
 
   let completer = null;
-  if (ed.config.mode == Editor.modes.css)
-    completer = new cssAutoCompleter({walker: walker});
+  if (ed.config.mode == Editor.modes.js) {
+    let defs = [
+      "tern/browser",
+      "tern/ecma5",
+    ].map(require);
+
+    CM_TERN_SCRIPTS.forEach(ed.loadScript, ed);
+    win.tern = require("tern/tern");
+    cm.tern = new CodeMirror.TernServer({ defs: defs });
+    cm.on("cursorActivity", (cm) => {
+      cm.tern.updateArgHints(cm);
+    });
+
+    let keyMap = {};
+
+    keyMap[Editor.keyFor("autocompletion")] = (cm) => {
+      cm.tern.getHint(cm, (data) => {
+        CodeMirror.on(data, "shown", () => ed.emit("before-suggest"));
+        CodeMirror.on(data, "close", () => ed.emit("after-suggest"));
+        CodeMirror.on(data, "select", () => ed.emit("suggestion-entered"));
+        CodeMirror.showHint(cm, (cm, cb) => cb(data), { async: true });
+      });
+    };
+
+    keyMap[Editor.keyFor("showInformation", { noaccel: true })] = (cm) => {
+      cm.tern.showType(cm, null, () => {
+        ed.emit("show-information");
+      });
+    };
+
+    cm.addKeyMap(keyMap);
+
+    // TODO: Integrate tern autocompletion with this autocomplete API.
+    return;
+  } else if (ed.config.mode == Editor.modes.css) {
+    completer = new cssAutoCompleter({walker: options.walker});
+  }
 
   let popup = new AutocompletePopup(win.parent.document, {
     position: "after_start",
     fixedWidth: true,
     theme: "auto",
     autoSelect: true
   });
 
   let cycle = (reverse) => {
     if (popup && popup.isOpen) {
       cycleSuggestions(ed, reverse == true);
       return;
     }
 
-    return win.CodeMirror.Pass;
+    return CodeMirror.Pass;
   };
 
   let keyMap = {
     "Tab": cycle,
     "Down": cycle,
     "Shift-Tab": cycle.bind(this, true),
     "Up": cycle.bind(this, true),
     "Enter": () => {
@@ -51,20 +95,20 @@ function setupAutoCompletion(ctx, walker
           ed.replaceText(text.slice(preLabel.length), cur, cur);
         }
         popup.hidePopup();
         // This event is used in tests
         ed.emit("popup-hidden");
         return;
       }
 
-      return win.CodeMirror.Pass;
+      return CodeMirror.Pass;
     }
   };
-  keyMap[Editor.accel("Space")] = cm => autoComplete(ctx);
+  keyMap[Editor.keyFor("autocompletion")] = cm => autoComplete(ctx);
   cm.addKeyMap(keyMap);
 
   cm.on("keydown", (cm, e) => onEditorKeypress(ctx, e));
   ed.on("change", () => autoComplete(ctx));
   ed.on("destroy", () => {
     cm.off("keydown", (cm, e) => onEditorKeypress(ctx, e));
     ed.off("change", () => autoComplete(ctx));
     popup.destroy();
--- a/browser/devtools/sourceeditor/codemirror/mozilla.css
+++ b/browser/devtools/sourceeditor/codemirror/mozilla.css
@@ -104,9 +104,106 @@ selector in floating-scrollbar-light.css
 .CodeMirror-foldgutter-open:after {
   font-size: 120%;
   content: "\25BE";
 }
 
 .CodeMirror-foldgutter-folded:after {
   font-size: 120%;
   content: "\25B8";
-}
\ No newline at end of file
+}
+
+.CodeMirror-hints {
+  position: absolute;
+  z-index: 10;
+  overflow: hidden;
+  list-style: none;
+  margin: 0;
+  padding: 2px;
+  border-radius: 3px;
+  font-size: 90%;
+  max-height: 20em;
+  overflow-y: auto;
+}
+
+.CodeMirror-hint {
+  margin: 0;
+  padding: 0 4px;
+  border-radius: 2px;
+  max-width: 19em;
+  overflow: hidden;
+  white-space: pre;
+  cursor: pointer;
+}
+
+.CodeMirror-Tern-completion {
+  -moz-padding-start: 22px;
+  position: relative;
+  line-height: 18px;
+}
+
+.CodeMirror-Tern-completion:before {
+  position: absolute;
+  left: 2px;
+  bottom: 2px;
+  border-radius: 50%;
+  font-size: 12px;
+  font-weight: bold;
+  height: 15px;
+  width: 15px;
+  line-height: 16px;
+  text-align: center;
+  color: #ffffff;
+  box-sizing: border-box;
+}
+
+.CodeMirror-Tern-completion-unknown:before {
+  content: "?";
+}
+
+.CodeMirror-Tern-completion-object:before {
+  content: "O";
+}
+
+.CodeMirror-Tern-completion-fn:before {
+  content: "F";
+}
+
+.CodeMirror-Tern-completion-array:before {
+  content: "A";
+}
+
+.CodeMirror-Tern-completion-number:before {
+  content: "N";
+}
+
+.CodeMirror-Tern-completion-string:before {
+  content: "S";
+}
+
+.CodeMirror-Tern-completion-bool:before {
+  content: "B";
+}
+
+.CodeMirror-Tern-completion-guess {
+  color: #999;
+}
+
+.CodeMirror-Tern-tooltip {
+  border-radius: 3px;
+  padding: 2px 5px;
+  white-space: pre-wrap;
+  max-width: 40em;
+  position: absolute;
+  z-index: 10;
+}
+
+.CodeMirror-Tern-hint-doc {
+  max-width: 25em;
+}
+
+.CodeMirror-Tern-farg-current {
+  text-decoration: underline;
+}
+
+.CodeMirror-Tern-fhint-guess {
+  opacity: .7;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/codemirror/show-hint.js
@@ -0,0 +1,341 @@
+(function() {
+  "use strict";
+
+  var HINT_ELEMENT_CLASS        = "CodeMirror-hint";
+  var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
+
+  CodeMirror.showHint = function(cm, getHints, options) {
+    // We want a single cursor position.
+    if (cm.somethingSelected()) return;
+    if (getHints == null) {
+      if (options && options.async) return;
+      else getHints = CodeMirror.hint.auto;
+    }
+
+    if (cm.state.completionActive) cm.state.completionActive.close();
+
+    var completion = cm.state.completionActive = new Completion(cm, getHints, options || {});
+    CodeMirror.signal(cm, "startCompletion", cm);
+    if (completion.options.async)
+      getHints(cm, function(hints) { completion.showHints(hints); }, completion.options);
+    else
+      return completion.showHints(getHints(cm, completion.options));
+  };
+
+  function Completion(cm, getHints, options) {
+    this.cm = cm;
+    this.getHints = getHints;
+    this.options = options;
+    this.widget = this.onClose = null;
+  }
+
+  Completion.prototype = {
+    close: function() {
+      if (!this.active()) return;
+      this.cm.state.completionActive = null;
+
+      if (this.widget) this.widget.close();
+      if (this.onClose) this.onClose();
+      CodeMirror.signal(this.cm, "endCompletion", this.cm);
+    },
+
+    active: function() {
+      return this.cm.state.completionActive == this;
+    },
+
+    pick: function(data, i) {
+      var completion = data.list[i];
+      if (completion.hint) completion.hint(this.cm, data, completion);
+      else this.cm.replaceRange(getText(completion), data.from, data.to);
+      CodeMirror.signal(data, "pick", completion);
+      this.close();
+    },
+
+    showHints: function(data) {
+      if (!data || !data.list.length || !this.active()) return this.close();
+
+      if (this.options.completeSingle != false && data.list.length == 1)
+        this.pick(data, 0);
+      else
+        this.showWidget(data);
+    },
+
+    showWidget: function(data) {
+      this.widget = new Widget(this, data);
+      CodeMirror.signal(data, "shown");
+
+      var debounce = 0, completion = this, finished;
+      var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/;
+      var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;
+
+      var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
+        return setTimeout(fn, 1000/60);
+      };
+      var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
+
+      function done() {
+        if (finished) return;
+        finished = true;
+        completion.close();
+        completion.cm.off("cursorActivity", activity);
+        if (data) CodeMirror.signal(data, "close");
+      }
+
+      function update() {
+        if (finished) return;
+        CodeMirror.signal(data, "update");
+        if (completion.options.async)
+          completion.getHints(completion.cm, finishUpdate, completion.options);
+        else
+          finishUpdate(completion.getHints(completion.cm, completion.options));
+      }
+      function finishUpdate(data_) {
+        data = data_;
+        if (finished) return;
+        if (!data || !data.list.length) return done();
+        completion.widget = new Widget(completion, data);
+      }
+
+      function clearDebounce() {
+        if (debounce) {
+          cancelAnimationFrame(debounce);
+          debounce = 0;
+        }
+      }
+
+      function activity() {
+        clearDebounce();
+        var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
+        if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
+            pos.ch < startPos.ch || completion.cm.somethingSelected() ||
+            (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
+          completion.close();
+        } else {
+          debounce = requestAnimationFrame(update);
+          if (completion.widget) completion.widget.close();
+        }
+      }
+      this.cm.on("cursorActivity", activity);
+      this.onClose = done;
+    }
+  };
+
+  function getText(completion) {
+    if (typeof completion == "string") return completion;
+    else return completion.text;
+  }
+
+  function buildKeyMap(options, handle) {
+    var baseMap = {
+      Up: function() {handle.moveFocus(-1);},
+      Down: function() {handle.moveFocus(1);},
+      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
+      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
+      Home: function() {handle.setFocus(0);},
+      End: function() {handle.setFocus(handle.length - 1);},
+      Enter: handle.pick,
+      Tab: handle.pick,
+      Esc: handle.close
+    };
+    var ourMap = options.customKeys ? {} : baseMap;
+    function addBinding(key, val) {
+      var bound;
+      if (typeof val != "string")
+        bound = function(cm) { return val(cm, handle); };
+      // This mechanism is deprecated
+      else if (baseMap.hasOwnProperty(val))
+        bound = baseMap[val];
+      else
+        bound = val;
+      ourMap[key] = bound;
+    }
+    if (options.customKeys)
+      for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key))
+        addBinding(key, options.customKeys[key]);
+    if (options.extraKeys)
+      for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key))
+        addBinding(key, options.extraKeys[key]);
+    return ourMap;
+  }
+
+  function getHintElement(hintsElement, el) {
+    while (el && el != hintsElement) {
+      if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
+      el = el.parentNode;
+    }
+  }
+
+  function Widget(completion, data) {
+    this.completion = completion;
+    this.data = data;
+    var widget = this, cm = completion.cm, options = completion.options;
+
+    var hints = this.hints = document.createElement("ul");
+    hints.className = "CodeMirror-hints";
+    this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0;
+
+    var completions = data.list;
+    for (var i = 0; i < completions.length; ++i) {
+      var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
+      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
+      if (cur.className != null) className = cur.className + " " + className;
+      elt.className = className;
+      if (cur.render) cur.render(elt, data, cur);
+      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
+      elt.hintId = i;
+    }
+
+    var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null);
+    var left = pos.left, top = pos.bottom, below = true;
+    hints.style.left = left + "px";
+    hints.style.top = top + "px";
+    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
+    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
+    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+    (options.container || document.body).appendChild(hints);
+    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+    if (overlapY > 0) {
+      var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top);
+      if (curTop - height > 0) { // Fits above cursor
+        hints.style.top = (top = curTop - height) + "px";
+        below = false;
+      } else if (height > winH) {
+        hints.style.height = (winH - 5) + "px";
+        hints.style.top = (top = pos.bottom - box.top) + "px";
+        var cursor = cm.getCursor();
+        if (data.from.ch != cursor.ch) {
+          pos = cm.cursorCoords(cursor);
+          hints.style.left = (left = pos.left) + "px";
+          box = hints.getBoundingClientRect();
+        }
+      }
+    }
+    var overlapX = box.left - winW;
+    if (overlapX > 0) {
+      if (box.right - box.left > winW) {
+        hints.style.width = (winW - 5) + "px";
+        overlapX -= (box.right - box.left) - winW;
+      }
+      hints.style.left = (left = pos.left - overlapX) + "px";
+    }
+
+    cm.addKeyMap(this.keyMap = buildKeyMap(options, {
+      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
+      setFocus: function(n) { widget.changeActive(n); },
+      menuSize: function() { return widget.screenAmount(); },
+      length: completions.length,
+      close: function() { completion.close(); },
+      pick: function() { widget.pick(); },
+      data: data
+    }));
+
+    if (options.closeOnUnfocus !== false) {
+      var closingOnBlur;
+      cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
+      cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
+    }
+
+    var startScroll = cm.getScrollInfo();
+    cm.on("scroll", this.onScroll = function() {
+      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
+      var newTop = top + startScroll.top - curScroll.top;
+      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
+      if (!below) point += hints.offsetHeight;
+      if (point <= editor.top || point >= editor.bottom) return completion.close();
+      hints.style.top = newTop + "px";
+      hints.style.left = (left + startScroll.left - curScroll.left) + "px";
+    });
+
+    CodeMirror.on(hints, "dblclick", function(e) {
+      var t = getHintElement(hints, e.target || e.srcElement);
+      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
+    });
+
+    CodeMirror.on(hints, "click", function(e) {
+      var t = getHintElement(hints, e.target || e.srcElement);
+      if (t && t.hintId != null) {
+        widget.changeActive(t.hintId);
+        if (options.completeOnSingleClick) widget.pick();
+      }
+    });
+
+    CodeMirror.on(hints, "mousedown", function() {
+      setTimeout(function(){cm.focus();}, 20);
+    });
+
+    CodeMirror.signal(data, "select", completions[0], hints.firstChild);
+    return true;
+  }
+
+  Widget.prototype = {
+    close: function() {
+      if (this.completion.widget != this) return;
+      this.completion.widget = null;
+      this.hints.parentNode.removeChild(this.hints);
+      this.completion.cm.removeKeyMap(this.keyMap);
+
+      var cm = this.completion.cm;
+      if (this.completion.options.closeOnUnfocus !== false) {
+        cm.off("blur", this.onBlur);
+        cm.off("focus", this.onFocus);
+      }
+      cm.off("scroll", this.onScroll);
+    },
+
+    pick: function() {
+      this.completion.pick(this.data, this.selectedHint);
+    },
+
+    changeActive: function(i, avoidWrap) {
+      if (i >= this.data.list.length)
+        i = avoidWrap ? this.data.list.length - 1 : 0;
+      else if (i < 0)
+        i = avoidWrap ? 0  : this.data.list.length - 1;
+      if (this.selectedHint == i) return;
+      var node = this.hints.childNodes[this.selectedHint];
+      node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+      node = this.hints.childNodes[this.selectedHint = i];
+      node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
+      if (node.offsetTop < this.hints.scrollTop)
+        this.hints.scrollTop = node.offsetTop - 3;
+      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
+        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
+      CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
+    },
+
+    screenAmount: function() {
+      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
+    }
+  };
+
+  CodeMirror.registerHelper("hint", "auto", function(cm, options) {
+    var helpers = cm.getHelpers(cm.getCursor(), "hint");
+    if (helpers.length) {
+      for (var i = 0; i < helpers.length; i++) {
+        var cur = helpers[i](cm, options);
+        if (cur && cur.list.length) return cur;
+      }
+    } else {
+      var words = cm.getHelper(cm.getCursor(), "hintWords");
+      if (words) return CodeMirror.hint.fromList(cm, {words: words});
+    }
+  });
+
+  CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
+    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+    var found = [];
+    for (var i = 0; i < options.words.length; i++) {
+      var word = options.words[i];
+      if (word.slice(0, token.string.length) == token.string)
+        found.push(word);
+    }
+
+    if (found.length) return {
+      list: found,
+      from: CodeMirror.Pos(cur.line, token.start),
+            to: CodeMirror.Pos(cur.line, token.end)
+    };
+  });
+
+  CodeMirror.commands.autocomplete = CodeMirror.showHint;
+})();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/codemirror/tern.js
@@ -0,0 +1,633 @@
+// Glue code between CodeMirror and Tern.
+//
+// Create a CodeMirror.TernServer to wrap an actual Tern server,
+// register open documents (CodeMirror.Doc instances) with it, and
+// call its methods to activate the assisting functions that Tern
+// provides.
+//
+// Options supported (all optional):
+// * defs: An array of JSON definition data structures.
+// * plugins: An object mapping plugin names to configuration
+//   options.
+// * getFile: A function(name, c) that can be used to access files in
+//   the project that haven't been loaded yet. Simply do c(null) to
+//   indicate that a file is not available.
+// * fileFilter: A function(value, docName, doc) that will be applied
+//   to documents before passing them on to Tern.
+// * switchToDoc: A function(name) that should, when providing a
+//   multi-file view, switch the view or focus to the named file.
+// * showError: A function(editor, message) that can be used to
+//   override the way errors are displayed.
+// * completionTip: Customize the content in tooltips for completions.
+//   Is passed a single argument—the completion's data as returned by
+//   Tern—and may return a string, DOM node, or null to indicate that
+//   no tip should be shown. By default the docstring is shown.
+// * typeTip: Like completionTip, but for the tooltips shown for type
+//   queries.
+// * responseFilter: A function(doc, query, request, error, data) that
+//   will be applied to the Tern responses before treating them
+//
+//
+// It is possible to run the Tern server in a web worker by specifying
+// these additional options:
+// * useWorker: Set to true to enable web worker mode. You'll probably
+//   want to feature detect the actual value you use here, for example
+//   !!window.Worker.
+// * workerScript: The main script of the worker. Point this to
+//   wherever you are hosting worker.js from this directory.
+// * workerDeps: An array of paths pointing (relative to workerScript)
+//   to the Acorn and Tern libraries and any Tern plugins you want to
+//   load. Or, if you minified those into a single script and included
+//   them in the workerScript, simply leave this undefined.
+
+(function() {
+  "use strict";
+  // declare global: tern
+
+  CodeMirror.TernServer = function(options) {
+    var self = this;
+    this.options = options || {};
+    var plugins = this.options.plugins || (this.options.plugins = {});
+    if (!plugins.doc_comment) plugins.doc_comment = true;
+    if (this.options.useWorker) {
+      this.server = new WorkerServer(this);
+    } else {
+      this.server = new tern.Server({
+        getFile: function(name, c) { return getFile(self, name, c); },
+        async: true,
+        defs: this.options.defs || [],
+        plugins: plugins
+      });
+    }
+    this.docs = Object.create(null);
+    this.trackChange = function(doc, change) { trackChange(self, doc, change); };
+
+    this.cachedArgHints = null;
+    this.activeArgHints = null;
+    this.jumpStack = [];
+  };
+
+  CodeMirror.TernServer.prototype = {
+    addDoc: function(name, doc) {
+      var data = {doc: doc, name: name, changed: null};
+      this.server.addFile(name, docValue(this, data));
+      CodeMirror.on(doc, "change", this.trackChange);
+      return this.docs[name] = data;
+    },
+
+    delDoc: function(name) {
+      var found = this.docs[name];
+      if (!found) return;
+      CodeMirror.off(found.doc, "change", this.trackChange);
+      delete this.docs[name];
+      this.server.delFile(name);
+    },
+
+    hideDoc: function(name) {
+      closeArgHints(this);
+      var found = this.docs[name];
+      if (found && found.changed) sendDoc(this, found);
+    },
+
+    complete: function(cm) {
+      var self = this;
+      CodeMirror.showHint(cm, function(cm, c) { return hint(self, cm, c); }, {async: true});
+    },
+
+    getHint: function(cm, c) { return hint(this, cm, c); },
+
+    showType: function(cm, pos, c) { showType(this, cm, pos, c); },
+
+    updateArgHints: function(cm) { updateArgHints(this, cm); },
+
+    jumpToDef: function(cm) { jumpToDef(this, cm); },
+
+    jumpBack: function(cm) { jumpBack(this, cm); },
+
+    rename: function(cm) { rename(this, cm); },
+
+    request: function (cm, query, c, pos) {
+      var self = this;
+      var doc = findDoc(this, cm.getDoc());
+      var request = buildRequest(this, doc, query, pos);
+
+      this.server.request(request, function (error, data) {
+        if (!error && self.options.responseFilter)
+          data = self.options.responseFilter(doc, query, request, error, data);
+        c(error, data);
+      });
+    }
+  };
+
+  var Pos = CodeMirror.Pos;
+  var cls = "CodeMirror-Tern-";
+  var bigDoc = 250;
+
+  function getFile(ts, name, c) {
+    var buf = ts.docs[name];
+    if (buf)
+      c(docValue(ts, buf));
+    else if (ts.options.getFile)
+      ts.options.getFile(name, c);
+    else
+      c(null);
+  }
+
+  function findDoc(ts, doc, name) {
+    for (var n in ts.docs) {
+      var cur = ts.docs[n];
+      if (cur.doc == doc) return cur;
+    }
+    if (!name) for (var i = 0;; ++i) {
+      n = "[doc" + (i || "") + "]";
+      if (!ts.docs[n]) { name = n; break; }
+    }
+    return ts.addDoc(name, doc);
+  }
+
+  function trackChange(ts, doc, change) {
+    var data = findDoc(ts, doc);
+
+    var argHints = ts.cachedArgHints;
+    if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
+      ts.cachedArgHints = null;
+
+    var changed = data.changed;
+    if (changed == null)
+      data.changed = changed = {from: change.from.line, to: change.from.line};
+    var end = change.from.line + (change.text.length - 1);
+    if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end);
+    if (end >= changed.to) changed.to = end + 1;
+    if (changed.from > change.from.line) changed.from = change.from.line;
+
+    if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() {
+      if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data);
+    }, 200);
+  }
+
+  function sendDoc(ts, doc) {
+    ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) {
+      if (error) console.error(error);
+      else doc.changed = null;
+    });
+  }
+
+  // Completion
+
+  function hint(ts, cm, c) {
+    ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) {
+      if (error) return showError(ts, cm, error);
+      var completions = [], after = "";
+      var from = data.start, to = data.end;
+      if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" &&
+          cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]")
+        after = "\"]";
+
+      for (var i = 0; i < data.completions.length; ++i) {
+        var completion = data.completions[i], className = typeToIcon(completion.type);
+        if (data.guess) className += " " + cls + "guess";
+        completions.push({text: completion.name + after,
+                          displayText: completion.name,
+                          className: className,
+                          data: completion});
+      }
+
+      var obj = {from: from, to: to, list: completions};
+      var tooltip = null;
+      CodeMirror.on(obj, "close", function() { remove(tooltip); });
+      CodeMirror.on(obj, "update", function() { remove(tooltip); });
+      CodeMirror.on(obj, "select", function(cur, node) {
+        remove(tooltip);
+        var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
+        if (content) {
+          tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
+                                node.getBoundingClientRect().top + window.pageYOffset, content);
+          tooltip.className += " " + cls + "hint-doc";
+        }
+      });
+      c(obj);
+    });
+  }
+
+  function typeToIcon(type) {
+    var suffix;
+    if (type == "?") suffix = "unknown";
+    else if (type == "number" || type == "string" || type == "bool") suffix = type;
+    else if (/^fn\(/.test(type)) suffix = "fn";
+    else if (/^\[/.test(type)) suffix = "array";
+    else suffix = "object";
+    return cls + "completion " + cls + "completion-" + suffix;
+  }
+
+  // Type queries
+
+  function showType(ts, cm, pos, c) {
+    ts.request(cm, "type", function(error, data) {
+      if (error) return showError(ts, cm, error);
+      if (ts.options.typeTip) {
+        var tip = ts.options.typeTip(data);
+      } else {
+        var tip = elt("span", cls + "information", elt("strong", null, data.type || "not found"));
+        if (data.doc)
+          tip.appendChild(document.createTextNode(" — " + data.doc));
+        if (data.url) {
+          tip.appendChild(document.createTextNode(" "));
+          tip.appendChild(elt("a", null, "[docs]")).href = data.url;
+        }
+      }
+      tempTooltip(cm, tip);
+      c && c(tip);
+    }, pos);
+  }
+
+  // Maintaining argument hints
+
+  function updateArgHints(ts, cm) {
+    closeArgHints(ts);
+
+    if (cm.somethingSelected()) return;
+    var state = cm.getTokenAt(cm.getCursor()).state;
+    var inner = CodeMirror.innerMode(cm.getMode(), state);
+    if (inner.mode.name != "javascript") return;
+    var lex = inner.state.lexical;
+    if (lex.info != "call") return;
+
+    var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize");
+    for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) {
+      var str = cm.getLine(line), extra = 0;
+      for (var pos = 0;;) {
+        var tab = str.indexOf("\t", pos);
+        if (tab == -1) break;
+        extra += tabSize - (tab + extra) % tabSize - 1;
+        pos = tab + 1;
+      }
+      ch = lex.column - extra;
+      if (str.charAt(ch) == "(") {found = true; break;}
+    }
+    if (!found) return;
+
+    var start = Pos(line, ch);
+    var cache = ts.cachedArgHints;
+    if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0)
+      return showArgHints(ts, cm, argPos);
+
+    ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
+      if (error || !data.type || !(/^fn\(/).test(data.type)) return;
+      ts.cachedArgHints = {
+        start: pos,
+        type: parseFnType(data.type),
+        name: data.exprName || data.name || "fn",
+        guess: data.guess,
+        doc: cm.getDoc()
+      };
+      showArgHints(ts, cm, argPos);
+    });
+  }
+
+  function showArgHints(ts, cm, pos) {
+    closeArgHints(ts);
+
+    var cache = ts.cachedArgHints, tp = cache.type;
+    var tip = elt("span", cache.guess ? cls + "fhint-guess" : null,
+                  elt("span", cls + "fname", cache.name), "(");
+    for (var i = 0; i < tp.args.length; ++i) {
+      if (i) tip.appendChild(document.createTextNode(", "));
+      var arg = tp.args[i];
+      tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?"));
+      if (arg.type != "?") {
+        tip.appendChild(document.createTextNode(":\u00a0"));
+        tip.appendChild(elt("span", cls + "type", arg.type));
+      }
+    }
+    tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
+    if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
+    var place = cm.cursorCoords(null, "page");
+    ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
+  }
+
+  function parseFnType(text) {
+    var args = [], pos = 3;
+
+    function skipMatching(upto) {
+      var depth = 0, start = pos;
+      for (;;) {
+        var next = text.charAt(pos);
+        if (upto.test(next) && !depth) return text.slice(start, pos);
+        if (/[{\[\(]/.test(next)) ++depth;
+        else if (/[}\]\)]/.test(next)) --depth;
+        ++pos;
+      }
+    }
+
+    // Parse arguments
+    if (text.charAt(pos) != ")") for (;;) {
+      var name = text.slice(pos).match(/^([^, \(\[\{]+): /);
+      if (name) {
+        pos += name[0].length;
+        name = name[1];
+      }
+      args.push({name: name, type: skipMatching(/[\),]/)});
+      if (text.charAt(pos) == ")") break;
+      pos += 2;
+    }
+
+    var rettype = text.slice(pos).match(/^\) -> (.*)$/);
+
+    return {args: args, rettype: rettype && rettype[1]};
+  }
+
+  // Moving to the definition of something
+
+  function jumpToDef(ts, cm) {
+    function inner(varName) {
+      var req = {type: "definition", variable: varName || null};
+      var doc = findDoc(ts, cm.getDoc());
+      ts.server.request(buildRequest(ts, doc, req), function(error, data) {
+        if (error) return showError(ts, cm, error);
+        if (!data.file && data.url) { window.open(data.url); return; }
+
+        if (data.file) {
+          var localDoc = ts.docs[data.file], found;
+          if (localDoc && (found = findContext(localDoc.doc, data))) {
+            ts.jumpStack.push({file: doc.name,
+                               start: cm.getCursor("from"),
+                               end: cm.getCursor("to")});
+            moveTo(ts, doc, localDoc, found.start, found.end);
+            return;
+          }
+        }
+        showError(ts, cm, "Could not find a definition.");
+      });
+    }
+
+    if (!atInterestingExpression(cm))
+      dialog(cm, "Jump to variable", function(name) { if (name) inner(name); });
+    else
+      inner();
+  }
+
+  function jumpBack(ts, cm) {
+    var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file];
+    if (!doc) return;
+    moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end);
+  }
+
+  function moveTo(ts, curDoc, doc, start, end) {
+    doc.doc.setSelection(end, start);
+    if (curDoc != doc && ts.options.switchToDoc) {
+      closeArgHints(ts);
+      ts.options.switchToDoc(doc.name);
+    }
+  }
+
+  // The {line,ch} representation of positions makes this rather awkward.
+  function findContext(doc, data) {
+    var before = data.context.slice(0, data.contextOffset).split("\n");
+    var startLine = data.start.line - (before.length - 1);
+    var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length);
+
+    var text = doc.getLine(startLine).slice(start.ch);
+    for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur)
+      text += "\n" + doc.getLine(cur);
+    if (text.slice(0, data.context.length) == data.context) return data;
+
+    var cursor = doc.getSearchCursor(data.context, 0, false);
+    var nearest, nearestDist = Infinity;
+    while (cursor.findNext()) {
+      var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000;
+      if (!dist) dist = Math.abs(from.ch - start.ch);
+      if (dist < nearestDist) { nearest = from; nearestDist = dist; }
+    }
+    if (!nearest) return null;
+
+    if (before.length == 1)
+      nearest.ch += before[0].length;
+    else
+      nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length);
+    if (data.start.line == data.end.line)
+      var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch));
+    else
+      var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch);
+    return {start: nearest, end: end};
+  }
+
+  function atInterestingExpression(cm) {
+    var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
+    if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
+    return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
+  }
+
+  // Variable renaming
+
+  function rename(ts, cm) {
+    var token = cm.getTokenAt(cm.getCursor());
+    if (!/\w/.test(token.string)) showError(ts, cm, "Not at a variable");
+    dialog(cm, "New name for " + token.string, function(newName) {
+      ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) {
+        if (error) return showError(ts, cm, error);
+        applyChanges(ts, data.changes);
+      });
+    });
+  }
+
+  var nextChangeOrig = 0;
+  function applyChanges(ts, changes) {
+    var perFile = Object.create(null);
+    for (var i = 0; i < changes.length; ++i) {
+      var ch = changes[i];
+      (perFile[ch.file] || (perFile[ch.file] = [])).push(ch);
+    }
+    for (var file in perFile) {
+      var known = ts.docs[file], chs = perFile[file];;
+      if (!known) continue;
+      chs.sort(function(a, b) { return cmpPos(b.start, a.start); });
+      var origin = "*rename" + (++nextChangeOrig);
+      for (var i = 0; i < chs.length; ++i) {
+        var ch = chs[i];
+        known.doc.replaceRange(ch.text, ch.start, ch.end, origin);
+      }
+    }
+  }
+
+  // Generic request-building helper
+
+  function buildRequest(ts, doc, query, pos) {
+    var files = [], offsetLines = 0, allowFragments = !query.fullDocs;
+    if (!allowFragments) delete query.fullDocs;
+    if (typeof query == "string") query = {type: query};
+    query.lineCharPositions = true;
+    if (query.end == null) {
+      query.end = pos || doc.doc.getCursor("end");
+      if (doc.doc.somethingSelected())
+        query.start = doc.doc.getCursor("start");
+    }
+    var startPos = query.start || query.end;
+
+    if (doc.changed) {
+      if (doc.doc.lineCount() > bigDoc && allowFragments !== false &&
+          doc.changed.to - doc.changed.from < 100 &&
+          doc.changed.from <= startPos.line && doc.changed.to > query.end.line) {
+        files.push(getFragmentAround(doc, startPos, query.end));
+        query.file = "#0";
+        var offsetLines = files[0].offsetLines;
+        if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch);
+        query.end = Pos(query.end.line - offsetLines, query.end.ch);
+      } else {
+        files.push({type: "full",
+                    name: doc.name,
+                    text: docValue(ts, doc)});
+        query.file = doc.name;
+        doc.changed = null;
+      }
+    } else {
+      query.file = doc.name;
+    }
+    for (var name in ts.docs) {
+      var cur = ts.docs[name];
+      if (cur.changed && cur != doc) {
+        files.push({type: "full", name: cur.name, text: docValue(ts, cur)});
+        cur.changed = null;
+      }
+    }
+
+    return {query: query, files: files};
+  }
+
+  function getFragmentAround(data, start, end) {
+    var doc = data.doc;
+    var minIndent = null, minLine = null, endLine, tabSize = 4;
+    for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) {
+      var line = doc.getLine(p), fn = line.search(/\bfunction\b/);
+      if (fn < 0) continue;
+      var indent = CodeMirror.countColumn(line, null, tabSize);
+      if (minIndent != null && minIndent <= indent) continue;
+      minIndent = indent;
+      minLine = p;
+    }
+    if (minLine == null) minLine = min;
+    var max = Math.min(doc.lastLine(), end.line + 20);
+    if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize))
+      endLine = max;
+    else for (endLine = end.line + 1; endLine < max; ++endLine) {
+      var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize);
+      if (indent <= minIndent) break;
+    }
+    var from = Pos(minLine, 0);
+
+    return {type: "part",
+            name: data.name,
+            offsetLines: from.line,
+            text: doc.getRange(from, Pos(endLine, 0))};
+  }
+
+  // Generic utilities
+
+  function cmpPos(a, b) { return a.line - b.line || a.ch - b.ch; }
+
+  function elt(tagname, cls /*, ... elts*/) {
+    var e = document.createElement(tagname);
+    if (cls) e.className = cls;
+    for (var i = 2; i < arguments.length; ++i) {
+      var elt = arguments[i];
+      if (typeof elt == "string") elt = document.createTextNode(elt);
+      e.appendChild(elt);
+    }
+    return e;
+  }
+
+  function dialog(cm, text, f) {
+    if (cm.openDialog)
+      cm.openDialog(text + ": <input type=text>", f);
+    else
+      f(prompt(text, ""));
+  }
+
+  // Tooltips
+
+  function tempTooltip(cm, content) {
+    var where = cm.cursorCoords();
+    var tip = makeTooltip(where.right + 1, where.bottom, content);
+    function clear() {
+      if (!tip.parentNode) return;
+      cm.off("cursorActivity", clear);
+      fadeOut(tip);
+    }
+    setTimeout(clear, 1700);
+    cm.on("cursorActivity", clear);
+  }
+
+  function makeTooltip(x, y, content) {
+    var node = elt("div", cls + "tooltip", content);
+    node.style.left = x + "px";
+    node.style.top = y + "px";
+    document.body.appendChild(node);
+    return node;
+  }
+
+  function remove(node) {
+    var p = node && node.parentNode;
+    if (p) p.removeChild(node);
+  }
+
+  function fadeOut(tooltip) {
+    tooltip.style.opacity = "0";
+    setTimeout(function() { remove(tooltip); }, 1100);
+  }
+
+  function showError(ts, cm, msg) {
+    if (ts.options.showError)
+      ts.options.showError(cm, msg);
+    else
+      tempTooltip(cm, String(msg));
+  }
+
+  function closeArgHints(ts) {
+    if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+  }
+
+  function docValue(ts, doc) {
+    var val = doc.doc.getValue();
+    if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc);
+    return val;
+  }
+
+  // Worker wrapper
+
+  function WorkerServer(ts) {
+    var worker = new Worker(ts.options.workerScript);
+    worker.postMessage({type: "init",
+                        defs: ts.options.defs,
+                        plugins: ts.options.plugins,
+                        scripts: ts.options.workerDeps});
+    var msgId = 0, pending = {};
+
+    function send(data, c) {
+      if (c) {
+        data.id = ++msgId;
+        pending[msgId] = c;
+      }
+      worker.postMessage(data);
+    }
+    worker.onmessage = function(e) {
+      var data = e.data;
+      if (data.type == "getFile") {
+        getFile(ts, data.name, function(err, text) {
+          send({type: "getFile", err: String(err), text: text, id: data.id});
+        });
+      } else if (data.type == "debug") {
+        console.log(data.message);
+      } else if (data.id && pending[data.id]) {
+        pending[data.id](data.err, data.body);
+        delete pending[data.id];
+      }
+    };
+    worker.onerror = function(e) {
+      for (var id in pending) pending[id](e);
+      pending = {};
+    };
+
+    this.addFile = function(name, text) { send({type: "add", name: name, text: text}); };
+    this.delFile = function(name) { send({type: "del", name: name}); };
+    this.request = function(body, c) { send({type: "req", body: body}, c); };
+  }
+})();
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -145,17 +145,18 @@ function Editor(config) {
     tabSize:           tabSize,
     contextMenu:       null,
     matchBrackets:     true,
     extraKeys:         {},
     indentWithTabs:    useTabs,
     styleActiveLine:   true,
     autoCloseBrackets: "()[]{}''\"\"",
     autoCloseEnabled:  useAutoClose,
-    theme:             "mozilla"
+    theme:             "mozilla",
+    autocomplete:      false
   };
 
   // Additional shortcuts.
   this.config.extraKeys[Editor.keyFor("jumpToLine")] = () => this.jumpToLine();
   this.config.extraKeys[Editor.keyFor("moveLineUp", { noaccel: true })] = () => this.moveLineUp();
   this.config.extraKeys[Editor.keyFor("moveLineDown", { noaccel: true })] = () => this.moveLineDown();
   this.config.extraKeys[Editor.keyFor("toggleComment")] = "toggleComment";
 
@@ -312,16 +313,20 @@ Editor.prototype = {
 
       cm.getInputField().controllers.insertControllerAt(0, controller(this));
 
       this.container = env;
       editors.set(this, cm);
 
       this.resetIndentUnit();
 
+      if (this.config.autocomplete) {
+        this.extend(require("./autocomplete"));
+      }
+
       def.resolve();
     };
 
     env.addEventListener("load", onLoad, true);
     env.setAttribute("src", CM_IFRAME);
     el.appendChild(env);
 
     this.once("destroy", () => el.removeChild(env));
@@ -332,16 +337,27 @@ Editor.prototype = {
    * Returns the currently active highlighting mode.
    * See Editor.modes for the list of all suppoert modes.
    */
   getMode: function () {
     return this.getOption("mode");
   },
 
   /**
+   * Load a script into editor's containing window.
+   */
+  loadScript: function (url) {
+    if (!this.container) {
+      throw new Error("Can't load a script until the editor is loaded.")
+    }
+    let win = this.container.contentWindow.wrappedJSObject;
+    Services.scriptloader.loadSubScript(url, win, "utf8");
+  },
+
+  /**
    * Changes the value of a currently used highlighting mode.
    * See Editor.modes for the list of all suppoert modes.
    */
   setMode: function (value) {
     this.setOption("mode", value);
   },
 
   /**
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -257,27 +257,26 @@ StyleSheetEditor.prototype = {
 
     let config = {
       value: this._state.text,
       lineNumbers: true,
       mode: Editor.modes.css,
       readOnly: false,
       autoCloseBrackets: "{}()[]",
       extraKeys: this._getKeyBindings(),
-      contextMenu: "sourceEditorContextMenu"
+      contextMenu: "sourceEditorContextMenu",
+      autocomplete: Services.prefs.getBoolPref(AUTOCOMPLETION_PREF)
     };
     let sourceEditor = new Editor(config);
 
     sourceEditor.on("dirty-change", this._onPropertyChange);
 
     return sourceEditor.appendTo(inputElement).then(() => {
-      if (Services.prefs.getBoolPref(AUTOCOMPLETION_PREF)) {
-        sourceEditor.extend(AutoCompleter);
-        sourceEditor.setupAutoCompletion(this.walker);
-      }
+      sourceEditor.setupAutoCompletion({ walker: this.walker });
+
       sourceEditor.on("save", () => {
         this.saveToFile();
       });
 
       if (this.styleSheet.update) {
         sourceEditor.on("change", () => {
           this.updateStyleSheet();
         });
--- a/browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/sourceeditor.properties
@@ -77,8 +77,17 @@ indentMore.commandkey=]
 
 # LOCALIZATION NOTE  (moveLineUp.commandkey): This is the key to use to move
 # the selected lines up.
 moveLineUp.commandkey=Alt-Up
 
 # LOCALIZATION NOTE  (moveLineDown.commandkey): This is the key to use to move
 # the selected lines down.
 moveLineDown.commandkey=Alt-Down
+
+# LOCALIZATION NOTE  (autocomplete.commandkey): This is the key to use
+# in conjunction with accel (Command on Mac or Ctrl on other platforms) for
+# autocompletion.
+autocompletion.commandkey=Space
+
+# LOCALIZATION NOTE  (showInformation.commandkey): This is the key to use to
+# show more information, like type inference.
+showInformation.commandkey=Shift-Space
--- a/browser/themes/shared/devtools/common.css
+++ b/browser/themes/shared/devtools/common.css
@@ -51,20 +51,18 @@
   min-width: 465px;
 }
 
 /* Autocomplete Popup */
 /* Dark and light theme */
 
 .devtools-autocomplete-popup {
   -moz-appearance: none !important;
-  border: 1px solid hsl(210,11%,10%);
   box-shadow: 0 1px 0 hsla(209,29%,72%,.25) inset;
   background-color: transparent;
-  background-image: linear-gradient(to bottom, hsla(209,18%,18%,0.9), hsl(210,11%,16%));
   border-radius: 3px;
   overflow-x: hidden;
 %if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
   max-height: 32rem;
 %else
   max-height: 40rem;
 %endif
 }
@@ -105,22 +103,34 @@
   margin: 0;
   padding: 1px 0;
 }
 
 .devtools-autocomplete-listbox > richlistitem > .autocomplete-count {
   text-align: right;
 }
 
-/* Rest of the light theme */
+/* Rest of the dark and light theme */
+
+.devtools-autocomplete-popup,
+.theme-dark .CodeMirror-hints,
+.theme-dark .CodeMirror-Tern-tooltip {
+  border: 1px solid hsl(210,11%,10%);
+  background-image: linear-gradient(to bottom, hsla(209,18%,18%,0.9), hsl(210,11%,16%));
+}
+
+.devtools-autocomplete-popup.light-theme,
+.light-theme .CodeMirror-hints,
+.light-theme .CodeMirror-Tern-tooltip {
+  border: 1px solid hsl(210,24%,90%);
+  background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
+}
 
 .devtools-autocomplete-popup.light-theme {
-  border: 1px solid hsl(210,24%,90%);
   box-shadow: 0 1px 0 hsla(209,29%,90%,.25) inset;
-  background-image: linear-gradient(to bottom, hsla(209,18%,100%,0.9), hsl(210,24%,95%));
 }
 
 .devtools-autocomplete-listbox.light-theme > richlistitem[selected],
 .devtools-autocomplete-listbox.light-theme > richlistitem:hover {
   background-color: rgba(128,128,128,0.3);
 }
 
 .devtools-autocomplete-listbox.light-theme > richlistitem[selected] > .autocomplete-value,
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -20,17 +20,18 @@
   background-color: #1d4f73;
   color: #f5f7fa;
 }
 
 .theme-bg-darker {
   background-color: rgba(0,0,0,0.5);
 }
 
-.theme-selected {
+.theme-selected,
+.CodeMirror-hint-active {
   background-color: #1d4f73;
   color: #f5f7fa; /* Light foreground text */
 }
 
 .theme-bg-contrast,
 .variable-or-property:not([overridden])[changed] { /* contrast bg color to attract attention on a container */
   background: #a18650;
 }
@@ -40,30 +41,35 @@
   color: #3689b2;
 }
 
 /*
  * FIXME: http://bugzil.la/575675 CSS links without :visited set cause assertion
  * failures in debug builds.
  */
 .theme-link:visited,
-.cm-s-mozilla .cm-link:visited { /* blue */
+.cm-s-mozilla .cm-link:visited,
+.CodeMirror-Tern-type { /* blue */
   color: #3689b2;
 }
 
 
 .theme-comment,
 .cm-s-mozilla .cm-meta,
 .cm-s-mozilla .cm-hr,
 .cm-s-mozilla .cm-comment,
 .variable-or-property .token-undefined,
 .variable-or-property .token-null { /* grey */
   color: #5c6773;
 }
 
+.CodeMirror-Tern-completion-unknown:before {
+  background-color: #5c6773;
+}
+
 .theme-gutter {
   background-color: #0f171f;
   color: #667380;
   border-color: #303b47;
 }
 
 .theme-separator { /* grey */
   border-color: #303b47;
@@ -72,61 +78,83 @@
 .theme-fg-color1,
 .cm-s-mozilla .cm-number,
 .variable-or-property .token-number,
 .variable-or-property[return] > .title > .name,
 .variable-or-property[scope] > .title > .name { /* green */
   color: #5c9966;
 }
 
+.CodeMirror-Tern-completion-number:before {
+  background-color: #5c9966;
+}
+
 .theme-fg-color2,
 .cm-s-mozilla .cm-attribute,
 .cm-s-mozilla .cm-variable,
 .cm-s-mozilla .cm-def,
 .cm-s-mozilla .cm-property,
 .cm-s-mozilla .cm-qualifier,
 .variables-view-variable > .title > .name { /* blue */
   color: #3689b2;
 }
 
+.CodeMirror-Tern-completion-object:before {
+  background-color: #3689b2;
+}
+
 .theme-fg-color3,
 .cm-s-mozilla .cm-builtin,
 .cm-s-mozilla .cm-tag,
 .cm-s-mozilla .cm-header,
 .variables-view-property > .title > .name { /* pink/lavender */
   color: #a673bf;
 }
 
+.CodeMirror-Tern-completion-array:before {
+  background-color: #a673bf;
+}
+
 .theme-fg-color4 { /* purple/violet */
   color: #6270b2;
 }
 
 .theme-fg-color5,
 .cm-s-mozilla .cm-bracket,
 .cm-s-mozilla .cm-keyword { /* Yellow */
   color: #a18650;
 }
 
 .theme-fg-color6,
 .cm-s-mozilla .cm-string,
 .cm-s-mozilla .cm-string-2,
-.variable-or-property .token-string { /* Orange */
+.variable-or-property .token-string,
+.CodeMirror-Tern-farg { /* Orange */
   color: #b26b47;
 }
 
+.CodeMirror-Tern-completion-string:before,
+.CodeMirror-Tern-completion-fn:before {
+  background-color: #b26b47;
+}
+
 .theme-fg-color7,
 .cm-s-mozilla .cm-atom,
 .cm-s-mozilla .cm-quote,
 .cm-s-mozilla .cm-error,
 .variable-or-property .token-boolean,
 .variable-or-property .token-domnode,
 .variable-or-property[exception] > .title > .name { /* Red */
   color: #bf5656;
 }
 
+.CodeMirror-Tern-completion-bool:before {
+  background-color: #bf5656;
+}
+
 .variable-or-property .token-domnode {
   font-weight: bold;
 }
 
 .theme-toolbar,
 .devtools-toolbar,
 .devtools-sidebar-tabs > tabs { /* General toolbar styling */
   color: #b6babf;
@@ -330,9 +358,21 @@ div.CodeMirror span.eval-text {
 }
 
 .devtools-textinput,
 .devtools-searchinput {
   background-color: rgba(24, 29, 32, 1);
   color: rgba(184, 200, 217, 1);
 }
 
+.CodeMirror-Tern-fname {
+  color: #f7f7f7;
+}
+
+.CodeMirror-hints,
+.CodeMirror-Tern-tooltip {
+  box-shadow: 0 0 4px rgba(255, 255, 255, .3);
+  background-color: #0f171f;
+  color: #8fa1b2;
+}
+
+
 %include toolbars.inc.css
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -20,28 +20,30 @@
   background-color: #4c9ed9;
   color: #f5f7fa;
 }
 
 .theme-bg-darker {
   background: #EFEFEF;
 }
 
-.theme-selected {
+.theme-selected,
+.CodeMirror-hint-active {
   background-color: #4c9ed9;
   color: #f5f7fa; /* Light foreground text */
 }
 
 .theme-bg-contrast,
 .variable-or-property:not([overridden])[changed] { /* contrast bg color to attract attention on a container */
   background: #a18650;
 }
 
 .theme-link,
-.cm-s-mozilla .cm-link { /* blue */
+.cm-s-mozilla .cm-link,
+.CodeMirror-Tern-type { /* blue */
   color: hsl(208,56%,40%);
 }
 
 /*
  * FIXME: http://bugzil.la/575675 CSS links without :visited set cause assertion
  * failures in debug builds.
  */
 .theme-link:visited,
@@ -53,16 +55,20 @@
 .cm-s-mozilla .cm-meta,
 .cm-s-mozilla .cm-hr,
 .cm-s-mozilla .cm-comment,
 .variable-or-property .token-undefined,
 .variable-or-property .token-null { /* grey */
   color: hsl(90,2%,46%);
 }
 
+.CodeMirror-Tern-completion-unknown:before {
+  background-color: hsl(90,2%,46%);
+}
+
 .theme-gutter {
   background-color: hsl(0,0%,90%);
   color: #667380;
   border-color: hsl(0,0%,65%);
 }
 
 .theme-separator { /* grey */
   border-color: #cddae5;
@@ -71,61 +77,83 @@
 .theme-fg-color1,
 .cm-s-mozilla .cm-number,
 .variable-or-property .token-number,
 .variable-or-property[return] > .title > .name,
 .variable-or-property[scope] > .title > .name { /* green */
   color: hsl(72,100%,27%);
 }
 
+.CodeMirror-Tern-completion-number:before {
+  background-color: hsl(72,100%,27%);
+}
+
 .theme-fg-color2,
 .cm-s-mozilla .cm-attribute,
 .cm-s-mozilla .cm-builtin,
 .cm-s-mozilla .cm-def,
 .cm-s-mozilla .cm-property,
 .cm-s-mozilla .cm-qualifier,
 .variables-view-variable > .title > .name { /* blue */
   color: hsl(208,56%,40%);
 }
 
+.CodeMirror-Tern-completion-object:before {
+  background-color: hsl(208,56%,40%);
+}
+
 .theme-fg-color3,
 .cm-s-mozilla .cm-variable,
 .cm-s-mozilla .cm-tag,
 .cm-s-mozilla .cm-header,
 .variables-view-property > .title > .name { /* dark blue */
-  color: hsl(208,81%,21%)
+  color: hsl(208,81%,21%);
+}
+
+.CodeMirror-Tern-completion-array:before { /* dark blue */
+  background-color: hsl(208,81%,21%);
 }
 
 .theme-fg-color4 { /* Orange */
   color: hsl(24,85%,39%);
 }
 
 .theme-fg-color5,
 .cm-s-mozilla .cm-bracket,
 .cm-s-mozilla .cm-keyword { /* Yellow */
   color: #a18650;
 }
 
 .theme-fg-color6,
 .cm-s-mozilla .cm-string,
 .cm-s-mozilla .cm-string-2,
-.variable-or-property .token-string { /* Orange */
+.variable-or-property .token-string,
+.CodeMirror-Tern-farg { /* Orange */
   color: hsl(24,85%,39%);
 }
 
+.CodeMirror-Tern-completion-string:before,
+.CodeMirror-Tern-completion-fn:before {
+  background-color: hsl(24,85%,39%);
+}
+
 .theme-fg-color7,
 .cm-s-mozilla .cm-atom,
 .cm-s-mozilla .cm-quote,
 .cm-s-mozilla .cm-error,
 .variable-or-property .token-boolean,
 .variable-or-property .token-domnode,
 .variable-or-property[exception] > .title > .name { /* Red */
   color: #bf5656;
 }
 
+.CodeMirror-Tern-completion-bool:before {
+  background-color: #bf5656;
+}
+
 .variable-or-property .token-domnode {
   font-weight: bold;
 }
 
 .theme-fg-contrast { /* To be used for text on theme-bg-contrast */
   color: black;
 }
 
@@ -333,9 +361,16 @@ div.CodeMirror span.eval-text {
   border-bottom: 1px solid #aaa;
 }
 
 .devtools-side-splitter {
   -moz-border-end: 1px solid #aaa;
   border-color: #aaa; /* Needed for responsive container at low width. */
 }
 
+.CodeMirror-hints,
+.CodeMirror-Tern-tooltip {
+  box-shadow: 0 0 4px rgba(128, 128, 128, .5);
+  background-color: #f7f7f7;
+}
+
+
 %include toolbars.inc.css
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -71,16 +71,17 @@ BuiltinProvider.prototype = {
         "devtools/touch-events": "resource://gre/modules/devtools/touch-events",
         "devtools/client": "resource://gre/modules/devtools/client",
         "devtools/pretty-fast": "resource://gre/modules/devtools/pretty-fast.js",
         "devtools/async-utils": "resource://gre/modules/devtools/async-utils",
         "devtools/content-observer": "resource://gre/modules/devtools/content-observer",
         "gcli": "resource://gre/modules/devtools/gcli",
         "acorn": "resource://gre/modules/devtools/acorn",
         "acorn/util/walk": "resource://gre/modules/devtools/acorn/walk.js",
+        "tern": "resource://gre/modules/devtools/tern",
 
         // Allow access to xpcshell test items from the loader.
         "xpcshell-test": "resource://test"
       },
       globals: loaderGlobals,
       invisibleToDebugger: this.invisibleToDebugger
     });
 
@@ -121,16 +122,17 @@ SrcdirProvider.prototype = {
     let touchEventsURI = this.fileURI(OS.Path.join(toolkitDir, "touch-events"));
     let clientURI = this.fileURI(OS.Path.join(toolkitDir, "client"));
     let prettyFastURI = this.fileURI(OS.Path.join(toolkitDir), "pretty-fast.js");
     let asyncUtilsURI = this.fileURI(OS.Path.join(toolkitDir), "async-utils.js");
     let contentObserverURI = this.fileURI(OS.Path.join(toolkitDir), "content-observer.js");
     let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
     let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
     let acornWalkURI = OS.Path.join(acornURI, "walk.js");
+    let ternURI = OS.Path.join(toolkitDir, "tern");
     this.loader = new loader.Loader({
       modules: {
         "Services": Object.create(Services),
         "toolkit/loader": loader,
         "source-map": SourceMap,
       },
       paths: {
         "": "resource://gre/modules/commonjs/",
@@ -145,17 +147,18 @@ SrcdirProvider.prototype = {
         "devtools/output-parser": outputParserURI,
         "devtools/touch-events": touchEventsURI,
         "devtools/client": clientURI,
         "devtools/pretty-fast": prettyFastURI,
         "devtools/async-utils": asyncUtilsURI,
         "devtools/content-observer": contentObserverURI,
         "gcli": gcliURI,
         "acorn": acornURI,
-        "acorn/util/walk": acornWalkURI
+        "acorn/util/walk": acornWalkURI,
+        "tern": ternURI
       },
       globals: loaderGlobals,
       invisibleToDebugger: this.invisibleToDebugger
     });
 
     return this._writeManifest(devtoolsDir).then(null, Cu.reportError);
   },
 
--- a/toolkit/devtools/moz.build
+++ b/toolkit/devtools/moz.build
@@ -8,13 +8,14 @@ PARALLEL_DIRS += [
     'server',
     'client',
     'gcli',
     'sourcemap',
     'webconsole',
     'apps',
     'styleinspector',
     'acorn',
-    'pretty-fast'
+    'pretty-fast',
+    'tern',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/browser.js
@@ -0,0 +1,2919 @@
+module.exports = {
+  "!name": "browser",
+  "location": {
+    "assign": {
+      "!type": "fn(url: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "Load the document at the provided URL."
+    },
+    "replace": {
+      "!type": "fn(url: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "Replace the current document with the one at the provided URL. The difference from the assign() method is that after using replace() the current page will not be saved in session history, meaning the user won't be able to use the Back button to navigate to it."
+    },
+    "reload": {
+      "!type": "fn()",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "Reload the document from the current URL. forceget is a boolean, which, when it is true, causes the page to always be reloaded from the server. If it is false or not specified, the browser may reload the page from its cache."
+    },
+    "origin": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The origin of the URL."
+    },
+    "hash": {
+      "!type": "string",
+      "!url": "https://developer.mthat follows the # symbol, including the # symbol."
+    },
+    "search": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The part of the URL that follows the ? symbol, including the ? symbol."
+    },
+    "pathname": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The path (relative to the host)."
+    },
+    "port": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The port number of the URL."
+    },
+    "hostname": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The host name (without the port number or square brackets)."
+    },
+    "host": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The host name and port number."
+    },
+    "protocol": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The protocol of the URL."
+    },
+    "href": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+      "!doc": "The entire URL."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.location",
+    "!doc": "Returns a location object with information about the current location of the document. Assigning to the location property changes the current page to the new address."
+  },
+  "Node": {
+    "!type": "fn()",
+    "prototype": {
+      "parentElement": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.parentElement",
+        "!doc": "Returns the DOM node's parent Element, or null if the node either has no parent, or its parent isn't a DOM Element."
+      },
+      "textContent": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.textContent",
+        "!doc": "Gets or sets the text content of a node and its descendants."
+      },
+      "baseURI": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.baseURI",
+        "!doc": "The absolute base URI of a node or null if unable to obtain an absolute URI."
+      },
+      "localName": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.localName",
+        "!doc": "Returns the local part of the qualified name of this node."
+      },
+      "prefix": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.prefix",
+        "!doc": "Returns the namespace prefix of the specified node, or null if no prefix is specified. This property is read only."
+      },
+      "namespaceURI": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.namespaceURI",
+        "!doc": "The namespace URI of the node, or null if the node is not in a namespace (read-only). When the node is a document, it returns the XML namespace for the current document."
+      },
+      "ownerDocument": {
+        "!type": "+Document",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.ownerDocument",
+        "!doc": "The ownerDocument property returns the top-level document object for this node."
+      },
+      "attributes": {
+        "!type": "+NamedNodeMap",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.attributes",
+        "!doc": "A collection of all attribute nodes registered to the specified node. It is a NamedNodeMap,not an Array, so it has no Array methods and the Attr nodes' indexes may differ among browsers."
+      },
+      "nextSibling": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.nextSibling",
+        "!doc": "Returns the node immediately following the specified one in its parent's childNodes list, or null if the specified node is the last node in that list."
+      },
+      "previousSibling": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.previousSibling",
+        "!doc": "Returns the node immediately preceding the specified one in its parent's childNodes list, null if the specified node is the first in that list."
+      },
+      "lastChild": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.lastChild",
+        "!doc": "Returns the last child of a node."
+      },
+      "firstChild": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.firstChild",
+        "!doc": "Returns the node's first child in the tree, or null if the node is childless. If the node is a Document, it returns the first node in the list of its direct children."
+      },
+      "childNodes": {
+        "!type": "+NodeList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.childNodes",
+        "!doc": "Returns a collection of child nodes of the given element."
+      },
+      "parentNode": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.parentNode",
+        "!doc": "Returns the parent of the specified node in the DOM tree."
+      },
+      "nodeType": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.nodeType",
+        "!doc": "Returns an integer code representing the type of the node."
+      },
+      "nodeValue": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.nodeValue",
+        "!doc": "Returns or sets the value of the current node."
+      },
+      "nodeName": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.nodeName",
+        "!doc": "Returns the name of the current node as a string."
+      },
+      "tagName": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.nodeName",
+        "!doc": "Returns the name of the current node as a string."
+      },
+      "insertBefore": {
+        "!type": "fn(newElt: +Element, before: +Element) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.insertBefore",
+        "!doc": "Inserts the specified node before a reference element as a child of the current node."
+      },
+      "replaceChild": {
+        "!type": "fn(newElt: +Element, oldElt: +Element) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.replaceChild",
+        "!doc": "Replaces one child node of the specified element with another."
+      },
+      "removeChild": {
+        "!type": "fn(oldElt: +Element) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.removeChild",
+        "!doc": "Removes a child node from the DOM. Returns removed node."
+      },
+      "appendChild": {
+        "!type": "fn(newElt: +Element) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.appendChild",
+        "!doc": "Adds a node to the end of the list of children of a specified parent node. If the node already exists it is removed from current parent node, then added to new parent node."
+      },
+      "hasChildNodes": {
+        "!type": "fn() -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.hasChildNodes",
+        "!doc": "Returns a Boolean value indicating whether the current Node has child nodes or not."
+      },
+      "cloneNode": {
+        "!type": "fn(deep: bool) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.cloneNode",
+        "!doc": "Returns a duplicate of the node on which this method was called."
+      },
+      "normalize": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.normalize",
+        "!doc": "Puts the specified node and all of its subtree into a \"normalized\" form. In a normalized subtree, no text nodes in the subtree are empty and there are no adjacent text nodes."
+      },
+      "isSupported": {
+        "!type": "fn(features: string, version: number) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.isSupported",
+        "!doc": "Tests whether the DOM implementation implements a specific feature and that feature is supported by this node."
+      },
+      "hasAttributes": {
+        "!type": "fn() -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.hasAttributes",
+        "!doc": "Returns a boolean value of true or false, indicating if the current element has any attributes or not."
+      },
+      "lookupPrefix": {
+        "!type": "fn(uri: string) -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.lookupPrefix",
+        "!doc": "Returns the prefix for a given namespaceURI if present, and null if not. When multiple prefixes are possible, the result is implementation-dependent."
+      },
+      "isDefaultNamespace": {
+        "!type": "fn(uri: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.isDefaultNamespace",
+        "!doc": "Accepts a namespace URI as an argument and returns true if the namespace is the default namespace on the given node or false if not."
+      },
+      "lookupNamespaceURI": {
+        "!type": "fn(uri: string) -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.lookupNamespaceURI",
+        "!doc": "Takes a prefix and returns the namespaceURI associated with it on the given node if found (and null if not). Supplying null for the prefix will return the default namespace."
+      },
+      "addEventListener": {
+        "!type": "fn(type: string, listener: fn(e: +Event), capture: bool)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/EventTarget.addEventListener",
+        "!doc": "Registers a single event listener on a single target. The event target may be a single element in a document, the document itself, a window, or an XMLHttpRequest."
+      },
+      "removeEventListener": {
+        "!type": "fn(type: string, listener: fn(), capture: bool)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/EventTarget.removeEventListener",
+        "!doc": "Allows the removal of event listeners from the event target."
+      },
+      "isSameNode": {
+        "!type": "fn(other: +Node) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.isSameNode",
+        "!doc": "Tests whether two nodes are the same, that is they reference the same object."
+      },
+      "isEqualNode": {
+        "!type": "fn(other: +Node) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.isEqualNode",
+        "!doc": "Tests whether two nodes are equal."
+      },
+      "compareDocumentPosition": {
+        "!type": "fn(other: +Node) -> number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.compareDocumentPosition",
+        "!doc": "Compares the position of the current node against another node in any other document."
+      },
+      "contains": {
+        "!type": "fn(other: +Node) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Node.contains",
+        "!doc": "Indicates whether a node is a descendent of a given node."
+      },
+      "dispatchEvent": {
+        "!type": "fn(event: +Event) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/EventTarget.dispatchEvent",
+        "!doc": "Dispatches an event into the event system. The event is subject to the same capturing and bubbling behavior as directly dispatched events."
+      },
+      "ELEMENT_NODE": "number",
+      "ATTRIBUTE_NODE": "number",
+      "TEXT_NODE": "number",
+      "CDATA_SECTION_NODE": "number",
+      "ENTITY_REFERENCE_NODE": "number",
+      "ENTITY_NODE": "number",
+      "PROCESSING_INSTRUCTION_NODE": "number",
+      "COMMENT_NODE": "number",
+      "DOCUMENT_NODE": "number",
+      "DOCUMENT_TYPE_NODE": "number",
+      "DOCUMENT_FRAGMENT_NODE": "number",
+      "NOTATION_NODE": "number",
+      "DOCUMENT_POSITION_DISCONNECTED": "number",
+      "DOCUMENT_POSITION_PRECEDING": "number",
+      "DOCUMENT_POSITION_FOLLOWING": "number",
+      "DOCUMENT_POSITION_CONTAINS": "number",
+      "DOCUMENT_POSITION_CONTAINED_BY": "number",
+      "DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC": "number"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Node",
+    "!doc": "A Node is an interface from which a number of DOM types inherit, and allows these various types to be treated (or tested) similarly."
+  },
+  "Element": {
+    "!type": "fn()",
+    "prototype": {
+      "!proto": "Node.prototype",
+      "getAttribute": {
+        "!type": "fn(name: string) -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getAttribute",
+        "!doc": "Returns the value of the named attribute on the specified element. If the named attribute does not exist, the value returned will either be null or \"\" (the empty string)."
+      },
+      "setAttribute": {
+        "!type": "fn(name: string, value: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.setAttribute",
+        "!doc": "Adds a new attribute or changes the value of an existing attribute on the specified element."
+      },
+      "removeAttribute": {
+        "!type": "fn(name: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.removeAttribute",
+        "!doc": "Removes an attribute from the specified element."
+      },
+      "getAttributeNode": {
+        "!type": "fn(name: string) -> +Attr",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getAttributeNode",
+        "!doc": "Returns the specified attribute of the specified element, as an Attr node."
+      },
+      "getElementsByTagName": {
+        "!type": "fn(tagName: string) -> +NodeList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getElementsByTagName",
+        "!doc": "Returns a list of elements with the given tag name. The subtree underneath the specified element is searched, excluding the element itself. The returned list is live, meaning that it updates itself with the DOM tree automatically. Consequently, there is no need to call several times element.getElementsByTagName with the same element and arguments."
+      },
+      "getElementsByTagNameNS": {
+        "!type": "fn(ns: string, tagName: string) -> +NodeList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getElementsByTagNameNS",
+        "!doc": "Returns a list of elements with the given tag name belonging to the given namespace."
+      },
+      "getAttributeNS": {
+        "!type": "fn(ns: string, name: string) -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getAttributeNS",
+        "!doc": "Returns the string value of the attribute with the specified namespace and name. If the named attribute does not exist, the value returned will either be null or \"\" (the empty string)."
+      },
+      "setAttributeNS": {
+        "!type": "fn(ns: string, name: string, value: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.setAttributeNS",
+        "!doc": "Adds a new attribute or changes the value of an attribute with the given namespace and name."
+      },
+      "removeAttributeNS": {
+        "!type": "fn(ns: string, name: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.removeAttributeNS",
+        "!doc": "removeAttributeNS removes the specified attribute from an element."
+      },
+      "getAttributeNodeNS": {
+        "!type": "fn(ns: string, name: string) -> +Attr",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getAttributeNodeNS",
+        "!doc": "Returns the Attr node for the attribute with the given namespace and name."
+      },
+      "hasAttribute": {
+        "!type": "fn(name: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.hasAttribute",
+        "!doc": "hasAttribute returns a boolean value indicating whether the specified element has the specified attribute or not."
+      },
+      "hasAttributeNS": {
+        "!type": "fn(ns: string, name: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.hasAttributeNS",
+        "!doc": "hasAttributeNS returns a boolean value indicating whether the current element has the specified attribute."
+      },
+      "focus": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.focus",
+        "!doc": "Sets focus on the specified element, if it can be focused."
+      },
+      "blur": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.blur",
+        "!doc": "The blur method removes keyboard focus from the current element."
+      },
+      "scrollIntoView": {
+        "!type": "fn(top: bool)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.scrollIntoView",
+        "!doc": "The scrollIntoView() method scrolls the element into view."
+      },
+      "scrollByLines": {
+        "!type": "fn(lines: number)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollByLines",
+        "!doc": "Scrolls the document by the given number of lines."
+      },
+      "scrollByPages": {
+        "!type": "fn(pages: number)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollByPages",
+        "!doc": "Scrolls the current document by the specified number of pages."
+      },
+      "getElementsByClassName": {
+        "!type": "fn(name: string) -> +NodeList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.getElementsByClassName",
+        "!doc": "Returns a set of elements which have all the given class names. When called on the document object, the complete document is searched, including the root node. You may also call getElementsByClassName on any element; it will return only elements which are descendants of the specified root element with the given class names."
+      },
+      "querySelector": {
+        "!type": "fn(selectors: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.querySelector",
+        "!doc": "Returns the first element that is a descendent of the element on which it is invoked that matches the specified group of selectors."
+      },
+      "querySelectorAll": {
+        "!type": "fn(selectors: string) -> +NodeList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.querySelectorAll",
+        "!doc": "Returns a non-live NodeList of all elements descended from the element on which it is invoked that match the specified group of CSS selectors."
+      },
+      "getClientRects": {
+        "!type": "fn() -> [+ClientRect]",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getClientRects",
+        "!doc": "Returns a collection of rectangles that indicate the bounding rectangles for each box in a client."
+      },
+      "getBoundingClientRect": {
+        "!type": "fn() -> +ClientRect",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getBoundingClientRect",
+        "!doc": "Returns a text rectangle object that encloses a group of text rectangles."
+      },
+      "setAttributeNode": {
+        "!type": "fn(attr: +Attr) -> +Attr",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.setAttributeNode",
+        "!doc": "Adds a new Attr node to the specified element."
+      },
+      "removeAttributeNode": {
+        "!type": "fn(attr: +Attr) -> +Attr",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.removeAttributeNode",
+        "!doc": "Removes the specified attribute from the current element."
+      },
+      "setAttributeNodeNS": {
+        "!type": "fn(attr: +Attr) -> +Attr",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.setAttributeNodeNS",
+        "!doc": "Adds a new namespaced attribute node to an element."
+      },
+      "insertAdjacentHTML": {
+        "!type": "fn(position: string, text: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.insertAdjacentHTML",
+        "!doc": "Parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position. It does not reparse the element it is being used on and thus it does not corrupt the existing elements inside the element. This, and avoiding the extra step of serialization make it much faster than direct innerHTML manipulation."
+      },
+      "children": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.children",
+        "!doc": "Returns a collection of child elements of the given element."
+      },
+      "childElementCount": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.childElementCount",
+        "!doc": "Returns the number of child elements of the given element."
+      },
+      "className": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.className",
+        "!doc": "Gets and sets the value of the class attribute of the specified element."
+      },
+      "style": {
+        "cssText": "string",
+        "alignmentBaseline": "string",
+        "background": "string",
+        "backgroundAttachment": "string",
+        "backgroundClip": "string",
+        "backgroundColor": "string",
+        "backgroundImage": "string",
+        "backgroundOrigin": "string",
+        "backgroundPosition": "string",
+        "backgroundPositionX": "string",
+        "backgroundPositionY": "string",
+        "backgroundRepeat": "string",
+        "backgroundRepeatX": "string",
+        "backgroundRepeatY": "string",
+        "backgroundSize": "string",
+        "baselineShift": "string",
+        "border": "string",
+        "borderBottom": "string",
+        "borderBottomColor": "string",
+        "borderBottomLeftRadius": "string",
+        "borderBottomRightRadius": "string",
+        "borderBottomStyle": "string",
+        "borderBottomWidth": "string",
+        "borderCollapse": "string",
+        "borderColor": "string",
+        "borderImage": "string",
+        "borderImageOutset": "string",
+        "borderImageRepeat": "string",
+        "borderImageSlice": "string",
+        "borderImageSource": "string",
+        "borderImageWidth": "string",
+        "borderLeft": "string",
+        "borderLeftColor": "string",
+        "borderLeftStyle": "string",
+        "borderLeftWidth": "string",
+        "borderRadius": "string",
+        "borderRight": "string",
+        "borderRightColor": "string",
+        "borderRightStyle": "string",
+        "borderRightWidth": "string",
+        "borderSpacing": "string",
+        "borderStyle": "string",
+        "borderTop": "string",
+        "borderTopColor": "string",
+        "borderTopLeftRadius": "string",
+        "borderTopRightRadius": "string",
+        "borderTopStyle": "string",
+        "borderTopWidth": "string",
+        "borderWidth": "string",
+        "bottom": "string",
+        "boxShadow": "string",
+        "boxSizing": "string",
+        "captionSide": "string",
+        "clear": "string",
+        "clip": "string",
+        "clipPath": "string",
+        "clipRule": "string",
+        "color": "string",
+        "colorInterpolation": "string",
+        "colorInterpolationFilters": "string",
+        "colorProfile": "string",
+        "colorRendering": "string",
+        "content": "string",
+        "counterIncrement": "string",
+        "counterReset": "string",
+        "cursor": "string",
+        "direction": "string",
+        "display": "string",
+        "dominantBaseline": "string",
+        "emptyCells": "string",
+        "enableBackground": "string",
+        "fill": "string",
+        "fillOpacity": "string",
+        "fillRule": "string",
+        "filter": "string",
+        "float": "string",
+        "floodColor": "string",
+        "floodOpacity": "string",
+        "font": "string",
+        "fontFamily": "string",
+        "fontSize": "string",
+        "fontStretch": "string",
+        "fontStyle": "string",
+        "fontVariant": "string",
+        "fontWeight": "string",
+        "glyphOrientationHorizontal": "string",
+        "glyphOrientationVertical": "string",
+        "height": "string",
+        "imageRendering": "string",
+        "kerning": "string",
+        "left": "string",
+        "letterSpacing": "string",
+        "lightingColor": "string",
+        "lineHeight": "string",
+        "listStyle": "string",
+        "listStyleImage": "string",
+        "listStylePosition": "string",
+        "listStyleType": "string",
+        "margin": "string",
+        "marginBottom": "string",
+        "marginLeft": "string",
+        "marginRight": "string",
+        "marginTop": "string",
+        "marker": "string",
+        "markerEnd": "string",
+        "markerMid": "string",
+        "markerStart": "string",
+        "mask": "string",
+        "maxHeight": "string",
+        "maxWidth": "string",
+        "minHeight": "string",
+        "minWidth": "string",
+        "opacity": "string",
+        "orphans": "string",
+        "outline": "string",
+        "outlineColor": "string",
+        "outlineOffset": "string",
+        "outlineStyle": "string",
+        "outlineWidth": "string",
+        "overflow": "string",
+        "overflowWrap": "string",
+        "overflowX": "string",
+        "overflowY": "string",
+        "padding": "string",
+        "paddingBottom": "string",
+        "paddingLeft": "string",
+        "paddingRight": "string",
+        "paddingTop": "string",
+        "page": "string",
+        "pageBreakAfter": "string",
+        "pageBreakBefore": "string",
+        "pageBreakInside": "string",
+        "pointerEvents": "string",
+        "position": "string",
+        "quotes": "string",
+        "resize": "string",
+        "right": "string",
+        "shapeRendering": "string",
+        "size": "string",
+        "speak": "string",
+        "src": "string",
+        "stopColor": "string",
+        "stopOpacity": "string",
+        "stroke": "string",
+        "strokeDasharray": "string",
+        "strokeDashoffset": "string",
+        "strokeLinecap": "string",
+        "strokeLinejoin": "string",
+        "strokeMiterlimit": "string",
+        "strokeOpacity": "string",
+        "strokeWidth": "string",
+        "tabSize": "string",
+        "tableLayout": "string",
+        "textAlign": "string",
+        "textAnchor": "string",
+        "textDecoration": "string",
+        "textIndent": "string",
+        "textLineThrough": "string",
+        "textLineThroughColor": "string",
+        "textLineThroughMode": "string",
+        "textLineThroughStyle": "string",
+        "textLineThroughWidth": "string",
+        "textOverflow": "string",
+        "textOverline": "string",
+        "textOverlineColor": "string",
+        "textOverlineMode": "string",
+        "textOverlineStyle": "string",
+        "textOverlineWidth": "string",
+        "textRendering": "string",
+        "textShadow": "string",
+        "textTransform": "string",
+        "textUnderline": "string",
+        "textUnderlineColor": "string",
+        "textUnderlineMode": "string",
+        "textUnderlineStyle": "string",
+        "textUnderlineWidth": "string",
+        "top": "string",
+        "unicodeBidi": "string",
+        "unicodeRange": "string",
+        "vectorEffect": "string",
+        "verticalAlign": "string",
+        "visibility": "string",
+        "whiteSpace": "string",
+        "width": "string",
+        "wordBreak": "string",
+        "wordSpacing": "string",
+        "wordWrap": "string",
+        "writingMode": "string",
+        "zIndex": "string",
+        "zoom": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.style",
+        "!doc": "Returns an object that represents the element's style attribute."
+      },
+      "classList": {
+        "!type": "+DOMTokenList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.classList",
+        "!doc": "Returns a token list of the class attribute of the element."
+      },
+      "contentEditable": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.contentEditable",
+        "!doc": "Indicates whether or not the element is editable."
+      },
+      "firstElementChild": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.firstElementChild",
+        "!doc": "Returns the element's first child element or null if there are no child elements."
+      },
+      "lastElementChild": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.lastElementChild",
+        "!doc": "Returns the element's last child element or null if there are no child elements."
+      },
+      "nextElementSibling": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.nextElementSibling",
+        "!doc": "Returns the element immediately following the specified one in its parent's children list, or null if the specified element is the last one in the list."
+      },
+      "previousElementSibling": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Element.previousElementSibling",
+        "!doc": "Returns the element immediately prior to the specified one in its parent's children list, or null if the specified element is the first one in the list."
+      },
+      "tabIndex": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.tabIndex",
+        "!doc": "Gets/sets the tab order of the current element."
+      },
+      "title": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.title",
+        "!doc": "Establishes the text to be displayed in a 'tool tip' popup when the mouse is over the displayed node."
+      },
+      "width": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.offsetWidth",
+        "!doc": "Returns the layout width of an element."
+      },
+      "height": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.offsetHeight",
+        "!doc": "Height of an element relative to the element's offsetParent."
+      },
+      "getContext": {
+        "!type": "fn(id: string) -> CanvasRenderingContext2D",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/HTMLCanvasElement",
+        "!doc": "DOM canvas elements expose the HTMLCanvasElement interface, which provides properties and methods for manipulating the layout and presentation of canvas elements. The HTMLCanvasElement interface inherits the properties and methods of the element object interface."
+      },
+      "supportsContext": "fn(id: string) -> bool",
+      "oncopy": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.oncopy",
+        "!doc": "The oncopy property returns the onCopy event handler code on the current element."
+      },
+      "oncut": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.oncut",
+        "!doc": "The oncut property returns the onCut event handler code on the current element."
+      },
+      "onpaste": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onpaste",
+        "!doc": "The onpaste property returns the onPaste event handler code on the current element."
+      },
+      "onbeforeunload": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/HTML/Element/body",
+        "!doc": "The HTML <body> element represents the main content of an HTML document. There is only one <body> element in a document."
+      },
+      "onfocus": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onfocus",
+        "!doc": "The onfocus property returns the onFocus event handler code on the current element."
+      },
+      "onblur": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onblur",
+        "!doc": "The onblur property returns the onBlur event handler code, if any, that exists on the current element."
+      },
+      "onchange": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onchange",
+        "!doc": "The onchange property sets and returns the onChange event handler code for the current element."
+      },
+      "onclick": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onclick",
+        "!doc": "The onclick property returns the onClick event handler code on the current element."
+      },
+      "ondblclick": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.ondblclick",
+        "!doc": "The ondblclick property returns the onDblClick event handler code on the current element."
+      },
+      "onmousedown": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmousedown",
+        "!doc": "The onmousedown property returns the onMouseDown event handler code on the current element."
+      },
+      "onmouseup": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmouseup",
+        "!doc": "The onmouseup property returns the onMouseUp event handler code on the current element."
+      },
+      "onmousewheel": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/wheel",
+        "!doc": "The wheel event is fired when a wheel button of a pointing device (usually a mouse) is rotated. This event deprecates the legacy mousewheel event."
+      },
+      "onmouseover": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmouseover",
+        "!doc": "The onmouseover property returns the onMouseOver event handler code on the current element."
+      },
+      "onmouseout": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmouseout",
+        "!doc": "The onmouseout property returns the onMouseOut event handler code on the current element."
+      },
+      "onmousemove": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmousemove",
+        "!doc": "The onmousemove property returns the mousemove event handler code on the current element."
+      },
+      "oncontextmenu": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/window.oncontextmenu",
+        "!doc": "An event handler property for right-click events on the window. Unless the default behavior is prevented, the browser context menu will activate. Note that this event will occur with any non-disabled right-click event and does not depend on an element possessing the \"contextmenu\" attribute."
+      },
+      "onkeydown": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onkeydown",
+        "!doc": "The onkeydown property returns the onKeyDown event handler code on the current element."
+      },
+      "onkeyup": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onkeyup",
+        "!doc": "The onkeyup property returns the onKeyUp event handler code for the current element."
+      },
+      "onkeypress": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onkeypress",
+        "!doc": "The onkeypress property sets and returns the onKeyPress event handler code for the current element."
+      },
+      "onresize": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onresize",
+        "!doc": "onresize returns the element's onresize event handler code. It can also be used to set the code to be executed when the resize event occurs."
+      },
+      "onscroll": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.onscroll",
+        "!doc": "The onscroll property returns the onScroll event handler code on the current element."
+      },
+      "ondragstart": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DragDrop/Drag_Operations",
+        "!doc": "The following describes the steps that occur during a drag and drop operation."
+      },
+      "ondragover": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragover",
+        "!doc": "The dragover event is fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds)."
+      },
+      "ondragleave": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragleave",
+        "!doc": "The dragleave event is fired when a dragged element or text selection leaves a valid drop target."
+      },
+      "ondragenter": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragenter",
+        "!doc": "The dragenter event is fired when a dragged element or text selection enters a valid drop target."
+      },
+      "ondragend": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragend",
+        "!doc": "The dragend event is fired when a drag operation is being ended (by releasing a mouse button or hitting the escape key)."
+      },
+      "ondrag": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/drag",
+        "!doc": "The drag event is fired when an element or text selection is being dragged (every few hundred milliseconds)."
+      },
+      "offsetTop": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.offsetTop",
+        "!doc": "Returns the distance of the current element relative to the top of the offsetParent node."
+      },
+      "offsetLeft": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.offsetLeft",
+        "!doc": "Returns the number of pixels that the upper left corner of the current element is offset to the left within the offsetParent node."
+      },
+      "offsetHeight": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.offsetHeight",
+        "!doc": "Height of an element relative to the element's offsetParent."
+      },
+      "offsetWidth": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.offsetWidth",
+        "!doc": "Returns the layout width of an element."
+      },
+      "scrollTop": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.scrollTop",
+        "!doc": "Gets or sets the number of pixels that the content of an element is scrolled upward."
+      },
+      "scrollLeft": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.scrollLeft",
+        "!doc": "Gets or sets the number of pixels that an element's content is scrolled to the left."
+      },
+      "scrollHeight": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.scrollHeight",
+        "!doc": "Height of the scroll view of an element; it includes the element padding but not its margin."
+      },
+      "scrollWidth": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.scrollWidth",
+        "!doc": "Read-only property that returns either the width in pixels of the content of an element or the width of the element itself, whichever is greater."
+      },
+      "clientTop": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.clientTop",
+        "!doc": "The width of the top border of an element in pixels. It does not include the top margin or padding. clientTop is read-only."
+      },
+      "clientLeft": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.clientLeft",
+        "!doc": "The width of the left border of an element in pixels. It includes the width of the vertical scrollbar if the text direction of the element is right-to-left and if there is an overflow causing a left vertical scrollbar to be rendered. clientLeft does not include the left margin or the left padding. clientLeft is read-only."
+      },
+      "clientHeight": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.clientHeight",
+        "!doc": "Returns the inner height of an element in pixels, including padding but not the horizontal scrollbar height, border, or margin."
+      },
+      "clientWidth": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.clientWidth",
+        "!doc": "The inner width of an element in pixels. It includes padding but not the vertical scrollbar (if present, if rendered), border or margin."
+      },
+      "innerHTML": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.innerHTML",
+        "!doc": "Sets or gets the HTML syntax describing the element's descendants."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Element",
+    "!doc": "Represents an element in an HTML or XML document."
+  },
+  "Text": {
+    "!type": "fn()",
+    "prototype": {
+      "!proto": "Node.prototype",
+      "wholeText": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Text.wholeText",
+        "!doc": "Returns all text of all Text nodes logically adjacent to the node.  The text is concatenated in document order.  This allows you to specify any text node and obtain all adjacent text as a single string."
+      },
+      "splitText": {
+        "!type": "fn(offset: number) -> +Text",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Text.splitText",
+        "!doc": "Breaks the Text node into two nodes at the specified offset, keeping both nodes in the tree as siblings."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Text",
+    "!doc": "In the DOM, the Text interface represents the textual content of an Element or Attr.  If an element has no markup within its content, it has a single child implementing Text that contains the element's text.  However, if the element contains markup, it is parsed into information items and Text nodes that form its children."
+  },
+  "Document": {
+    "!type": "fn()",
+    "prototype": {
+      "!proto": "Node.prototype",
+      "activeElement": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.activeElement",
+        "!doc": "Returns the currently focused element, that is, the element that will get keystroke events if the user types any. This attribute is read only."
+      },
+      "compatMode": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.compatMode",
+        "!doc": "Indicates whether the document is rendered in Quirks mode or Strict mode."
+      },
+      "designMode": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.designMode",
+        "!doc": "Can be used to make any document editable, for example in a <iframe />:"
+      },
+      "dir": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Document.dir",
+        "!doc": "This property should indicate and allow the setting of the directionality of the text of the document, whether left to right (default) or right to left."
+      },
+      "height": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.height",
+        "!doc": "Returns the height of the <body> element of the current document."
+      },
+      "width": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.width",
+        "!doc": "Returns the width of the <body> element of the current document in pixels."
+      },
+      "characterSet": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.characterSet",
+        "!doc": "Returns the character encoding of the current document."
+      },
+      "readyState": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.readyState",
+        "!doc": "Returns \"loading\" while the document is loading, \"interactive\" once it is finished parsing but still loading sub-resources, and \"complete\" once it has loaded."
+      },
+      "location": {
+        "!type": "location",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.location",
+        "!doc": "Returns a Location object, which contains information about the URL of the document and provides methods for changing that URL."
+      },
+      "lastModified": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.lastModified",
+        "!doc": "Returns a string containing the date and time on which the current document was last modified."
+      },
+      "head": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.head",
+        "!doc": "Returns the <head> element of the current document. If there are more than one <head> elements, the first one is returned."
+      },
+      "body": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.body",
+        "!doc": "Returns the <body> or <frameset> node of the current document."
+      },
+      "cookie": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.cookie",
+        "!doc": "Get and set the cookies associated with the current document."
+      },
+      "URL": "string",
+      "domain": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.domain",
+        "!doc": "Gets/sets the domain portion of the origin of the current document, as used by the same origin policy."
+      },
+      "referrer": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.referrer",
+        "!doc": "Returns the URI of the page that linked to this page."
+      },
+      "title": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.title",
+        "!doc": "Gets or sets the title of the document."
+      },
+      "defaultView": {
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.defaultView",
+        "!doc": "In browsers returns the window object associated with the document or null if none available."
+      },
+      "documentURI": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.documentURI",
+        "!doc": "Returns the document location as string. It is read-only per DOM4 specification."
+      },
+      "xmlStandalone": "bool",
+      "xmlVersion": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.xmlVersion",
+        "!doc": "Returns the version number as specified in the XML declaration (e.g., <?xml version=\"1.0\"?>) or \"1.0\" if the declaration is absent."
+      },
+      "xmlEncoding": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Document.xmlEncoding",
+        "!doc": "Returns the encoding as determined by the XML declaration. Should be null if unspecified or unknown."
+      },
+      "inputEncoding": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.inputEncoding",
+        "!doc": "Returns a string representing the encoding under which the document was parsed (e.g. ISO-8859-1)."
+      },
+      "documentElement": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.documentElement",
+        "!doc": "Read-only"
+      },
+      "implementation": {
+        "hasFeature": "fn(feature: string, version: number) -> bool",
+        "createDocumentType": {
+          "!type": "fn(qualifiedName: string, publicId: string, systemId: string) -> +Node",
+          "!url": "https://developer.mozilla.org/en/docs/DOM/DOMImplementation.createDocumentType",
+          "!doc": "Returns a DocumentType object which can either be used with DOMImplementation.createDocument upon document creation or they can be put into the document via Node.insertBefore() or Node.replaceChild(): http://www.w3.org/TR/DOM-Level-3-Cor...l#ID-B63ED1A31 (less ideal due to features not likely being as accessible: http://www.w3.org/TR/DOM-Level-3-Cor...createDocument ). In any case, entity declarations and notations will not be available: http://www.w3.org/TR/DOM-Level-3-Cor...-createDocType   "
+        },
+        "createHTMLDocument": {
+          "!type": "fn(title: string) -> +Document",
+          "!url": "https://developer.mozilla.org/en/docs/DOM/DOMImplementation.createHTMLDocument",
+          "!doc": "This method (available from document.implementation) creates a new HTML document."
+        },
+        "createDocument": {
+          "!type": "fn(namespaceURI: string, qualifiedName: string, type: +Node) -> +Document",
+          "!url": "https://developer.mozilla.org/en-US/docs/DOM/DOMImplementation.createHTMLDocument",
+          "!doc": "This method creates a new HTML document."
+        },
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.implementation",
+        "!doc": "Returns a DOMImplementation object associated with the current document."
+      },
+      "doctype": {
+        "!type": "+Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.doctype",
+        "!doc": "Returns the Document Type Declaration (DTD) associated with current document. The returned object implements the DocumentType interface. Use DOMImplementation.createDocumentType() to create a DocumentType."
+      },
+      "open": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.open",
+        "!doc": "The document.open() method opens a document for writing."
+      },
+      "close": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.close",
+        "!doc": "The document.close() method finishes writing to a document, opened with document.open()."
+      },
+      "write": {
+        "!type": "fn(html: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.write",
+        "!doc": "Writes a string of text to a document stream opened by document.open()."
+      },
+      "writeln": {
+        "!type": "fn(html: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.writeln",
+        "!doc": "Writes a string of text followed by a newline character to a document."
+      },
+      "clear": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.clear",
+        "!doc": "In recent versions of Mozilla-based applications as well as in Internet Explorer and Netscape 4 this method does nothing."
+      },
+      "hasFocus": {
+        "!type": "fn() -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.hasFocus",
+        "!doc": "Returns a Boolean value indicating whether the document or any element inside the document has focus. This method can be used to determine whether the active element in a document has focus."
+      },
+      "createElement": {
+        "!type": "fn(tagName: string) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createElement",
+        "!doc": "Creates the specified element."
+      },
+      "createElementNS": {
+        "!type": "fn(ns: string, tagName: string) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createElementNS",
+        "!doc": "Creates an element with the specified namespace URI and qualified name."
+      },
+      "createDocumentFragment": {
+        "!type": "fn() -> +DocumentFragment",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createDocumentFragment",
+        "!doc": "Creates a new empty DocumentFragment."
+      },
+      "createTextNode": {
+        "!type": "fn(content: string) -> +Text",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createTextNode",
+        "!doc": "Creates a new Text node."
+      },
+      "createComment": {
+        "!type": "fn(content: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createComment",
+        "!doc": "Creates a new comment node, and returns it."
+      },
+      "createCDATASection": {
+        "!type": "fn(content: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createCDATASection",
+        "!doc": "Creates a new CDATA section node, and returns it. "
+      },
+      "createProcessingInstruction": {
+        "!type": "fn(content: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createProcessingInstruction",
+        "!doc": "Creates a new processing instruction node, and returns it."
+      },
+      "createAttribute": {
+        "!type": "fn(name: string) -> +Attr",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createAttribute",
+        "!doc": "Creates a new attribute node, and returns it."
+      },
+      "createAttributeNS": {
+        "!type": "fn(ns: string, name: string) -> +Attr",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Attr",
+        "!doc": "This type represents a DOM element's attribute as an object. In most DOM methods, you will probably directly retrieve the attribute as a string (e.g., Element.getAttribute(), but certain functions (e.g., Element.getAttributeNode()) or means of iterating give Attr types."
+      },
+      "importNode": {
+        "!type": "fn(node: +Node, deep: bool) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.importNode",
+        "!doc": "Creates a copy of a node from an external document that can be inserted into the current document."
+      },
+      "getElementById": {
+        "!type": "fn(id: string) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.getElementById",
+        "!doc": "Returns a reference to the element by its ID."
+      },
+      "getElementsByTagName": {
+        "!type": "fn(tagName: string) -> +NodeList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.getElementsByTagName",
+        "!doc": "Returns a NodeList of elements with the given tag name. The complete document is searched, including the root node. The returned NodeList is live, meaning that it updates itself automatically to stay in sync with the DOM tree without having to call document.getElementsByTagName again."
+      },
+      "getElementsByTagNameNS": {
+        "!type": "fn(ns: string, tagName: string) -> +NodeList",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.getElementsByTagNameNS",
+        "!doc": "Returns a list of elements with the given tag name belonging to the given namespace. The complete document is searched, including the root node."
+      },
+      "createEvent": {
+        "!type": "fn(type: string) -> +Event",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createEvent",
+        "!doc": "Creates an event of the type specified. The returned object should be first initialized and can then be passed to element.dispatchEvent."
+      },
+      "createRange": {
+        "!type": "fn() -> +Range",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createRange",
+        "!doc": "Returns a new Range object."
+      },
+      "evaluate": {
+        "!type": "fn(expr: ?) -> +XPathResult",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.evaluate",
+        "!doc": "Returns an XPathResult based on an XPath expression and other given parameters."
+      },
+      "execCommand": {
+        "!type": "fn(cmd: string)",
+        "!url": "https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla#Executing_Commands",
+        "!doc": "Run command to manipulate the contents of an editable region."
+      },
+      "queryCommandEnabled": {
+        "!type": "fn(cmd: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document",
+        "!doc": "Returns true if the Midas command can be executed on the current range."
+      },
+      "queryCommandIndeterm": {
+        "!type": "fn(cmd: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document",
+        "!doc": "Returns true if the Midas command is in a indeterminate state on the current range."
+      },
+      "queryCommandState": {
+        "!type": "fn(cmd: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document",
+        "!doc": "Returns true if the Midas command has been executed on the current range."
+      },
+      "queryCommandSupported": {
+        "!type": "fn(cmd: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.queryCommandSupported",
+        "!doc": "Reports whether or not the specified editor query command is supported by the browser."
+      },
+      "queryCommandValue": {
+        "!type": "fn(cmd: string) -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document",
+        "!doc": "Returns the current value of the current range for Midas command."
+      },
+      "getElementsByName": {
+        "!type": "fn(name: string) -> +HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.getElementsByName",
+        "!doc": "Returns a list of elements with a given name in the HTML document."
+      },
+      "elementFromPoint": {
+        "!type": "fn(x: number, y: number) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.elementFromPoint",
+        "!doc": "Returns the element from the document whose elementFromPoint method is being called which is the topmost element which lies under the given point.  To get an element, specify the point via coordinates, in CSS pixels, relative to the upper-left-most point in the window or frame containing the document."
+      },
+      "getSelection": {
+        "!type": "fn() -> +Selection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.getSelection",
+        "!doc": "The DOM getSelection() method is available on the Window and Document interfaces."
+      },
+      "adoptNode": {
+        "!type": "fn(node: +Node) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.adoptNode",
+        "!doc": "Adopts a node from an external document. The node and its subtree is removed from the document it's in (if any), and its ownerDocument is changed to the current document. The node can then be inserted into the current document."
+      },
+      "createTreeWalker": {
+        "!type": "fn(root: +Node, mask: number) -> ?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createTreeWalker",
+        "!doc": "Returns a new TreeWalker object."
+      },
+      "createExpression": {
+        "!type": "fn(text: string) -> ?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createExpression",
+        "!doc": "This method compiles an XPathExpression which can then be used for (repeated) evaluations."
+      },
+      "createNSResolver": {
+        "!type": "fn(node: +Node)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.createNSResolver",
+        "!doc": "Creates an XPathNSResolver which resolves namespaces with respect to the definitions in scope for a specified node."
+      },
+      "scripts": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Document.scripts",
+        "!doc": "Returns a list of the <script> elements in the document. The returned object is an HTMLCollection."
+      },
+      "plugins": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.plugins",
+        "!doc": "Returns an HTMLCollection object containing one or more HTMLEmbedElements or null which represent the <embed> elements in the current document."
+      },
+      "embeds": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.embeds",
+        "!doc": "Returns a list of the embedded OBJECTS within the current document."
+      },
+      "anchors": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.anchors",
+        "!doc": "Returns a list of all of the anchors in the document."
+      },
+      "links": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.links",
+        "!doc": "The links property returns a collection of all AREA elements and anchor elements in a document with a value for the href attribute. "
+      },
+      "forms": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.forms",
+        "!doc": "Returns a collection (an HTMLCollection) of the form elements within the current document."
+      },
+      "styleSheets": {
+        "!type": "+HTMLCollection",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.styleSheets",
+        "!doc": "Returns a list of stylesheet objects for stylesheets explicitly linked into or embedded in a document."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/document",
+    "!doc": "Each web page loaded in the browser has its own document object. This object serves as an entry point to the web page's content (the DOM tree, including elements such as <body> and <table>) and provides functionality global to the document (such as obtaining the page's URL and creating new elements in the document)."
+  },
+  "document": {
+    "!type": "+Document",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/document",
+    "!doc": "Each web page loaded in the browser has its own document object. This object serves as an entry point to the web page's content (the DOM tree, including elements such as <body> and <table>) and provides functionality global to the document (such as obtaining the page's URL and creating new elements in the document)."
+  },
+  "XMLDocument": {
+    "!type": "fn()",
+    "prototype": "Document.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/Parsing_and_serializing_XML",
+    "!doc": "The Web platform provides the following objects for parsing and serializing XML:"
+  },
+  "Attr": {
+    "!type": "fn()",
+    "prototype": {
+      "isId": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Attr",
+        "!doc": "This type represents a DOM element's attribute as an object. In most DOM methods, you will probably directly retrieve the attribute as a string (e.g., Element.getAttribute(), but certain functions (e.g., Element.getAttributeNode()) or means of iterating give Attr types."
+      },
+      "name": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Attr",
+        "!doc": "This type represents a DOM element's attribute as an object. In most DOM methods, you will probably directly retrieve the attribute as a string (e.g., Element.getAttribute(), but certain functions (e.g., Element.getAttributeNode()) or means of iterating give Attr types."
+      },
+      "value": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Attr",
+        "!doc": "This type represents a DOM element's attribute as an object. In most DOM methods, you will probably directly retrieve the attribute as a string (e.g., Element.getAttribute(), but certain functions (e.g., Element.getAttributeNode()) or means of iterating give Attr types."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Attr",
+    "!doc": "This type represents a DOM element's attribute as an object. In most DOM methods, you will probably directly retrieve the attribute as a string (e.g., Element.getAttribute(), but certain functions (e.g., Element.getAttributeNode()) or means of iterating give Attr types."
+  },
+  "NodeList": {
+    "!type": "fn()",
+    "prototype": {
+      "length": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.length",
+        "!doc": "Returns the number of items in a NodeList."
+      },
+      "item": {
+        "!type": "fn(i: number) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NodeList.item",
+        "!doc": "Returns a node from a NodeList by index."
+      },
+      "<i>": "+Element"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/NodeList",
+    "!doc": "NodeList objects are collections of nodes returned by getElementsByTagName, getElementsByTagNameNS, Node.childNodes, querySelectorAll, getElementsByClassName, etc."
+  },
+  "HTMLCollection": {
+    "!type": "fn()",
+    "prototype": {
+      "length": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/HTMLCollection",
+        "!doc": "The number of items in the collection."
+      },
+      "item": {
+        "!type": "fn(i: number) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/HTMLCollection",
+        "!doc": "Returns the specific node at the given zero-based index into the list. Returns null if the index is out of range."
+      },
+      "namedItem": {
+        "!type": "fn(name: string) -> +Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/HTMLCollection",
+        "!doc": "Returns the specific node whose ID or, as a fallback, name matches the string specified by name. Matching by name is only done as a last resort, only in HTML, and only if the referenced element supports the name attribute. Returns null if no node exists by the given name."
+      },
+      "<i>": "+Element"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/HTMLCollection",
+    "!doc": "HTMLCollection is an interface representing a generic collection of elements (in document order) and offers methods and properties for traversing the list."
+  },
+  "NamedNodeMap": {
+    "!type": "fn()",
+    "prototype": {
+      "length": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "The number of items in the map."
+      },
+      "getNamedItem": {
+        "!type": "fn(name: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "Gets a node by name."
+      },
+      "setNamedItem": {
+        "!type": "fn(node: +Node) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "Adds (or replaces) a node by its nodeName."
+      },
+      "removeNamedItem": {
+        "!type": "fn(name: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "Removes a node (or if an attribute, may reveal a default if present)."
+      },
+      "item": {
+        "!type": "fn(i: number) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "Returns the item at the given index (or null if the index is higher or equal to the number of nodes)."
+      },
+      "getNamedItemNS": {
+        "!type": "fn(ns: string, name: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "Gets a node by namespace and localName."
+      },
+      "setNamedItemNS": {
+        "!type": "fn(node: +Node) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "Adds (or replaces) a node by its localName and namespaceURI."
+      },
+      "removeNamedItemNS": {
+        "!type": "fn(ns: string, name: string) -> +Node",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+        "!doc": "Removes a node (or if an attribute, may reveal a default if present)."
+      },
+      "<i>": "+Node"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/NamedNodeMap",
+    "!doc": "A collection of nodes returned by Element.attributes (also potentially for DocumentType.entities, DocumentType.notations). NamedNodeMaps are not in any particular order (unlike NodeList), although they may be accessed by an index as in an array (they may also be accessed with the item() method). A NamedNodeMap object are live and will thus be auto-updated if changes are made to their contents internally or elsewhere."
+  },
+  "DocumentFragment": {
+    "!type": "fn()",
+    "prototype": {
+      "!proto": "Node.prototype"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/document.createDocumentFragment",
+    "!doc": "Creates a new empty DocumentFragment."
+  },
+  "DOMTokenList": {
+    "!type": "fn()",
+    "prototype": {
+      "length": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/DOMTokenList",
+        "!doc": "The amount of items in the list."
+      },
+      "item": {
+        "!type": "fn(i: number) -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/DOMTokenList",
+        "!doc": "Returns an item in the list by its index."
+      },
+      "contains": {
+        "!type": "fn(token: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/DOMTokenList",
+        "!doc": "Return true if the underlying string contains token, otherwise false."
+      },
+      "add": {
+        "!type": "fn(token: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/DOMTokenList",
+        "!doc": "Adds token to the underlying string."
+      },
+      "remove": {
+        "!type": "fn(token: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/DOMTokenList",
+        "!doc": "Remove token from the underlying string."
+      },
+      "toggle": {
+        "!type": "fn(token: string) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/DOMTokenList",
+        "!doc": "Removes token from string and returns false. If token doesn't exist it's added and the function returns true."
+      },
+      "<i>": "string"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/DOMTokenList",
+    "!doc": "This type represents a set of space-separated tokens. Commonly returned by HTMLElement.classList, HTMLLinkElement.relList, HTMLAnchorElement.relList or HTMLAreaElement.relList. It is indexed beginning with 0 as with JavaScript arrays. DOMTokenList is always case-sensitive."
+  },
+  "XPathResult": {
+    "!type": "fn()",
+    "prototype": {
+      "boolValue": "bool",
+      "invalidIteratorState": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/Introduction_to_using_XPath_in_JavaScript",
+        "!doc": "This document describes the interface for using XPath in JavaScript internally, in extensions, and from websites. Mozilla implements a fair amount of the DOM 3 XPath. Which means that XPath expressions can be run against both HTML and XML documents."
+      },
+      "numberValue": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/XPathResult",
+        "!doc": "Refer to nsIDOMXPathResult for more detail."
+      },
+      "resultType": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/document.evaluate",
+        "!doc": "Returns an XPathResult based on an XPath expression and other given parameters."
+      },
+      "singleNodeValue": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/Introduction_to_using_XPath_in_JavaScript",
+        "!doc": "This document describes the interface for using XPath in JavaScript internally, in extensions, and from websites. Mozilla implements a fair amount of the DOM 3 XPath. Which means that XPath expressions can be run against both HTML and XML documents."
+      },
+      "snapshotLength": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/XPathResult",
+        "!doc": "Refer to nsIDOMXPathResult for more detail."
+      },
+      "stringValue": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/Introduction_to_using_XPath_in_JavaScript",
+        "!doc": "This document describes the interface for using XPath in JavaScript internally, in extensions, and from websites. Mozilla implements a fair amount of the DOM 3 XPath. Which means that XPath expressions can be run against both HTML and XML documents."
+      },
+      "iterateNext": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/Introduction_to_using_XPath_in_JavaScript",
+        "!doc": "This document describes the interface for using XPath in JavaScript internally, in extensions, and from websites. Mozilla implements a fair amount of the DOM 3 XPath. Which means that XPath expressions can be run against both HTML and XML documents."
+      },
+      "snapshotItem": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en-US/docs/XPathResult#snapshotItem()"
+      },
+      "ANY_TYPE": "number",
+      "NUMBER_TYPE": "number",
+      "STRING_TYPE": "number",
+      "BOOL_TYPE": "number",
+      "UNORDERED_NODE_ITERATOR_TYPE": "number",
+      "ORDERED_NODE_ITERATOR_TYPE": "number",
+      "UNORDERED_NODE_SNAPSHOT_TYPE": "number",
+      "ORDERED_NODE_SNAPSHOT_TYPE": "number",
+      "ANY_UNORDERED_NODE_TYPE": "number",
+      "FIRST_ORDERED_NODE_TYPE": "number"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/XPathResult",
+    "!doc": "Refer to nsIDOMXPathResult for more detail."
+  },
+  "ClientRect": {
+    "!type": "fn()",
+    "prototype": {
+      "top": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getClientRects",
+        "!doc": "Top of the box, in pixels, relative to the viewport."
+      },
+      "left": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getClientRects",
+        "!doc": "Left of the box, in pixels, relative to the viewport."
+      },
+      "bottom": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getClientRects",
+        "!doc": "Bottom of the box, in pixels, relative to the viewport."
+      },
+      "right": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/element.getClientRects",
+        "!doc": "Right of the box, in pixels, relative to the viewport."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.getClientRects",
+    "!doc": "Returns a collection of rectangles that indicate the bounding rectangles for each box in a client."
+  },
+  "Event": {
+    "!type": "fn()",
+    "prototype": {
+      "stopPropagation": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.stopPropagation",
+        "!doc": "Prevents further propagation of the current event."
+      },
+      "preventDefault": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.preventDefault",
+        "!doc": "Cancels the event if it is cancelable, without stopping further propagation of the event."
+      },
+      "initEvent": {
+        "!type": "fn(type: string, bubbles: bool, cancelable: bool)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.initEvent",
+        "!doc": "The initEvent method is used to initialize the value of an event created using document.createEvent."
+      },
+      "stopImmediatePropagation": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.stopImmediatePropagation",
+        "!doc": "Prevents other listeners of the same event to be called."
+      },
+      "NONE": "number",
+      "CAPTURING_PHASE": "number",
+      "AT_TARGET": "number",
+      "BUBBLING_PHASE": "number",
+      "MOUSEDOWN": "number",
+      "MOUSEUP": "number",
+      "MOUSEOVER": "number",
+      "MOUSEOUT": "number",
+      "MOUSEMOVE": "number",
+      "MOUSEDRAG": "number",
+      "CLICK": "number",
+      "DBLCLICK": "number",
+      "KEYDOWN": "number",
+      "KEYUP": "number",
+      "KEYPRESS": "number",
+      "DRAGDROP": "number",
+      "FOCUS": "number",
+      "BLUR": "number",
+      "SELECT": "number",
+      "CHANGE": "number",
+      "target": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/EventTarget",
+        "!doc": "An EventTarget is a DOM interface implemented by objects that can receive DOM events and have listeners for them. The most common EventTargets are DOM elements, although other objects can be EventTargets too, for example document, window, XMLHttpRequest, and others."
+      },
+      "relatedTarget": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.relatedTarget",
+        "!doc": "Identifies a secondary target for the event."
+      },
+      "pageX": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.pageX",
+        "!doc": "Returns the horizontal coordinate of the event relative to whole document."
+      },
+      "pageY": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.pageY",
+        "!doc": "Returns the vertical coordinate of the event relative to the whole document."
+      },
+      "clientX": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.clientX",
+        "!doc": "Returns the horizontal coordinate within the application's client area at which the event occurred (as opposed to the coordinates within the page). For example, clicking in the top-left corner of the client area will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally."
+      },
+      "clientY": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.clientY",
+        "!doc": "Returns the vertical coordinate within the application's client area at which the event occurred (as opposed to the coordinates within the page). For example, clicking in the top-left corner of the client area will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically."
+      },
+      "keyCode": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.keyCode",
+        "!doc": "Returns the Unicode value of a non-character key in a keypress event or any key in any other type of keyboard event."
+      },
+      "charCode": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.charCode",
+        "!doc": "Returns the Unicode value of a character key pressed during a keypress event."
+      },
+      "which": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.which",
+        "!doc": "Returns the numeric keyCode of the key pressed, or the character code (charCode) for an alphanumeric key pressed."
+      },
+      "button": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.button",
+        "!doc": "Indicates which mouse button caused the event."
+      },
+      "shiftKey": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.shiftKey",
+        "!doc": "Indicates whether the SHIFT key was pressed when the event fired."
+      },
+      "ctrlKey": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.ctrlKey",
+        "!doc": "Indicates whether the CTRL key was pressed when the event fired."
+      },
+      "altKey": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.altKey",
+        "!doc": "Indicates whether the ALT key was pressed when the event fired."
+      },
+      "metaKey": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.metaKey",
+        "!doc": "Indicates whether the META key was pressed when the event fired."
+      },
+      "returnValue": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/window.onbeforeunload",
+        "!doc": "An event that fires when a window is about to unload its resources. The document is still visible and the event is still cancelable."
+      },
+      "cancelBubble": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/event.cancelBubble",
+        "!doc": "bool is the boolean value of true or false."
+      },
+      "dataTransfer": {
+        "dropEffect": {
+          "!type": "string",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/DataTransfer",
+          "!doc": "The actual effect that will be used, and should always be one of the possible values of effectAllowed."
+        },
+        "effectAllowed": {
+          "!type": "string",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/Drag_Operations",
+          "!doc": "Specifies the effects that are allowed for this drag."
+        },
+        "files": {
+          "!type": "+FileList",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/DataTransfer",
+          "!doc": "Contains a list of all the local files available on the data transfer."
+        },
+        "types": {
+          "!type": "[string]",
+          "!url": "https://developer.mozilla.org/en-US/docs/DragDrop/DataTransfer",
+          "!doc": "Holds a list of the format types of the data that is stored for the first item, in the same order the data was added. An empty list will be returned if no data was added."
+        },
+        "addElement": {
+          "!type": "fn(element: +Element)",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/DataTransfer",
+          "!doc": "Set the drag source."
+        },
+        "clearData": {
+          "!type": "fn(type?: string)",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/Drag_Operations",
+          "!doc": "Remove the data associated with a given type."
+        },
+        "getData": {
+          "!type": "fn(type: string) -> string",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/Drag_Operations",
+          "!doc": "Retrieves the data for a given type, or an empty string if data for that type does not exist or the data transfer contains no data."
+        },
+        "setData": {
+          "!type": "fn(type: string, data: string)",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/Drag_Operations",
+          "!doc": "Set the data for a given type."
+        },
+        "setDragImage": {
+          "!type": "fn(image: +Element)",
+          "!url": "https://developer.mozilla.org/en/docs/DragDrop/Drag_Operations",
+          "!doc": "Set the image to be used for dragging if a custom one is desired."
+        },
+        "!url": "https://developer.mozilla.org/en/docs/DragDrop/DataTransfer",
+        "!doc": "This object is available from the dataTransfer property of all drag events. It cannot be created separately."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/event",
+    "!doc": "The DOM Event interface is accessible from within the handler function, via the event object passed as the first argument."
+  },
+  "TouchEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Touch_events",
+    "!doc": "In order to provide quality support for touch-based user interfaces, touch events offer the ability to interpret finger activity on touch screens or trackpads."
+  },
+  "WheelEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/WheelEvent",
+    "!doc": "The DOM WheelEvent represents events that occur due to the user moving a mouse wheel or similar input device."
+  },
+  "MouseEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/MouseEvent",
+    "!doc": "The DOM MouseEvent represents events that occur due to the user interacting with a pointing device (such as a mouse). It's represented by the nsINSDOMMouseEvent interface, which extends the nsIDOMMouseEvent interface."
+  },
+  "KeyboardEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/KeyboardEvent",
+    "!doc": "KeyboardEvent objects describe a user interaction with the keyboard. Each event describes a key; the event type (keydown, keypress, or keyup) identifies what kind of activity was performed."
+  },
+  "HashChangeEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onhashchange",
+    "!doc": "The hashchange event fires when a window's hash changes."
+  },
+  "ErrorEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/DOM_event_reference/error",
+    "!doc": "The error event is fired whenever a resource fails to load."
+  },
+  "CustomEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Event/CustomEvent",
+    "!doc": "The DOM CustomEvent are events initialized by an application for any purpose."
+  },
+  "BeforeLoadEvent": {
+    "!type": "fn()",
+    "prototype": "Event.prototype",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window",
+    "!doc": "This section provides a brief reference for all of the methods, properties, and events available through the DOM window object. The window object implements the Window interface, which in turn inherits from the AbstractView interface. Some additional global functions, namespaces objects, and constructors, not typically associated with the window, but available on it, are listed in the JavaScript Reference."
+  },
+  "WebSocket": {
+    "!type": "fn(url: string)",
+    "prototype": {
+      "close": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/WebSockets_reference/CloseEvent",
+        "!doc": "A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute."
+      },
+      "send": {
+        "!type": "fn(data: string)",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/WebSockets_reference/WebSocket",
+        "!doc": "The WebSocket object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection."
+      },
+      "binaryType": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/WebSockets_reference/WebSocket",
+        "!doc": "The WebSocket object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection."
+      },
+      "bufferedAmount": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/Writing_WebSocket_client_applications",
+        "!doc": "WebSockets is a technology that makes it possible to open an interactive communication session between the user's browser and a server. Using a WebSocket connection, Web applications can perform real-time communication instead of having to poll for changes back and forth."
+      },
+      "extensions": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/WebSockets_reference/WebSocket",
+        "!doc": "The WebSocket object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection."
+      },
+      "onclose": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/WebSockets_reference/CloseEvent",
+        "!doc": "A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute."
+      },
+      "onerror": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/Writing_WebSocket_client_applications",
+        "!doc": "WebSockets is a technology that makes it possible to open an interactive communication session between the user's browser and a server. Using a WebSocket connection, Web applications can perform real-time communication instead of having to poll for changes back and forth."
+      },
+      "onmessage": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/WebSockets_reference/WebSocket",
+        "!doc": "The WebSocket object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection."
+      },
+      "onopen": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/WebSockets_reference/WebSocket",
+        "!doc": "The WebSocket object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection."
+      },
+      "protocol": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets",
+        "!doc": "WebSockets is an advanced technology that makes it possible to open an interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply."
+      },
+      "url": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/WebSockets/Writing_WebSocket_client_applications",
+        "!doc": "WebSockets is a technology that makes it possible to open an interactive communication session between the user's browser and a server. Using a WebSocket connection, Web applications can perform real-time communication instead of having to poll for changes back and forth."
+      },
+      "CONNECTING": "number",
+      "OPEN": "number",
+      "CLOSING": "number",
+      "CLOSED": "number"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/WebSockets",
+    "!doc": "WebSockets is an advanced technology that makes it possible to open an interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply."
+  },
+  "Worker": {
+    "!type": "fn(scriptURL: string)",
+    "prototype": {
+      "postMessage": {
+        "!type": "fn(message: ?)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Worker",
+        "!doc": "Sends a message to the worker's inner scope. This accepts a single parameter, which is the data to send to the worker. The data may be any value or JavaScript object handled by the structured clone algorithm, which includes cyclical references."
+      },
+      "terminate": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Worker",
+        "!doc": "Immediately terminates the worker. This does not offer the worker an opportunity to finish its operations; it is simply stopped at once."
+      },
+      "onmessage": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Worker",
+        "!doc": "An event listener that is called whenever a MessageEvent with type message bubbles through the worker. The message is stored in the event's data member."
+      },
+      "onerror": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Worker",
+        "!doc": "An event listener that is called whenever an ErrorEvent with type error bubbles through the worker."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Worker",
+    "!doc": "Workers are background tasks that can be easily created and can send messages back to their creators. Creating a worker is as simple as calling the Worker() constructor, specifying a script to be run in the worker thread."
+  },
+  "localStorage": {
+    "setItem": {
+      "!type": "fn(name: string, value: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Storage",
+      "!doc": "Store an item in storage."
+    },
+    "getItem": {
+      "!type": "fn(name: string) -> string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Storage",
+      "!doc": "Retrieve an item from storage."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Storage",
+    "!doc": "The DOM Storage mechanism is a means through which string key/value pairs can be securely stored and later retrieved for use."
+  },
+  "sessionStorage": {
+    "setItem": {
+      "!type": "fn(name: string, value: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Storage",
+      "!doc": "Store an item in storage."
+    },
+    "getItem": {
+      "!type": "fn(name: string) -> string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Storage",
+      "!doc": "Retrieve an item from storage."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Storage",
+    "!doc": "This is a global object (sessionStorage) that maintains a storage area that's available for the duration of the page session. A page session lasts for as long as the browser is open and survives over page reloads and restores. Opening a page in a new tab or window will cause a new session to be initiated."
+  },
+  "FileList": {
+    "!type": "fn()",
+    "prototype": {
+      "length": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileList",
+        "!doc": "A read-only value indicating the number of files in the list."
+      },
+      "item": {
+        "!type": "fn(i: number) -> +File",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileList",
+        "!doc": "Returns a File object representing the file at the specified index in the file list."
+      },
+      "<i>": "+File"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/FileList",
+    "!doc": "An object of this type is returned by the files property of the HTML input element; this lets you access the list of files selected with the <input type=\"file\"> element. It's also used for a list of files dropped into web content when using the drag and drop API."
+  },
+  "File": {
+    "!type": "fn()",
+    "prototype": {
+      "!proto": "Blob.prototype",
+      "fileName": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/File.fileName",
+        "!doc": "Returns the name of the file. For security reasons the path is excluded from this property."
+      },
+      "fileSize": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/File.fileSize",
+        "!doc": "Returns the size of a file in bytes."
+      },
+      "lastModifiedDate": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/File.lastModifiedDate",
+        "!doc": "Returns the last modified date of the file. Files without a known last modified date use the current date instead."
+      },
+      "name": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/File.name",
+        "!doc": "Returns the name of the file. For security reasons, the path is excluded from this property."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/File",
+    "!doc": "The File object provides information about -- and access to the contents of -- files. These are generally retrieved from a FileList object returned as a result of a user selecting files using the input element, or from a drag and drop operation's DataTransfer object."
+  },
+  "Blob": {
+    "!type": "fn(parts: [?], properties?: ?)",
+    "prototype": {
+      "size": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
+        "!doc": "The size, in bytes, of the data contained in the Blob object. Read only."
+      },
+      "type": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
+        "!doc": "An ASCII-encoded string, in all lower case, indicating the MIME type of the data contained in the Blob. If the type is unknown, this string is empty. Read only."
+      },
+      "slice": {
+        "!type": "fn(start: number, end?: number, type?: string) -> +Blob",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
+        "!doc": "Returns a new Blob object containing the data in the specified range of bytes of the source Blob."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Blob",
+    "!doc": "A Blob object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system."
+  },
+  "FileReader": {
+    "!type": "fn()",
+    "prototype": {
+      "abort": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Aborts the read operation. Upon return, the readyState will be DONE."
+      },
+      "readAsArrayBuffer": {
+        "!type": "fn(blob: +Blob)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Starts reading the contents of the specified Blob, producing an ArrayBuffer."
+      },
+      "readAsBinaryString": {
+        "!type": "fn(blob: +Blob)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Starts reading the contents of the specified Blob, producing raw binary data."
+      },
+      "readAsDataURL": {
+        "!type": "fn(blob: +Blob)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Starts reading the contents of the specified Blob, producing a data: url."
+      },
+      "readAsText": {
+        "!type": "fn(blob: +Blob, encoding?: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Starts reading the contents of the specified Blob, producing a string."
+      },
+      "EMPTY": "number",
+      "LOADING": "number",
+      "DONE": "number",
+      "error": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "The error that occurred while reading the file. Read only."
+      },
+      "readyState": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Indicates the state of the FileReader. This will be one of the State constants. Read only."
+      },
+      "result": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "The file's contents. This property is only valid after the read operation is complete, and the format of the data depends on which of the methods was used to initiate the read operation. Read only."
+      },
+      "onabort": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Called when the read operation is aborted."
+      },
+      "onerror": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Called when an error occurs."
+      },
+      "onload": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Called when the read operation is successfully completed."
+      },
+      "onloadend": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Called when the read is completed, whether successful or not. This is called after either onload or onerror."
+      },
+      "onloadstart": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Called when reading the data is about to begin."
+      },
+      "onprogress": {
+        "!type": "?",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+        "!doc": "Called periodically while the data is being read."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/FileReader",
+    "!doc": "The FileReader object lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, using File or Blob objects to specify the file or data to read. File objects may be obtained from a FileList object returned as a result of a user selecting files using the <input> element, from a drag and drop operation's DataTransfer object, or from the mozGetAsFile() API on an HTMLCanvasElement."
+  },
+  "Range": {
+    "!type": "fn()",
+    "prototype": {
+      "collapsed": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.collapsed",
+        "!doc": "Returns a boolean indicating whether the range's start and end points are at the same position."
+      },
+      "commonAncestorContainer": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.commonAncestorContainer",
+        "!doc": "Returns the deepest Node that contains the  startContainer and  endContainer Nodes."
+      },
+      "endContainer": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.endContainer",
+        "!doc": "Returns the Node within which the Range ends."
+      },
+      "endOffset": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.endOffset",
+        "!doc": "Returns a number representing where in the  endContainer the Range ends."
+      },
+      "startContainer": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.startContainer",
+        "!doc": "Returns the Node within which the Range starts."
+      },
+      "startOffset": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.startOffset",
+        "!doc": "Returns a number representing where in the startContainer the Range starts."
+      },
+      "setStart": {
+        "!type": "fn(node: +Element, offset: number)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.setStart",
+        "!doc": "Sets the start position of a Range."
+      },
+      "setEnd": {
+        "!type": "fn(node: +Element, offset: number)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.setEnd",
+        "!doc": "Sets the end position of a Range."
+      },
+      "setStartBefore": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.setStartBefore",
+        "!doc": "Sets the start position of a Range relative to another Node."
+      },
+      "setStartAfter": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.setStartAfter",
+        "!doc": "Sets the start position of a Range relative to a Node."
+      },
+      "setEndBefore": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.setEndBefore",
+        "!doc": "Sets the end position of a Range relative to another Node."
+      },
+      "setEndAfter": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.setEndAfter",
+        "!doc": "Sets the end position of a Range relative to another Node."
+      },
+      "selectNode": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.selectNode",
+        "!doc": "Sets the Range to contain the Node and its contents."
+      },
+      "selectNodeContents": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.selectNodeContents",
+        "!doc": "Sets the Range to contain the contents of a Node."
+      },
+      "collapse": {
+        "!type": "fn(toStart: bool)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.collapse",
+        "!doc": "Collapses the Range to one of its boundary points."
+      },
+      "cloneContents": {
+        "!type": "fn() -> +DocumentFragment",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.cloneContents",
+        "!doc": "Returns a DocumentFragment copying the Nodes of a Range."
+      },
+      "deleteContents": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.deleteContents",
+        "!doc": "Removes the contents of a Range from the Document."
+      },
+      "extractContents": {
+        "!type": "fn() -> +DocumentFragment",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.extractContents",
+        "!doc": "Moves contents of a Range from the document tree into a DocumentFragment."
+      },
+      "insertNode": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.insertNode",
+        "!doc": "Insert a node at the start of a Range."
+      },
+      "surroundContents": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.surroundContents",
+        "!doc": "Moves content of a Range into a new node, placing the new node at the start of the specified range."
+      },
+      "compareBoundaryPoints": {
+        "!type": "fn(how: number, other: +Range) -> number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.compareBoundaryPoints",
+        "!doc": "Compares the boundary points of two Ranges."
+      },
+      "cloneRange": {
+        "!type": "fn() -> +Range",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.cloneRange",
+        "!doc": "Returns a Range object with boundary points identical to the cloned Range."
+      },
+      "detach": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/range.detach",
+        "!doc": "Releases a Range from use to improve performance. This lets the browser choose to release resources associated with this Range. Subsequent attempts to use the detached range will result in a DOMException being thrown with an error code of INVALID_STATE_ERR."
+      },
+      "END_TO_END": "number",
+      "END_TO_START": "number",
+      "START_TO_END": "number",
+      "START_TO_START": "number"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/range.detach",
+    "!doc": "Releases a Range from use to improve performance. This lets the browser choose to release resources associated with this Range. Subsequent attempts to use the detached range will result in a DOMException being thrown with an error code of INVALID_STATE_ERR."
+  },
+  "XMLHttpRequest": {
+    "!type": "fn()",
+    "prototype": {
+      "abort": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Aborts the request if it has already been sent."
+      },
+      "getAllResponseHeaders": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Returns all the response headers as a string, or null if no response has been received. Note: For multipart requests, this returns the headers from the current part of the request, not from the original channel."
+      },
+      "getResponseHeader": {
+        "!type": "fn(header: string) -> string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Returns the string containing the text of the specified header, or null if either the response has not yet been received or the header doesn't exist in the response."
+      },
+      "open": {
+        "!type": "fn(method: string, url: string, async?: bool, user?: string, password?: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Initializes a request."
+      },
+      "overrideMimeType": {
+        "!type": "fn(type: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Overrides the MIME type returned by the server."
+      },
+      "send": {
+        "!type": "fn(data?: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Sends the request. If the request is asynchronous (which is the default), this method returns as soon as the request is sent. If the request is synchronous, this method doesn't return until the response has arrived."
+      },
+      "setRequestHeader": {
+        "!type": "fn(header: string, value: string)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Sets the value of an HTTP request header.You must call setRequestHeader() after open(), but before send()."
+      },
+      "onreadystatechange": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "A JavaScript function object that is called whenever the readyState attribute changes."
+      },
+      "readyState": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "The state of the request. (0=unsent, 1=opened, 2=headers_received, 3=loading, 4=done)"
+      },
+      "response": {
+        "!type": "+Document",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "The response entity body according to responseType, as an ArrayBuffer, Blob, Document, JavaScript object (for \"json\"), or string. This is null if the request is not complete or was not successful."
+      },
+      "responseText": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "The response to the request as text, or null if the request was unsuccessful or has not yet been sent."
+      },
+      "responseType": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "Can be set to change the response type."
+      },
+      "responseXML": {
+        "!type": "+Document",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "The response to the request as a DOM Document object, or null if the request was unsuccessful, has not yet been sent, or cannot be parsed as XML or HTML."
+      },
+      "status": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "The status of the response to the request. This is the HTTP result code"
+      },
+      "statusText": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+        "!doc": "The response string returned by the HTTP server. Unlike status, this includes the entire text of the response message (\"200 OK\", for example)."
+      },
+      "timeout": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest/Synchronous_and_Asynchronous_Requests",
+        "!doc": "The number of milliseconds a request can take before automatically being terminated. A value of 0 (which is the default) means there is no timeout."
+      },
+      "UNSENT": "number",
+      "OPENED": "number",
+      "HEADERS_RECEIVED": "number",
+      "LOADING": "number",
+      "DONE": "number"
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/XMLHttpRequest",
+    "!doc": "XMLHttpRequest is a JavaScript object that was designed by Microsoft and adopted by Mozilla, Apple, and Google. It's now being standardized in the W3C. It provides an easy way to retrieve data at a URL. Despite its name, XMLHttpRequest can be used to retrieve any type of data, not just XML, and it supports protocols other than HTTP (including file and ftp)."
+  },
+  "DOMParser": {
+    "!type": "fn()",
+    "prototype": {
+      "parseFromString": {
+        "!type": "fn(data: string, mime: string) -> +Document",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/DOMParser",
+        "!doc": "DOMParser can parse XML or HTML source stored in a string into a DOM Document. DOMParser is specified in DOM Parsing and Serialization."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/DOMParser",
+    "!doc": "DOMParser can parse XML or HTML source stored in a string into a DOM Document. DOMParser is specified in DOM Parsing and Serialization."
+  },
+  "Selection": {
+    "!type": "fn()",
+    "prototype": {
+      "anchorNode": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/anchorNode",
+        "!doc": "Returns the node in which the selection begins."
+      },
+      "anchorOffset": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/anchorOffset",
+        "!doc": "Returns the number of characters that the selection's anchor is offset within the anchorNode."
+      },
+      "focusNode": {
+        "!type": "+Element",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/focusNode",
+        "!doc": "Returns the node in which the selection ends."
+      },
+      "focusOffset": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/focusOffset",
+        "!doc": "Returns the number of characters that the selection's focus is offset within the focusNode. "
+      },
+      "isCollapsed": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/isCollapsed",
+        "!doc": "Returns a boolean indicating whether the selection's start and end points are at the same position."
+      },
+      "rangeCount": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/rangeCount",
+        "!doc": "Returns the number of ranges in the selection."
+      },
+      "getRangeAt": {
+        "!type": "fn(i: number) -> +Range",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/getRangeAt",
+        "!doc": "Returns a range object representing one of the ranges currently selected."
+      },
+      "collapse": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/collapse",
+        "!doc": "Collapses the current selection to a single point. The document is not modified. If the content is focused and editable, the caret will blink there."
+      },
+      "extend": {
+        "!type": "fn(node: +Element, offset: number)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/extend",
+        "!doc": "Moves the focus of the selection to a specified point. The anchor of the selection does not move. The selection will be from the anchor to the new focus regardless of direction."
+      },
+      "collapseToStart": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/collapseToStart",
+        "!doc": "Collapses the selection to the start of the first range in the selection.  If the content of the selection is focused and editable, the caret will blink there."
+      },
+      "collapseToEnd": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/collapseToEnd",
+        "!doc": "Collapses the selection to the end of the last range in the selection.  If the content the selection is in is focused and editable, the caret will blink there."
+      },
+      "selectAllChildren": {
+        "!type": "fn(node: +Element)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/selectAllChildren",
+        "!doc": "Adds all the children of the specified node to the selection. Previous selection is lost."
+      },
+      "addRange": {
+        "!type": "fn(range: +Range)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/addRange",
+        "!doc": "Adds a Range to a Selection."
+      },
+      "removeRange": {
+        "!type": "fn(range: +Range)",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/removeRange",
+        "!doc": "Removes a range from the selection."
+      },
+      "removeAllRanges": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/removeAllRanges",
+        "!doc": "Removes all ranges from the selection, leaving the anchorNode and focusNode properties equal to null and leaving nothing selected. "
+      },
+      "deleteFromDocument": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/deleteFromDocument",
+        "!doc": "Deletes the actual text being represented by a selection object from the document's DOM."
+      },
+      "containsNode": {
+        "!type": "fn(node: +Element) -> bool",
+        "!url": "https://developer.mozilla.org/en/docs/DOM/Selection/containsNode",
+        "!doc": "Indicates if the node is part of the selection."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Selection",
+    "!doc": "Selection is the class of the object returned by window.getSelection() and other methods. It represents the text selection in the greater page, possibly spanning multiple elements, when the user drags over static text and other parts of the page. For information about text selection in an individual text editing element."
+  },
+  "console": {
+    "error": {
+      "!type": "fn(text: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/console.error",
+      "!doc": "Outputs an error message to the Web Console."
+    },
+    "info": {
+      "!type": "fn(text: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/console.info",
+      "!doc": "Outputs an informational message to the Web Console."
+    },
+    "log": {
+      "!type": "fn(text: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/console.log",
+      "!doc": "Outputs a message to the Web Console."
+    },
+    "warn": {
+      "!type": "fn(text: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/console.warn",
+      "!doc": "Outputs a warning message to the Web Console."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/console",
+    "!doc": "The console object provides access to the browser's debugging console. The specifics of how it works vary from browser to browser, but there is a de facto set of features that are typically provided."
+  },
+  "top": {
+    "!type": "<top>",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.top",
+    "!doc": "Returns a reference to the topmost window in the window hierarchy."
+  },
+  "parent": {
+    "!type": "<top>",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.parent",
+    "!doc": "A reference to the parent of the current window or subframe."
+  },
+  "window": {
+    "!type": "<top>",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window",
+    "!doc": "This section provides a brief reference for all of the methods, properties, and events available through the DOM window object. The window object implements the Window interface, which in turn inherits from the AbstractView interface. Some additional global functions, namespaces objects, and constructors, not typically associated with the window, but available on it, are listed in the JavaScript Reference."
+  },
+  "opener": {
+    "!type": "<top>",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.opener",
+    "!doc": "Returns a reference to the window that opened this current window."
+  },
+  "self": {
+    "!type": "<top>",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.self",
+    "!doc": "Returns an object reference to the window object. "
+  },
+  "devicePixelRatio": "number",
+  "name": {
+    "!type": "string",
+    "!url": "https://developer.mozilla.org/en/docs/JavaScript/Reference/Global_Objects/Function/name",
+    "!doc": "The name of the function."
+  },
+  "closed": {
+    "!type": "bool",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.closed",
+    "!doc": "This property indicates whether the referenced window is closed or not."
+  },
+  "pageYOffset": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollY",
+    "!doc": "Returns the number of pixels that the document has already been scrolled vertically."
+  },
+  "pageXOffset": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollX",
+    "!doc": "Returns the number of pixels that the document has already been scrolled vertically."
+  },
+  "scrollY": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollY",
+    "!doc": "Returns the number of pixels that the document has already been scrolled vertically."
+  },
+  "scrollX": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollX",
+    "!doc": "Returns the number of pixels that the document has already been scrolled vertically."
+  },
+  "screenTop": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.top",
+    "!doc": "Returns the distance in pixels from the top side of the current screen."
+  },
+  "screenLeft": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.left",
+    "!doc": "Returns the distance in pixels from the left side of the main screen to the left side of the current screen."
+  },
+  "screenY": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/event.screenY",
+    "!doc": "Returns the vertical coordinate of the event within the screen as a whole."
+  },
+  "screenX": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/event.screenX",
+    "!doc": "Returns the horizontal coordinate of the event within the screen as a whole."
+  },
+  "innerWidth": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.innerWidth",
+    "!doc": "Width (in pixels) of the browser window viewport including, if rendered, the vertical scrollbar."
+  },
+  "innerHeight": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.innerHeight",
+    "!doc": "Height (in pixels) of the browser window viewport including, if rendered, the horizontal scrollbar."
+  },
+  "outerWidth": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.outerWidth",
+    "!doc": "window.outerWidth gets the width of the outside of the browser window. It represents the width of the whole browser window including sidebar (if expanded), window chrome and window resizing borders/handles."
+  },
+  "outerHeight": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.outerHeight",
+    "!doc": "window.outerHeight gets the height in pixels of the whole browser window."
+  },
+  "frameElement": {
+    "!type": "+Element",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.frameElement",
+    "!doc": "Returns the element (such as <iframe> or <object>) in which the window is embedded, or null if the window is top-level."
+  },
+  "crypto": {
+    "getRandomValues": {
+      "!type": "fn([number])",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues",
+      "!doc": "This methods lets you get cryptographically random values."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.crypto.getRandomValues",
+    "!doc": "This methods lets you get cryptographically random values."
+  },
+  "navigator": {
+    "appName": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.appName",
+      "!doc": "Returns the name of the browser. The HTML5 specification also allows any browser to return \"Netscape\" here, for compatibility reasons."
+    },
+    "appVersion": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.appVersion",
+      "!doc": "Returns the version of the browser as a string. It may be either a plain version number, like \"5.0\", or a version number followed by more detailed information. The HTML5 specification also allows any browser to return \"4.0\" here, for compatibility reasons."
+    },
+    "language": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.language",
+      "!doc": "Returns a string representing the language version of the browser."
+    },
+    "platform": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.platform",
+      "!doc": "Returns a string representing the platform of the browser."
+    },
+    "plugins": {
+      "!type": "[?]",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.plugins",
+      "!doc": "Returns a PluginArray object, listing the plugins installed in the application."
+    },
+    "userAgent": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.userAgent",
+      "!doc": "Returns the user agent string for the current browser."
+    },
+    "vendor": {
+      "!type": "string",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.vendor",
+      "!doc": "Returns the name of the browser vendor for the current browser."
+    },
+    "javaEnabled": {
+      "!type": "bool",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator.javaEnabled",
+      "!doc": "This method indicates whether the current browser is Java-enabled or not."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.navigator",
+    "!doc": "Returns a reference to the navigator object, which can be queried for information about the application running the script."
+  },
+  "history": {
+    "state": {
+      "!type": "?",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Manipulating_the_browser_history",
+      "!doc": "The DOM window object provides access to the browser's history through the history object. It exposes useful methods and properties that let you move back and forth through the user's history, as well as -- starting with HTML5 -- manipulate the contents of the history stack."
+    },
+    "length": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Manipulating_the_browser_history",
+      "!doc": "The DOM window object provides access to the browser's history through the history object. It exposes useful methods and properties that let you move back and forth through the user's history, as well as -- starting with HTML5 -- manipulate the contents of the history stack."
+    },
+    "go": {
+      "!type": "fn(delta: number)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.history",
+      "!doc": "Returns a reference to the History object, which provides an interface for manipulating the browser session history (pages visited in the tab or frame that the current page is loaded in)."
+    },
+    "forward": {
+      "!type": "fn()",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Manipulating_the_browser_history",
+      "!doc": "The DOM window object provides access to the browser's history through the history object. It exposes useful methods and properties that let you move back and forth through the user's history, as well as -- starting with HTML5 -- manipulate the contents of the history stack."
+    },
+    "back": {
+      "!type": "fn()",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Manipulating_the_browser_history",
+      "!doc": "The DOM window object provides access to the browser's history through the history object. It exposes useful methods and properties that let you move back and forth through the user's history, as well as -- starting with HTML5 -- manipulate the contents of the history stack."
+    },
+    "pushState": {
+      "!type": "fn(data: ?, title: string, url?: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Manipulating_the_browser_history",
+      "!doc": "The DOM window object provides access to the browser's history through the history object. It exposes useful methods and properties that let you move back and forth through the user's history, as well as -- starting with HTML5 -- manipulate the contents of the history stack."
+    },
+    "replaceState": {
+      "!type": "fn(data: ?, title: string, url?: string)",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/Manipulating_the_browser_history",
+      "!doc": "The DOM window object provides access to the browser's history through the history object. It exposes useful methods and properties that let you move back and forth through the user's history, as well as -- starting with HTML5 -- manipulate the contents of the history stack."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Manipulating_the_browser_history",
+    "!doc": "The DOM window object provides access to the browser's history through the history object. It exposes useful methods and properties that let you move back and forth through the user's history, as well as -- starting with HTML5 -- manipulate the contents of the history stack."
+  },
+  "screen": {
+    "availWidth": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.availWidth",
+      "!doc": "Returns the amount of horizontal space in pixels available to the window."
+    },
+    "availHeight": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.availHeight",
+      "!doc": "Returns the amount of vertical space available to the window on the screen."
+    },
+    "availTop": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.availTop",
+      "!doc": "Specifies the y-coordinate of the first pixel that is not allocated to permanent or semipermanent user interface features."
+    },
+    "availLeft": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.availLeft",
+      "!doc": "Returns the first available pixel available from the left side of the screen."
+    },
+    "pixelDepth": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.pixelDepth",
+      "!doc": "Returns the bit depth of the screen."
+    },
+    "colorDepth": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.colorDepth",
+      "!doc": "Returns the color depth of the screen."
+    },
+    "width": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.width",
+      "!doc": "Returns the width of the screen."
+    },
+    "height": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen.height",
+      "!doc": "Returns the height of the screen in pixels."
+    },
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.screen",
+    "!doc": "Returns a reference to the screen object associated with the window."
+  },
+  "postMessage": {
+    "!type": "fn(message: string, targetOrigin: string)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.postMessage",
+    "!doc": "window.postMessage, when called, causes a MessageEvent to be dispatched at the target window when any pending script that must be executed completes (e.g. remaining event handlers if window.postMessage is called from an event handler, previously-set pending timeouts, etc.). The MessageEvent has the type message, a data property which is set to the value of the first argument provided to window.postMessage, an origin property corresponding to the origin of the main document in the window calling window.postMessage at the time window.postMessage was called, and a source property which is the window from which window.postMessage is called. (Other standard properties of events are present with their expected values.)"
+  },
+  "close": {
+    "!type": "fn()",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.close",
+    "!doc": "Closes the current window, or a referenced window."
+  },
+  "blur": {
+    "!type": "fn()",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.blur",
+    "!doc": "The blur method removes keyboard focus from the current element."
+  },
+  "focus": {
+    "!type": "fn()",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.focus",
+    "!doc": "Sets focus on the specified element, if it can be focused."
+  },
+  "onload": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onload",
+    "!doc": "An event handler for the load event of a window."
+  },
+  "onunload": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onunload",
+    "!doc": "The unload event is raised when the window is unloading its content and resources. The resources removal is processed after the unload event occurs."
+  },
+  "onscroll": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onscroll",
+    "!doc": "Specifies the function to be called when the window is scrolled."
+  },
+  "onresize": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onresize",
+    "!doc": "An event handler for the resize event on the window."
+  },
+  "ononline": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/document.ononline",
+    "!doc": ",fgh s dgkljgsdfl dfjg sdlgj sdlg sdlfj dlg jkdfkj dfjgdfkglsdfjsdlfkgj hdflkg hdlkfjgh dfkjgh"
+  },
+  "onoffline": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/Online_and_offline_events",
+    "!doc": "Some browsers implement Online/Offline events from the WHATWG Web Applications 1.0 specification."
+  },
+  "onmousewheel": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/DOM_event_reference/mousewheel",
+    "!doc": "The DOM mousewheel event is fired asynchronously when mouse wheel or similar device is operated. It's represented by the MouseWheelEvent interface."
+  },
+  "onmouseup": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onmouseup",
+    "!doc": "An event handler for the mouseup event on the window."
+  },
+  "onmouseover": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmouseover",
+    "!doc": "The onmouseover property returns the onMouseOver event handler code on the current element."
+  },
+  "onmouseout": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmouseout",
+    "!doc": "The onmouseout property returns the onMouseOut event handler code on the current element."
+  },
+  "onmousemove": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onmousemove",
+    "!doc": "The onmousemove property returns the mousemove event handler code on the current element."
+  },
+  "onmousedown": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onmousedown",
+    "!doc": "An event handler for the mousedown event on the window."
+  },
+  "onclick": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onclick",
+    "!doc": "The onclick property returns the onClick event handler code on the current element."
+  },
+  "ondblclick": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.ondblclick",
+    "!doc": "The ondblclick property returns the onDblClick event handler code on the current element."
+  },
+  "onmessage": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/Worker",
+    "!doc": "Dedicated Web Workers provide a simple means for web content to run scripts in background threads.  Once created, a worker can send messages to the spawning task by posting messages to an event handler specified by the creator."
+  },
+  "onkeyup": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onkeyup",
+    "!doc": "The onkeyup property returns the onKeyUp event handler code for the current element."
+  },
+  "onkeypress": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onkeypress",
+    "!doc": "The onkeypress property sets and returns the onKeyPress event handler code for the current element."
+  },
+  "onkeydown": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onkeydown",
+    "!doc": "An event handler for the keydown event on the window."
+  },
+  "oninput": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/DOM_event_reference/input",
+    "!doc": "The DOM input event is fired synchronously when the value of an <input> or <textarea> element is changed. Additionally, it's also fired on contenteditable editors when its contents are changed. In this case, the event target is the editing host element. If there are two or more elements which have contenteditable as true, \"editing host\" is the nearest ancestor element whose parent isn't editable. Similarly, it's also fired on root element of designMode editors."
+  },
+  "onpopstate": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onpopstate",
+    "!doc": "An event handler for the popstate event on the window."
+  },
+  "onhashchange": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onhashchange",
+    "!doc": "The hashchange event fires when a window's hash changes."
+  },
+  "onfocus": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onfocus",
+    "!doc": "The onfocus property returns the onFocus event handler code on the current element."
+  },
+  "onblur": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onblur",
+    "!doc": "The onblur property returns the onBlur event handler code, if any, that exists on the current element."
+  },
+  "onerror": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onerror",
+    "!doc": "An event handler for runtime script errors."
+  },
+  "ondrop": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/drop",
+    "!doc": "The drop event is fired when an element or text selection is dropped on a valid drop target."
+  },
+  "ondragstart": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/dragstart",
+    "!doc": "The dragstart event is fired when the user starts dragging an element or text selection."
+  },
+  "ondragover": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/dragover",
+    "!doc": "The dragover event is fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds)."
+  },
+  "ondragleave": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/dragleave",
+    "!doc": "The dragleave event is fired when a dragged element or text selection leaves a valid drop target."
+  },
+  "ondragenter": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/dragenter",
+    "!doc": "The dragenter event is fired when a dragged element or text selection enters a valid drop target."
+  },
+  "ondragend": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/dragend",
+    "!doc": "The dragend event is fired when a drag operation is being ended (by releasing a mouse button or hitting the escape key)."
+  },
+  "ondrag": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/drag",
+    "!doc": "The drag event is fired when an element or text selection is being dragged (every few hundred milliseconds)."
+  },
+  "oncontextmenu": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.oncontextmenu",
+    "!doc": "An event handler property for right-click events on the window. Unless the default behavior is prevented, the browser context menu will activate (though IE8 has a bug with this and will not activate the context menu if a contextmenu event handler is defined). Note that this event will occur with any non-disabled right-click event and does not depend on an element possessing the \"contextmenu\" attribute."
+  },
+  "onchange": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/element.onchange",
+    "!doc": "The onchange property sets and returns the onChange event handler code for the current element."
+  },
+  "onbeforeunload": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onbeforeunload",
+    "!doc": "An event that fires when a window is about to unload its resources. The document is still visible and the event is still cancelable."
+  },
+  "onabort": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.onabort",
+    "!doc": "An event handler for abort events sent to the window."
+  },
+  "getSelection": {
+    "!type": "fn() -> +Selection",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.getSelection",
+    "!doc": "Returns a selection object representing the range of text selected by the user. "
+  },
+  "alert": {
+    "!type": "fn(message: string)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.alert",
+    "!doc": "Display an alert dialog with the specified content and an OK button."
+  },
+  "confirm": {
+    "!type": "fn(message: string) -> bool",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.confirm",
+    "!doc": "Displays a modal dialog with a message and two buttons, OK and Cancel."
+  },
+  "prompt": {
+    "!type": "fn(message: string, value: string) -> string",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.prompt",
+    "!doc": "Displays a dialog with a message prompting the user to input some text."
+  },
+  "scrollBy": {
+    "!type": "fn(x: number, y: number)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollBy",
+    "!doc": "Scrolls the document in the window by the given amount."
+  },
+  "scrollTo": {
+    "!type": "fn(x: number, y: number)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.scrollTo",
+    "!doc": "Scrolls to a particular set of coordinates in the document."
+  },
+  "scroll": {
+    "!type": "fn(x: number, y: number)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.scroll",
+    "!doc": "Scrolls the window to a particular place in the document."
+  },
+  "setTimeout": {
+    "!type": "fn(f: fn(), ms: number) -> number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.setTimeout",
+    "!doc": "Calls a function or executes a code snippet after specified delay."
+  },
+  "clearTimeout": {
+    "!type": "fn(timeout: number)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.clearTimeout",
+    "!doc": "Clears the delay set by window.setTimeout()."
+  },
+  "setInterval": {
+    "!type": "fn(f: fn(), ms: number) -> number",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.setInterval",
+    "!doc": "Calls a function or executes a code snippet repeatedly, with a fixed time delay between each call to that function."
+  },
+  "clearInterval": {
+    "!type": "fn(interval: number)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.clearInterval",
+    "!doc": "Cancels repeated action which was set up using setInterval."
+  },
+  "atob": {
+    "!type": "fn(encoded: string) -> string",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.atob",
+    "!doc": "Decodes a string of data which has been encoded using base-64 encoding."
+  },
+  "btoa": {
+    "!type": "fn(data: string) -> string",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.btoa",
+    "!doc": "Creates a base-64 encoded ASCII string from a string of binary data."
+  },
+  "addEventListener": {
+    "!type": "fn(type: string, listener: fn(e: +Event), capture: bool)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/EventTarget.addEventListener",
+    "!doc": "Registers a single event listener on a single target. The event target may be a single element in a document, the document itself, a window, or an XMLHttpRequest."
+  },
+  "removeEventListener": {
+    "!type": "fn(type: string, listener: fn(), capture: bool)",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/EventTarget.removeEventListener",
+    "!doc": "Allows the removal of event listeners from the event target."
+  },
+  "dispatchEvent": {
+    "!type": "fn(event: +Event) -> bool",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/EventTarget.dispatchEvent",
+    "!doc": "Dispatches an event into the event system. The event is subject to the same capturing and bubbling behavior as directly dispatched events."
+  },
+  "getComputedStyle": {
+    "!type": "fn(node: +Element, pseudo?: string) -> Element.prototype.style",
+    "!url": "https://developer.mozilla.org/en/docs/DOM/window.getComputedStyle",
+    "!doc": "Gives the final used values of all the CSS properties of an element."
+  },
+  "CanvasRenderingContext2D": {
+    "canvas": "+Element",
+    "width": "number",
+    "height": "number",
+    "commit": "fn()",
+    "save": "fn()",
+    "restore": "fn()",
+    "currentTransform": "?",
+    "scale": "fn(x: number, y: number)",
+    "rotate": "fn(angle: number)",
+    "translate": "fn(x: number, y: number)",
+    "transform": "fn(a: number, b: number, c: number, d: number, e: number, f: number)",
+    "setTransform": "fn(a: number, b: number, c: number, d: number, e: number, f: number)",
+    "resetTransform": "fn()",
+    "globalAlpha": "number",
+    "globalCompositeOperation": "string",
+    "imageSmoothingEnabled": "bool",
+    "strokeStyle": "string",
+    "fillStyle": "string",
+    "createLinearGradient": "fn(x0: number, y0: number, x1: number, y1: number) -> ?",
+    "createPattern": "fn(image: ?, repetition: string) -> ?",
+    "shadowOffsetX": "number",
+    "shadowOffsetY": "number",
+    "shadowBlur": "number",
+    "shadowColor": "string",
+    "clearRect": "fn(x: number, y: number, w: number, h: number)",
+    "fillRect": "fn(x: number, y: number, w: number, h: number)",
+    "strokeRect": "fn(x: number, y: number, w: number, h: number)",
+    "fillRule": "string",
+    "fill": "fn()",
+    "beginPath": "fn()",
+    "stroke": "fn()",
+    "clip": "fn()",
+    "resetClip": "fn()",
+    "measureText": "fn(text: string) -> ?",
+    "drawImage": "fn(image: ?, dx: number, dy: number)",
+    "createImageData": "fn(sw: number, sh: number) -> ?",
+    "getImageData": "fn(sx: number, sy: number, sw: number, sh: number) -> ?",
+    "putImageData": "fn(imagedata: ?, dx: number, dy: number)",
+    "lineWidth": "number",
+    "lineCap": "string",
+    "lineJoin": "string",
+    "miterLimit": "number",
+    "setLineDash": "fn(segments: [number])",
+    "getLineDash": "fn() -> [number]",
+    "lineDashOffset": "number",
+    "font": "string",
+    "textAlign": "string",
+    "textBaseline": "string",
+    "direction": "string",
+    "closePath": "fn()",
+    "moveTo": "fn(x: number, y: number)",
+    "lineTo": "fn(x: number, y: number)",
+    "quadraticCurveTo": "fn(cpx: number, cpy: number, x: number, y: number)",
+    "bezierCurveTo": "fn(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number)",
+    "arcTo": "fn(x1: number, y1: number, x2: number, y2: number, radius: number)",
+    "rect": "fn(x: number, y: number, w: number, h: number)",
+    "arc": "fn(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: bool)",
+    "ellipse": "fn(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise: bool)"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/comment.js
@@ -0,0 +1,87 @@
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports);
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports"], mod);
+  mod(tern.comment || (tern.comment = {}));
+})(function(exports) {
+  function isSpace(ch) {
+    return (ch < 14 && ch > 8) || ch === 32 || ch === 160;
+  }
+
+  function onOwnLine(text, pos) {
+    for (; pos > 0; --pos) {
+      var ch = text.charCodeAt(pos - 1);
+      if (ch == 10) break;
+      if (!isSpace(ch)) return false;
+    }
+    return true;
+  }
+
+  // Gather comments directly before a function
+  exports.commentsBefore = function(text, pos) {
+    var found = null, emptyLines = 0, topIsLineComment;
+    out: while (pos > 0) {
+      var prev = text.charCodeAt(pos - 1);
+      if (prev == 10) {
+        for (var scan = --pos, sawNonWS = false; scan > 0; --scan) {
+          prev = text.charCodeAt(scan - 1);
+          if (prev == 47 && text.charCodeAt(scan - 2) == 47) {
+            if (!onOwnLine(text, scan - 2)) break out;
+            var content = text.slice(scan, pos);
+            if (!emptyLines && topIsLineComment) found[0] = content + "\n" + found[0];
+            else (found || (found = [])).unshift(content);
+            topIsLineComment = true;
+            emptyLines = 0;
+            pos = scan - 2;
+            break;
+          } else if (prev == 10) {
+            if (!sawNonWS && ++emptyLines > 1) break out;
+            break;
+          } else if (!sawNonWS && !isSpace(prev)) {
+            sawNonWS = true;
+          }
+        }
+      } else if (prev == 47 && text.charCodeAt(pos - 2) == 42) {
+        for (var scan = pos - 2; scan > 1; --scan) {
+          if (text.charCodeAt(scan - 1) == 42 && text.charCodeAt(scan - 2) == 47) {
+            if (!onOwnLine(text, scan - 2)) break out;
+            (found || (found = [])).unshift(text.slice(scan, pos - 2));
+            topIsLineComment = false;
+            emptyLines = 0;
+            break;
+          }
+        }
+        pos = scan - 2;
+      } else if (isSpace(prev)) {
+        --pos;
+      } else {
+        break;
+      }
+    }
+    return found;
+  };
+
+  exports.commentAfter = function(text, pos) {
+    while (pos < text.length) {
+      var next = text.charCodeAt(pos);
+      if (next == 47) {
+        var after = text.charCodeAt(pos + 1), end;
+        if (after == 47) // line comment
+          end = text.indexOf("\n", pos + 2);
+        else if (after == 42) // block comment
+          end = text.indexOf("*/", pos + 2);
+        else
+          return;
+        return text.slice(pos + 2, end < 0 ? text.length : end);
+      } else if (isSpace(next)) {
+        ++pos;
+      }
+    }
+  };
+
+  exports.ensureCommentsBefore = function(text, node) {
+    if (node.hasOwnProperty("commentsBefore")) return node.commentsBefore;
+    return node.commentsBefore = exports.commentsBefore(text, node.start);
+  };
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/condense.js
@@ -0,0 +1,282 @@
+// Condensing an inferred set of types to a JSON description document.
+
+// This code can be used to, after a library has been analyzed,
+// extract the types defined in that library and dump them as a JSON
+// structure (as parsed by def.js).
+
+// The idea being that big libraries can be analyzed once, dumped, and
+// then cheaply included in later analysis.
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports, require("./infer"));
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports", "./infer"], mod);
+  mod(self.tern || (self.tern = {}), tern); // Plain browser env
+})(function(exports, infer) {
+  "use strict";
+
+  exports.condense = function(origins, name, options) {
+    if (typeof origins == "string") origins = [origins];
+    var state = new State(origins, name || origins[0], options || {});
+
+    runPass(state.passes.preCondenseReach, state);
+
+    state.cx.topScope.path = "<top>";
+    state.cx.topScope.reached("", state);
+    for (var path in state.roots)
+      reach(state.roots[path], null, path, state);
+    for (var i = 0; i < state.patchUp.length; ++i)
+      patchUpSimpleInstance(state.patchUp[i], state);
+
+    runPass(state.passes.postCondenseReach, state);
+
+    for (var path in state.types)
+      store(createPath(path.split("."), state), state.types[path], state);
+    for (var path in state.altPaths)
+      storeAlt(path, state.altPaths[path], state);
+    var hasDef = false;
+    for (var _def in state.output["!define"]) { hasDef = true; break; }
+    if (!hasDef) delete state.output["!define"];
+
+    runPass(state.passes.postCondense, state);
+
+    return simplify(state.output, state.options.sortOutput);
+  };
+
+  function State(origins, name, options) {
+    this.origins = origins;
+    this.cx = infer.cx();
+    this.passes = options.passes || this.cx.parent && this.cx.parent.passes || {};
+    this.maxOrigin = -Infinity;
+    for (var i = 0; i < origins.length; ++i)
+      this.maxOrigin = Math.max(this.maxOrigin, this.cx.origins.indexOf(origins[i]));
+    this.output = {"!name": name, "!define": {}};
+    this.options = options;
+    this.types = Object.create(null);
+    this.altPaths = Object.create(null);
+    this.patchUp = [];
+    this.roots = Object.create(null);
+  }
+
+  State.prototype.isTarget = function(origin) {
+    return this.origins.indexOf(origin) > -1;
+  };
+
+  State.prototype.getSpan = function(node) {
+    if (this.options.spans == false || !this.isTarget(node.origin)) return null;
+    if (node.span) return node.span;
+    var srv = this.cx.parent, file;
+    if (!srv || !node.originNode || !(file = srv.findFile(node.origin))) return null;
+    var start = node.originNode.start, end = node.originNode.end;
+    var pStart = file.asLineChar(start), pEnd = file.asLineChar(end);
+    return start + "[" + pStart.line + ":" + pStart.ch + "]-" +
+      end + "[" + pEnd.line + ":" + pEnd.ch + "]";
+  };
+
+  function pathLen(path) {
+    var len = 1, pos = 0, dot;
+    while ((dot = path.indexOf(".", pos)) != -1) {
+      pos = dot + 1;
+      len += path.charAt(pos) == "!" ? 10 : 1;
+    }
+    return len;
+  }
+
+  function isConcrete(path) {
+    return !/\!|<i>/.test(path);
+  }
+
+  function hop(obj, prop) {
+    return Object.prototype.hasOwnProperty.call(obj, prop);
+  }
+
+  function isSimpleInstance(o) {
+    return o.proto && !(o instanceof infer.Fn) && o.proto != infer.cx().protos.Object &&
+      o.proto.hasCtor && !o.hasCtor;
+  }
+
+  function reach(type, path, id, state) {
+    var actual = type.getType(false);
+    if (!actual) return;
+    var orig = type.origin || actual.origin, relevant = false;
+    if (orig) {
+      var origPos = state.cx.origins.indexOf(orig);
+      // This is a path that is newer than the code we are interested in.
+      if (origPos > state.maxOrigin) return;
+      relevant = state.isTarget(orig);
+    }
+    var newPath = path ? path + "." + id : id, oldPath = actual.path;
+    var shorter = !oldPath || pathLen(oldPath) > pathLen(newPath);
+    if (shorter) {
+      if (!(actual instanceof infer.Prim)) actual.path = newPath;
+      if (actual.reached(newPath, state, !relevant) && relevant) {
+        var data = state.types[oldPath];
+        if (data) {
+          delete state.types[oldPath];
+          state.altPaths[oldPath] = actual;
+        } else data = {type: actual};
+        data.span = state.getSpan(type) || (actual != type && state.isTarget(actual.origin) && state.getSpan(actual)) || data.span;
+        data.doc = type.doc || (actual != type && state.isTarget(actual.origin) && type.doc) || data.doc;
+        data.data = actual.metaData;
+        state.types[newPath] = data;
+      }
+    } else {
+      if (relevant) state.altPaths[newPath] = actual;
+    }
+  }
+  function reachTypeOnly(aval, path, id, state) {
+    var type = aval.getType();
+    if (type) reach(type, path, id, state);
+  }
+
+  infer.Prim.prototype.reached = function() {return true;};
+
+  infer.Arr.prototype.reached = function(path, state, concrete) {
+    if (!concrete) reachTypeOnly(this.getProp("<i>"), path, "<i>", state);
+    return true;
+  };
+
+  infer.Fn.prototype.reached = function(path, state, concrete) {
+    infer.Obj.prototype.reached.call(this, path, state, concrete);
+    if (!concrete) {
+      for (var i = 0; i < this.args.length; ++i)
+        reachTypeOnly(this.args[i], path, "!" + i, state);
+      reachTypeOnly(this.retval, path, "!ret", state);
+    }
+    return true;
+  };
+
+  infer.Obj.prototype.reached = function(path, state, concrete) {
+    if (isSimpleInstance(this) && !this.condenseForceInclude) {
+      if (state.patchUp.indexOf(this) == -1) state.patchUp.push(this);
+      return true;
+    } else if (this.proto && !concrete) {
+      reach(this.proto, path, "!proto", state);
+    }
+    var hasProps = false;
+    for (var prop in this.props) {
+      reach(this.props[prop], path, prop, state);
+      hasProps = true;
+    }
+    if (!hasProps && !this.condenseForceInclude && !(this instanceof infer.Fn)) {
+      this.nameOverride = "?";
+      return false;
+    }
+    return true;
+  };
+
+  function patchUpSimpleInstance(obj, state) {
+    var path = obj.proto.hasCtor.path;
+    if (path) {
+      obj.nameOverride = "+" + path;
+    } else {
+      path = obj.path;
+    }
+    for (var prop in obj.props)
+      reach(obj.props[prop], path, prop, state);
+  }
+
+  function createPath(parts, state) {
+    var base = state.output;
+    for (var i = parts.length - 1; i >= 0; --i) if (!isConcrete(parts[i])) {
+      var def = parts.slice(0, i + 1).join(".");
+      var defs = state.output["!define"];
+      if (hop(defs, def)) base = defs[def];
+      else defs[def] = base = {};
+      parts = parts.slice(i + 1);
+    }
+    for (var i = 0; i < parts.length; ++i) {
+      if (hop(base, parts[i])) base = base[parts[i]];
+      else base = base[parts[i]] = {};
+    }
+    return base;
+  }
+
+  function store(out, info, state) {
+    var name = typeName(info.type);
+    if (name != info.type.path && name != "?") {
+      out["!type"] = name;
+    } else if (info.type.proto && info.type.proto != state.cx.protos.Object) {
+      var protoName = typeName(info.type.proto);
+      if (protoName != "?") out["!proto"] = protoName;
+    }
+    if (info.span) out["!span"] = info.span;
+    if (info.doc) out["!doc"] = info.doc;
+    if (info.data) out["!data"] = info.data;
+  }
+
+  function storeAlt(path, type, state) {
+    var parts = path.split("."), last = parts.pop();
+    var base = createPath(parts, state);
+    if (!hop(base, last)) base[last] = type.nameOverride || type.path;
+  }
+
+  var typeNameStack = [];
+  function typeName(type) {
+    var actual = type.getType(false);
+    if (!actual || typeNameStack.indexOf(actual) > -1)
+      return actual && actual.path || "?";
+    typeNameStack.push(actual);
+    var name = actual.typeName();
+    typeNameStack.pop();
+    return name;
+  }
+
+  infer.Prim.prototype.typeName = function() { return this.name; };
+
+  infer.Arr.prototype.typeName = function() {
+    return "[" + typeName(this.getProp("<i>")) + "]";
+  };
+
+  infer.Fn.prototype.typeName = function() {
+    var out = "fn(";
+    for (var i = 0; i < this.args.length; ++i) {
+      if (i) out += ", ";
+      var name = this.argNames[i];
+      if (name && name != "?") out += name + ": ";
+      out += typeName(this.args[i]);
+    }
+    out += ")";
+    if (this.computeRetSource) {
+      out += " -> " + this.computeRetSource;
+    } else if (!this.retval.isEmpty()) {
+      var rettype = this.retval.getType(false);
+      if (rettype) out += " -> " + typeName(rettype);
+    }
+    return out;
+  };
+
+  infer.Obj.prototype.typeName = function() {
+    if (this.nameOverride) return this.nameOverride;
+    if (!this.path) return "?";
+    return this.path;
+  };
+
+  function simplify(data, sort) {
+    if (typeof data != "object") return data;
+    var sawType = false, sawOther = false;
+    for (var prop in data) {
+      if (prop == "!type") sawType = true;
+      else sawOther = true;
+      if (prop != "!data")
+        data[prop] = simplify(data[prop], sort);
+    }
+    if (sawType && !sawOther) return data["!type"];
+    return sort ? sortObject(data) : data;
+  }
+
+  function sortObject(obj) {
+    var props = [], out = {};
+    for (var prop in obj) props.push({name: prop, val: obj[prop]});
+    props.sort(function(a, b) { return a.name < b.name ? -1 : 1; });
+    for (var i = 0; i < props.length; ++i)
+      out[props[i].name] = props[i].val;
+    return out;
+  }
+
+  function runPass(functions) {
+    if (functions) for (var i = 0; i < functions.length; ++i)
+      functions[i].apply(null, Array.prototype.slice.call(arguments, 1));
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/def.js
@@ -0,0 +1,462 @@
+// Type description parser
+
+// Type description JSON files (such as ecma5.json and browser.json)
+// are used to
+//
+// A) describe types that come from native code
+
+// B) to cheaply load the types for big libraries, or libraries that
+//    can't be inferred well
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return exports.init = mod;
+  if (typeof define == "function" && define.amd) // AMD
+    return define({init: mod});
+  tern.def = {init: mod};
+})(function(exports, infer) {
+  "use strict";
+
+  function hop(obj, prop) {
+    return Object.prototype.hasOwnProperty.call(obj, prop);
+  }
+
+  var TypeParser = exports.TypeParser = function(spec, start, base, forceNew) {
+    this.pos = start || 0;
+    this.spec = spec;
+    this.base = base;
+    this.forceNew = forceNew;
+  };
+  TypeParser.prototype = {
+    eat: function(str) {
+      if (str.length == 1 ? this.spec.charAt(this.pos) == str : this.spec.indexOf(str, this.pos) == this.pos) {
+        this.pos += str.length;
+        return true;
+      }
+    },
+    word: function(re) {
+      var word = "", ch, re = re || /[\w$]/;
+      while ((ch = this.spec.charAt(this.pos)) && re.test(ch)) { word += ch; ++this.pos; }
+      return word;
+    },
+    error: function() {
+      throw new Error("Unrecognized type spec: " + this.spec + " (at " + this.pos + ")");
+    },
+    parseFnType: function(name, top) {
+      var args = [], names = [];
+      if (!this.eat(")")) for (var i = 0; ; ++i) {
+        var colon = this.spec.indexOf(": ", this.pos), argname;
+        if (colon != -1) {
+          argname = this.spec.slice(this.pos, colon);
+          if (/^[$\w?]+$/.test(argname))
+            this.pos = colon + 2;
+          else
+            argname = null;
+        }
+        names.push(argname);
+        args.push(this.parseType());
+        if (!this.eat(", ")) {
+          this.eat(")") || this.error();
+          break;
+        }
+      }
+      var retType, computeRet, computeRetStart, fn;
+      if (this.eat(" -> ")) {
+        if (top && this.spec.indexOf("!", this.pos) > -1) {
+          retType = infer.ANull;
+          computeRetStart = this.pos;
+          computeRet = this.parseRetType();
+        } else retType = this.parseType();
+      } else retType = infer.ANull;
+      if (top && (fn = this.base))
+        infer.Fn.call(this.base, name, infer.ANull, args, names, retType);
+      else
+        fn = new infer.Fn(name, infer.ANull, args, names, retType);
+      if (computeRet) fn.computeRet = computeRet;
+      if (computeRetStart != null) fn.computeRetSource = this.spec.slice(computeRetStart, this.pos);
+      return fn;
+    },
+    parseType: function(name, top) {
+      if (this.eat("fn(")) {
+        return this.parseFnType(name, top);
+      } else if (this.eat("[")) {
+        var inner = this.parseType();
+        this.eat("]") || this.error();
+        if (top && this.base) {
+          infer.Arr.call(this.base, inner);
+          return this.base;
+        }
+        return new infer.Arr(inner);
+      } else if (this.eat("+")) {
+        var path = this.word(/[\w$<>\.!]/);
+        var base = parsePath(path + ".prototype");
+        if (!(base instanceof infer.Obj)) base = parsePath(path);
+        if (!(base instanceof infer.Obj)) return base;
+        if (top && this.forceNew) return new infer.Obj(base);
+        return infer.getInstance(base);
+      } else if (this.eat("?")) {
+        return infer.ANull;
+      } else {
+        return this.fromWord(this.word(/[\w$<>\.!`]/));
+      }
+    },
+    fromWord: function(spec) {
+      var cx = infer.cx();
+      switch (spec) {
+      case "number": return cx.num;
+      case "string": return cx.str;
+      case "bool": return cx.bool;
+      case "<top>": return cx.topScope;
+      }
+      if (cx.localDefs && spec in cx.localDefs) return cx.localDefs[spec];
+      return parsePath(spec);
+    },
+    parseBaseRetType: function() {
+      if (this.eat("[")) {
+        var inner = this.parseRetType();
+        this.eat("]") || this.error();
+        return function(self, args) { return new infer.Arr(inner(self, args)); };
+      } else if (this.eat("+")) {
+        var base = this.parseRetType();
+        return function(self, args) { return infer.getInstance(base(self, args)); };
+      } else if (this.eat("!")) {
+        var arg = this.word(/\d/);
+        if (arg) {
+          arg = Number(arg);
+          return function(_self, args) {return args[arg] || infer.ANull;};
+        } else if (this.eat("this")) {
+          return function(self) {return self;};
+        } else if (this.eat("custom:")) {
+          var fname = this.word(/[\w$]/);
+          return customFunctions[fname] || function() { return infer.ANull; };
+        } else {
+          return this.fromWord("!" + arg + this.word(/[\w$<>\.!]/));
+        }
+      }
+      var t = this.parseType();
+      return function(){return t;};
+    },
+    extendRetType: function(base) {
+      var propName = this.word(/[\w<>$!]/) || this.error();
+      if (propName == "!ret") return function(self, args) {
+        var lhs = base(self, args);
+        if (lhs.retval) return lhs.retval;
+        var rv = new infer.AVal;
+        lhs.propagate(new infer.IsCallee(infer.ANull, [], null, rv));
+        return rv;
+      };
+      return function(self, args) {return base(self, args).getProp(propName);};
+    },
+    parseRetType: function() {
+      var tp = this.parseBaseRetType();
+      while (this.eat(".")) tp = this.extendRetType(tp);
+      return tp;
+    }
+  };
+
+  function parseType(spec, name, base, forceNew) {
+    var type = new TypeParser(spec, null, base, forceNew).parseType(name, true);
+    if (/^fn\(/.test(spec)) for (var i = 0; i < type.args.length; ++i) (function(i) {
+      var arg = type.args[i];
+      if (arg instanceof infer.Fn && arg.args && arg.args.length) addEffect(type, function(_self, fArgs) {
+        var fArg = fArgs[i];
+        if (fArg) fArg.propagate(new infer.IsCallee(infer.cx().topScope, arg.args, null, infer.ANull));
+      });
+    })(i);
+    return type;
+  }
+
+  function addEffect(fn, handler, replaceRet) {
+    var oldCmp = fn.computeRet, rv = fn.retval;
+    fn.computeRet = function(self, args, argNodes) {
+      var handled = handler(self, args, argNodes);
+      var old = oldCmp ? oldCmp(self, args, argNodes) : rv;
+      return replaceRet ? handled : old;
+    };
+  }
+
+  var parseEffect = exports.parseEffect = function(effect, fn) {
+    var m;
+    if (effect.indexOf("propagate ") == 0) {
+      var p = new TypeParser(effect, 10);
+      var getOrigin = p.parseRetType();
+      if (!p.eat(" ")) p.error();
+      var getTarget = p.parseRetType();
+      addEffect(fn, function(self, args) {
+        getOrigin(self, args).propagate(getTarget(self, args));
+      });
+    } else if (effect.indexOf("call ") == 0) {
+      var andRet = effect.indexOf("and return ", 5) == 5;
+      var p = new TypeParser(effect, andRet ? 16 : 5);
+      var getCallee = p.parseRetType(), getSelf = null, getArgs = [];
+      if (p.eat(" this=")) getSelf = p.parseRetType();
+      while (p.eat(" ")) getArgs.push(p.parseRetType());
+      addEffect(fn, function(self, args) {
+        var callee = getCallee(self, args);
+        var slf = getSelf ? getSelf(self, args) : infer.ANull, as = [];
+        for (var i = 0; i < getArgs.length; ++i) as.push(getArgs[i](self, args));
+        var result = andRet ? new infer.AVal : infer.ANull;
+        callee.propagate(new infer.IsCallee(slf, as, null, result));
+        return result;
+      }, andRet);
+    } else if (m = effect.match(/^custom (\S+)\s*(.*)/)) {
+      var customFunc = customFunctions[m[1]];
+      if (customFunc) addEffect(fn, m[2] ? customFunc(m[2]) : customFunc);
+    } else if (effect.indexOf("copy ") == 0) {
+      var p = new TypeParser(effect, 5);
+      var getFrom = p.parseRetType();
+      p.eat(" ");
+      var getTo = p.parseRetType();
+      addEffect(fn, function(self, args) {
+        var from = getFrom(self, args), to = getTo(self, args);
+        from.forAllProps(function(prop, val, local) {
+          if (local && prop != "<i>")
+            to.propagate(new infer.PropHasSubset(prop, val));
+        });
+      });
+    } else {
+      throw new Error("Unknown effect type: " + effect);
+    }
+  };
+
+  var currentTopScope;
+
+  var parsePath = exports.parsePath = function(path) {
+    var cx = infer.cx(), cached = cx.paths[path], origPath = path;
+    if (cached != null) return cached;
+    cx.paths[path] = infer.ANull;
+
+    var base = currentTopScope || cx.topScope;
+
+    if (cx.localDefs) for (var name in cx.localDefs) {
+      if (path.indexOf(name) == 0) {
+        if (path == name) return cx.paths[path] = cx.localDefs[path];
+        if (path.charAt(name.length) == ".") {
+          base = cx.localDefs[name];
+          path = path.slice(name.length + 1);
+          break;
+        }
+      }
+    }
+
+    var parts = path.split(".");
+    for (var i = 0; i < parts.length && base != infer.ANull; ++i) {
+      var prop = parts[i];
+      if (prop.charAt(0) == "!") {
+        if (prop == "!proto") {
+          base = (base instanceof infer.Obj && base.proto) || infer.ANull;
+        } else {
+          var fn = base.getFunctionType();
+          if (!fn) {
+            base = infer.ANull;
+          } else if (prop == "!ret") {
+            base = fn.retval && fn.retval.getType() || infer.ANull;
+          } else {
+            var arg = fn.args && fn.args[Number(prop.slice(1))];
+            base = (arg && arg.getType()) || infer.ANull;
+          }
+        }
+      } else if (base instanceof infer.Obj) {
+        var propVal = (prop == "prototype" && base instanceof infer.Fn) ? base.getProp(prop) : base.props[prop];
+        if (!propVal || propVal.isEmpty())
+          base = infer.ANull;
+        else
+          base = propVal.types[0];
+      }
+    }
+    // Uncomment this to get feedback on your poorly written .json files
+    // if (base == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")");
+    cx.paths[origPath] = base == infer.ANull ? null : base;
+    return base;
+  };
+
+  function emptyObj(ctor) {
+    var empty = Object.create(ctor.prototype);
+    empty.props = Object.create(null);
+    empty.isShell = true;
+    return empty;
+  }
+
+  function isSimpleAnnotation(spec) {
+    if (!spec["!type"] || /^fn\(/.test(spec["!type"])) return false;
+    for (var prop in spec)
+      if (prop != "!type" && prop != "!doc" && prop != "!url" && prop != "!span" && prop != "!data")
+        return false;
+    return true;
+  }
+
+  function passOne(base, spec, path) {
+    if (!base) {
+      var tp = spec["!type"];
+      if (tp) {
+        if (/^fn\(/.test(tp)) base = emptyObj(infer.Fn);
+        else if (tp.charAt(0) == "[") base = emptyObj(infer.Arr);
+        else throw new Error("Invalid !type spec: " + tp);
+      } else if (spec["!stdProto"]) {
+        base = infer.cx().protos[spec["!stdProto"]];
+      } else {
+        base = emptyObj(infer.Obj);
+      }
+      base.name = path;
+    }
+
+    for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
+      var inner = spec[name];
+      if (typeof inner == "string" || isSimpleAnnotation(inner)) continue;
+      var prop = base.defProp(name);
+      passOne(prop.getType(), inner, path ? path + "." + name : name).propagate(prop);
+    }
+    return base;
+  }
+
+  function passTwo(base, spec, path) {
+    if (base.isShell) {
+      delete base.isShell;
+      var tp = spec["!type"];
+      if (tp) {
+        parseType(tp, path, base);
+      } else {
+        var proto = spec["!proto"] && parseType(spec["!proto"]);
+        infer.Obj.call(base, proto instanceof infer.Obj ? proto : true, path);
+      }
+    }
+
+    var effects = spec["!effects"];
+    if (effects && base instanceof infer.Fn) for (var i = 0; i < effects.length; ++i)
+      parseEffect(effects[i], base);
+    copyInfo(spec, base);
+
+    for (var name in spec) if (hop(spec, name) && name.charCodeAt(0) != 33) {
+      var inner = spec[name], known = base.defProp(name), innerPath = path ? path + "." + name : name;
+      var type = known.getType();
+      if (typeof inner == "string") {
+        if (type) continue;
+        parseType(inner, innerPath).propagate(known);
+      } else {
+        if (!isSimpleAnnotation(inner)) {
+          passTwo(type, inner, innerPath);
+        } else if (!type) {
+          parseType(inner["!type"], innerPath, null, true).propagate(known);
+          type = known.getType();
+          if (type instanceof infer.Obj) copyInfo(inner, type);
+        } else continue;
+        if (inner["!doc"]) known.doc = inner["!doc"];
+        if (inner["!url"]) known.url = inner["!url"];
+        if (inner["!span"]) known.span = inner["!span"];
+      }
+    }
+  }
+
+  function copyInfo(spec, type) {
+    if (spec["!doc"]) type.doc = spec["!doc"];
+    if (spec["!url"]) type.url = spec["!url"];
+    if (spec["!span"]) type.span = spec["!span"];
+    if (spec["!data"]) type.metaData = spec["!data"];
+  }
+
+  function runPasses(type, arg) {
+    var parent = infer.cx().parent, pass = parent && parent.passes && parent.passes[type];
+    if (pass) for (var i = 0; i < pass.length; i++) pass[i](arg);
+  }
+
+  function doLoadEnvironment(data, scope) {
+    var cx = infer.cx();
+
+    infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + cx.origins.length);
+    cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null);
+
+    runPasses("preLoadDef", data);
+
+    passOne(scope, data);
+
+    var def = data["!define"];
+    if (def) {
+      for (var name in def) {
+        var spec = def[name];
+        cx.localDefs[name] = typeof spec == "string" ? parsePath(spec) : passOne(null, spec, name);
+      }
+      for (var name in def) {
+        var spec = def[name];
+        if (typeof spec != "string") passTwo(cx.localDefs[name], def[name], name);
+      }
+    }
+
+    passTwo(scope, data);
+
+    runPasses("postLoadDef", data);
+
+    cx.curOrigin = cx.localDefs = null;
+  }
+
+  exports.load = function(data, scope) {
+    if (!scope) scope = infer.cx().topScope;
+    var oldScope = currentTopScope;
+    currentTopScope = scope;
+    try {
+      doLoadEnvironment(data, scope);
+    } finally {
+      currentTopScope = oldScope;
+    }
+  };
+
+  // Used to register custom logic for more involved effect or type
+  // computation.
+  var customFunctions = Object.create(null);
+  infer.registerFunction = function(name, f) { customFunctions[name] = f; };
+
+  var IsCreated = infer.constraint("created, target, spec", {
+    addType: function(tp) {
+      if (tp instanceof infer.Obj && this.created++ < 5) {
+        var derived = new infer.Obj(tp), spec = this.spec;
+        if (spec instanceof infer.AVal) spec = spec.getType();
+        if (spec instanceof infer.Obj) for (var prop in spec.props) {
+          var cur = spec.props[prop].types[0];
+          var p = derived.defProp(prop);
+          if (cur && cur instanceof infer.Obj && cur.props.value) {
+            var vtp = cur.props.value.getType();
+            if (vtp) p.addType(vtp);
+          }
+        }
+        this.target.addType(derived);
+      }
+    }
+  });
+
+  infer.registerFunction("Object_create", function(_self, args, argNodes) {
+    if (argNodes && argNodes.length && argNodes[0].type == "Literal" && argNodes[0].value == null)
+      return new infer.Obj();
+
+    var result = new infer.AVal;
+    if (args[0]) args[0].propagate(new IsCreated(0, result, args[1]));
+    return result;
+  });
+
+  var IsBound = infer.constraint("self, args, target", {
+    addType: function(tp) {
+      if (!(tp instanceof infer.Fn)) return;
+      this.target.addType(new infer.Fn(tp.name, tp.self, tp.args.slice(this.args.length),
+                                       tp.argNames.slice(this.args.length), tp.retval));
+      this.self.propagate(tp.self);
+      for (var i = 0; i < Math.min(tp.args.length, this.args.length); ++i)
+        this.args[i].propagate(tp.args[i]);
+    }
+  });
+
+  infer.registerFunction("Function_bind", function(self, args) {
+    if (!args.length) return infer.ANull;
+    var result = new infer.AVal;
+    self.propagate(new IsBound(args[0], args.slice(1), result));
+    return result;
+  });
+
+  infer.registerFunction("Array_ctor", function(_self, args) {
+    var arr = new infer.Arr;
+    if (args.length != 1 || !args[0].hasType(infer.cx().num)) {
+      var content = arr.getProp("<i>");
+      for (var i = 0; i < args.length; ++i) args[i].propagate(content);
+    }
+    return arr;
+  });
+
+  return exports;
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/ecma5.js
@@ -0,0 +1,950 @@
+module.exports = {
+  "!name": "ecma5",
+  "!define": {"Error.prototype": "Error.prototype"},
+  "Infinity": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Infinity",
+    "!doc": "A numeric value representing infinity."
+  },
+  "undefined": {
+    "!type": "?",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/undefined",
+    "!doc": "The value undefined."
+  },
+  "NaN": {
+    "!type": "number",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/NaN",
+    "!doc": "A value representing Not-A-Number."
+  },
+  "Object": {
+    "!type": "fn()",
+    "getPrototypeOf": {
+      "!type": "fn(obj: ?) -> ?",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getPrototypeOf",
+      "!doc": "Returns the prototype (i.e. the internal prototype) of the specified object."
+    },
+    "create": {
+      "!type": "fn(proto: ?) -> !custom:Object_create",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create",
+      "!doc": "Creates a new object with the specified prototype object and properties."
+    },
+    "defineProperty": {
+      "!type": "fn(obj: ?, prop: string, desc: ?)",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty",
+      "!doc": "Defines a new property directly on an object, or modifies an existing property on an object, and returns the object. If you want to see how to use the Object.defineProperty method with a binary-flags-like syntax, see this article."
+    },
+    "defineProperties": {
+      "!type": "fn(obj: ?, props: ?)",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty",
+      "!doc": "Defines a new property directly on an object, or modifies an existing property on an object, and returns the object. If you want to see how to use the Object.defineProperty method with a binary-flags-like syntax, see this article."
+    },
+    "getOwnPropertyDescriptor": {
+      "!type": "fn(obj: ?, prop: string) -> ?",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor",
+      "!doc": "Returns a property descriptor for an own property (that is, one directly present on an object, not present by dint of being along an object's prototype chain) of a given object."
+    },
+    "keys": {
+      "!type": "fn(obj: ?) -> [string]",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys",
+      "!doc": "Returns an array of a given object's own enumerable properties, in the same order as that provided by a for-in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well)."
+    },
+    "getOwnPropertyNames": {
+      "!type": "fn(obj: ?) -> [string]",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames",
+      "!doc": "Returns an array of all properties (enumerable or not) found directly upon a given object."
+    },
+    "seal": {
+      "!type": "fn(obj: ?)",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/seal",
+      "!doc": "Seals an object, preventing new properties from being added to it and marking all existing properties as non-configurable. Values of present properties can still be changed as long as they are writable."
+    },
+    "isSealed": {
+      "!type": "fn(obj: ?) -> bool",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/isSealed",
+      "!doc": "Determine if an object is sealed."
+    },
+    "freeze": {
+      "!type": "fn(obj: ?)",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/freeze",
+      "!doc": "Freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed. In essence the object is made effectively immutable. The method returns the object being frozen."
+    },
+    "isFrozen": {
+      "!type": "fn(obj: ?) -> bool",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/isFrozen",
+      "!doc": "Determine if an object is frozen."
+    },
+    "prototype": {
+      "!stdProto": "Object",
+      "toString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/toString",
+        "!doc": "Returns a string representing the object."
+      },
+      "toLocaleString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/toLocaleString",
+        "!doc": "Returns a string representing the object. This method is meant to be overriden by derived objects for locale-specific purposes."
+      },
+      "valueOf": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/valueOf",
+        "!doc": "Returns the primitive value of the specified object"
+      },
+      "hasOwnProperty": {
+        "!type": "fn(prop: string) -> bool",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/hasOwnProperty",
+        "!doc": "Returns a boolean indicating whether the object has the specified property."
+      },
+      "propertyIsEnumerable": {
+        "!type": "fn(prop: string) -> bool",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable",
+        "!doc": "Returns a Boolean indicating whether the specified property is enumerable."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object",
+    "!doc": "Creates an object wrapper."
+  },
+  "Function": {
+    "!type": "fn(body: string) -> fn()",
+    "prototype": {
+      "!stdProto": "Function",
+      "apply": {
+        "!type": "fn(this: ?, args: [?])",
+        "!effects": [
+          "call and return !this this=!0 !1.<i> !1.<i> !1.<i>"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply",
+        "!doc": "Calls a function with a given this value and arguments provided as an array (or an array like object)."
+      },
+      "call": {
+        "!type": "fn(this: ?, args?: ?) -> !this.!ret",
+        "!effects": [
+          "call and return !this this=!0 !1 !2 !3 !4"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/call",
+        "!doc": "Calls a function with a given this value and arguments provided individually."
+      },
+      "bind": {
+        "!type": "fn(this: ?, args?: ?) -> !custom:Function_bind",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind",
+        "!doc": "Creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function was called."
+      },
+      "prototype": "?"
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function",
+    "!doc": "Every function in JavaScript is actually a Function object."
+  },
+  "Array": {
+    "!type": "fn(size: number) -> !custom:Array_ctor",
+    "isArray": {
+      "!type": "fn(value: ?) -> bool",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray",
+      "!doc": "Returns true if an object is an array, false if it is not."
+    },
+    "prototype": {
+      "!stdProto": "Array",
+      "length": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/length",
+        "!doc": "An unsigned, 32-bit integer that specifies the number of elements in an array."
+      },
+      "concat": {
+        "!type": "fn(other: [?]) -> !this",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/concat",
+        "!doc": "Returns a new array comprised of this array joined with other array(s) and/or value(s)."
+      },
+      "join": {
+        "!type": "fn(separator?: string) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/join",
+        "!doc": "Joins all elements of an array into a string."
+      },
+      "splice": {
+        "!type": "fn(pos: number, amount: number)",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/splice",
+        "!doc": "Changes the content of an array, adding new elements while removing old elements."
+      },
+      "pop": {
+        "!type": "fn() -> !this.<i>",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/pop",
+        "!doc": "Removes the last element from an array and returns that element."
+      },
+      "push": {
+        "!type": "fn(newelt: ?) -> number",
+        "!effects": [
+          "propagate !0 !this.<i>"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push",
+        "!doc": "Mutates an array by appending the given elements and returning the new length of the array."
+      },
+      "shift": {
+        "!type": "fn() -> !this.<i>",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/shift",
+        "!doc": "Removes the first element from an array and returns that element. This method changes the length of the array."
+      },
+      "unshift": {
+        "!type": "fn(newelt: ?) -> number",
+        "!effects": [
+          "propagate !0 !this.<i>"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/unshift",
+        "!doc": "Adds one or more elements to the beginning of an array and returns the new length of the array."
+      },
+      "slice": {
+        "!type": "fn(from: number, to?: number) -> !this",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/slice",
+        "!doc": "Returns a shallow copy of a portion of an array."
+      },
+      "reverse": {
+        "!type": "fn()",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/reverse",
+        "!doc": "Reverses an array in place.  The first array element becomes the last and the last becomes the first."
+      },
+      "sort": {
+        "!type": "fn(compare?: fn(a: ?, b: ?) -> number)",
+        "!effects": [
+          "call !0 !this.<i> !this.<i>"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort",
+        "!doc": "Sorts the elements of an array in place and returns the array."
+      },
+      "indexOf": {
+        "!type": "fn(elt: ?, from?: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf",
+        "!doc": "Returns the first index at which a given element can be found in the array, or -1 if it is not present."
+      },
+      "lastIndexOf": {
+        "!type": "fn(elt: ?, from?: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/lastIndexOf",
+        "!doc": "Returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex."
+      },
+      "every": {
+        "!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool",
+        "!effects": [
+          "call !0 this=!1 !this.<i> number"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every",
+        "!doc": "Tests whether all elements in the array pass the test implemented by the provided function."
+      },
+      "some": {
+        "!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool",
+        "!effects": [
+          "call !0 this=!1 !this.<i> number"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/some",
+        "!doc": "Tests whether some element in the array passes the test implemented by the provided function."
+      },
+      "filter": {
+        "!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> !this",
+        "!effects": [
+          "call !0 this=!1 !this.<i> number"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter",
+        "!doc": "Creates a new array with all elements that pass the test implemented by the provided function."
+      },
+      "forEach": {
+        "!type": "fn(f: fn(elt: ?, i: number), context?: ?)",
+        "!effects": [
+          "call !0 this=!1 !this.<i> number"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach",
+        "!doc": "Executes a provided function once per array element."
+      },
+      "map": {
+        "!type": "fn(f: fn(elt: ?, i: number) -> ?, context?: ?) -> [!0.!ret]",
+        "!effects": [
+          "call !0 this=!1 !this.<i> number"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map",
+        "!doc": "Creates a new array with the results of calling a provided function on every element in this array."
+      },
+      "reduce": {
+        "!type": "fn(combine: fn(sum: ?, elt: ?, i: number) -> ?, init?: ?) -> !0.!ret",
+        "!effects": [
+          "call !0 !1 !this.<i> number"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/Reduce",
+        "!doc": "Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value."
+      },
+      "reduceRight": {
+        "!type": "fn(combine: fn(sum: ?, elt: ?, i: number) -> ?, init?: ?) -> !0.!ret",
+        "!effects": [
+          "call !0 !1 !this.<i> number"
+        ],
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/ReduceRight",
+        "!doc": "Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array",
+    "!doc": "The JavaScript Array global object is a constructor for arrays, which are high-level, list-like objects."
+  },
+  "String": {
+    "!type": "fn(value: ?) -> string",
+    "fromCharCode": {
+      "!type": "fn(code: number) -> string",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/fromCharCode",
+      "!doc": "Returns a string created by using the specified sequence of Unicode values."
+    },
+    "prototype": {
+      "!stdProto": "String",
+      "length": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en/docs/JavaScript/Reference/Global_Objects/String/length",
+        "!doc": "Represents the length of a string."
+      },
+      "<i>": "string",
+      "charAt": {
+        "!type": "fn(i: number) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/charAt",
+        "!doc": "Returns the specified character from a string."
+      },
+      "charCodeAt": {
+        "!type": "fn(i: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/charCodeAt",
+        "!doc": "Returns the numeric Unicode value of the character at the given index (except for unicode codepoints > 0x10000)."
+      },
+      "indexOf": {
+        "!type": "fn(char: string, from?: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/indexOf",
+        "!doc": "Returns the index within the calling String object of the first occurrence of the specified value, starting the search at fromIndex,\nreturns -1 if the value is not found."
+      },
+      "lastIndexOf": {
+        "!type": "fn(char: string, from?: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/lastIndexOf",
+        "!doc": "Returns the index within the calling String object of the last occurrence of the specified value, or -1 if not found. The calling string is searched backward, starting at fromIndex."
+      },
+      "substring": {
+        "!type": "fn(from: number, to?: number) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substring",
+        "!doc": "Returns a subset of a string between one index and another, or through the end of the string."
+      },
+      "substr": {
+        "!type": "fn(from: number, length?: number) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substr",
+        "!doc": "Returns the characters in a string beginning at the specified location through the specified number of characters."
+      },
+      "slice": {
+        "!type": "fn(from: number, to?: number) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/slice",
+        "!doc": "Extracts a section of a string and returns a new string."
+      },
+      "trim": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/Trim",
+        "!doc": "Removes whitespace from both ends of the string."
+      },
+      "trimLeft": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/TrimLeft",
+        "!doc": "Removes whitespace from the left end of the string."
+      },
+      "trimRight": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/TrimRight",
+        "!doc": "Removes whitespace from the right end of the string."
+      },
+      "toUpperCase": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase",
+        "!doc": "Returns the calling string value converted to uppercase."
+      },
+      "toLowerCase": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLowerCase",
+        "!doc": "Returns the calling string value converted to lowercase."
+      },
+      "toLocaleUpperCase": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLocaleUpperCase",
+        "!doc": "Returns the calling string value converted to upper case, according to any locale-specific case mappings."
+      },
+      "toLocaleLowerCase": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLocaleLowerCase",
+        "!doc": "Returns the calling string value converted to lower case, according to any locale-specific case mappings."
+      },
+      "split": {
+        "!type": "fn(pattern: string) -> [string]",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/split",
+        "!doc": "Splits a String object into an array of strings by separating the string into substrings."
+      },
+      "concat": {
+        "!type": "fn(other: string) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/concat",
+        "!doc": "Combines the text of two or more strings and returns a new string."
+      },
+      "localeCompare": {
+        "!type": "fn(other: string) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/localeCompare",
+        "!doc": "Returns a number indicating whether a reference string comes before or after or is the same as the given string in sort order."
+      },
+      "match": {
+        "!type": "fn(pattern: +RegExp) -> [string]",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/match",
+        "!doc": "Used to retrieve the matches when matching a string against a regular expression."
+      },
+      "replace": {
+        "!type": "fn(pattern: +RegExp, replacement: string) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/replace",
+        "!doc": "Returns a new string with some or all matches of a pattern replaced by a replacement.  The pattern can be a string or a RegExp, and the replacement can be a string or a function to be called for each match."
+      },
+      "search": {
+        "!type": "fn(pattern: +RegExp) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/search",
+        "!doc": "Executes the search for a match between a regular expression and this String object."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String",
+    "!doc": "The String global object is a constructor for strings, or a sequence of characters."
+  },
+  "Number": {
+    "!type": "fn(value: ?) -> number",
+    "MAX_VALUE": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/MAX_VALUE",
+      "!doc": "The maximum numeric value representable in JavaScript."
+    },
+    "MIN_VALUE": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/MIN_VALUE",
+      "!doc": "The smallest positive numeric value representable in JavaScript."
+    },
+    "POSITIVE_INFINITY": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY",
+      "!doc": "A value representing the positive Infinity value."
+    },
+    "NEGATIVE_INFINITY": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/NEGATIVE_INFINITY",
+      "!doc": "A value representing the negative Infinity value."
+    },
+    "prototype": {
+      "!stdProto": "Number",
+      "toString": {
+        "!type": "fn(radix?: number) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toString",
+        "!doc": "Returns a string representing the specified Number object"
+      },
+      "toFixed": {
+        "!type": "fn(digits: number) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toFixed",
+        "!doc": "Formats a number using fixed-point notation"
+      },
+      "toExponential": {
+        "!type": "fn(digits: number) -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toExponential",
+        "!doc": "Returns a string representing the Number object in exponential notation"
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number",
+    "!doc": "The Number JavaScript object is a wrapper object allowing you to work with numerical values. A Number object is created using the Number() constructor."
+  },
+  "Boolean": {
+    "!type": "fn(value: ?) -> bool",
+    "prototype": {
+      "!stdProto": "Boolean"
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Boolean",
+    "!doc": "The Boolean object is an object wrapper for a boolean value."
+  },
+  "RegExp": {
+    "!type": "fn(source: string, flags?: string)",
+    "prototype": {
+      "!stdProto": "RegExp",
+      "exec": {
+        "!type": "fn(input: string) -> [string]",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/exec",
+        "!doc": "Executes a search for a match in a specified string. Returns a result array, or null."
+      },
+      "compile": {
+        "!type": "fn(source: string, flags?: string)",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
+        "!doc": "Creates a regular expression object for matching text with a pattern."
+      },
+      "test": {
+        "!type": "fn(input: string) -> bool",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/test",
+        "!doc": "Executes the search for a match between a regular expression and a specified string. Returns true or false."
+      },
+      "global": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
+        "!doc": "Creates a regular expression object for matching text with a pattern."
+      },
+      "ignoreCase": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
+        "!doc": "Creates a regular expression object for matching text with a pattern."
+      },
+      "multiline": {
+        "!type": "bool",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/multiline",
+        "!doc": "Reflects whether or not to search in strings across multiple lines.\n"
+      },
+      "source": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/source",
+        "!doc": "A read-only property that contains the text of the pattern, excluding the forward slashes.\n"
+      },
+      "lastIndex": {
+        "!type": "number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/lastIndex",
+        "!doc": "A read/write integer property that specifies the index at which to start the next match."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp",
+    "!doc": "Creates a regular expression object for matching text with a pattern."
+  },
+  "Date": {
+    "!type": "fn(ms: number)",
+    "parse": {
+      "!type": "fn(source: string) -> +Date",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse",
+      "!doc": "Parses a string representation of a date, and returns the number of milliseconds since January 1, 1970, 00:00:00 UTC."
+    },
+    "UTC": {
+      "!type": "fn(year: number, month: number, date: number, hour?: number, min?: number, sec?: number, ms?: number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/UTC",
+      "!doc": "Accepts the same parameters as the longest form of the constructor, and returns the number of milliseconds in a Date object since January 1, 1970, 00:00:00, universal time."
+    },
+    "now": {
+      "!type": "fn() -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/now",
+      "!doc": "Returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC."
+    },
+    "prototype": {
+      "toUTCString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toUTCString",
+        "!doc": "Converts a date to a string, using the universal time convention."
+      },
+      "toISOString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toISOString",
+        "!doc": "JavaScript provides a direct way to convert a date object into a string in ISO format, the ISO 8601 Extended Format."
+      },
+      "toDateString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toDateString",
+        "!doc": "Returns the date portion of a Date object in human readable form in American English."
+      },
+      "toTimeString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toTimeString",
+        "!doc": "Returns the time portion of a Date object in human readable form in American English."
+      },
+      "toLocaleDateString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleDateString",
+        "!doc": "Converts a date to a string, returning the \"date\" portion using the operating system's locale's conventions.\n"
+      },
+      "toLocaleTimeString": {
+        "!type": "fn() -> string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString",
+        "!doc": "Converts a date to a string, returning the \"time\" portion using the current locale's conventions."
+      },
+      "getTime": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getTime",
+        "!doc": "Returns the numeric value corresponding to the time for the specified date according to universal time."
+      },
+      "getFullYear": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getFullYear",
+        "!doc": "Returns the year of the specified date according to local time."
+      },
+      "getYear": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getYear",
+        "!doc": "Returns the year in the specified date according to local time."
+      },
+      "getMonth": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMonth",
+        "!doc": "Returns the month in the specified date according to local time."
+      },
+      "getUTCMonth": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCMonth",
+        "!doc": "Returns the month of the specified date according to universal time.\n"
+      },
+      "getDate": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getDate",
+        "!doc": "Returns the day of the month for the specified date according to local time."
+      },
+      "getUTCDate": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCDate",
+        "!doc": "Returns the day (date) of the month in the specified date according to universal time.\n"
+      },
+      "getDay": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getDay",
+        "!doc": "Returns the day of the week for the specified date according to local time."
+      },
+      "getUTCDay": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCDay",
+        "!doc": "Returns the day of the week in the specified date according to universal time.\n"
+      },
+      "getHours": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getHours",
+        "!doc": "Returns the hour for the specified date according to local time."
+      },
+      "getUTCHours": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCHours",
+        "!doc": "Returns the hours in the specified date according to universal time.\n"
+      },
+      "getMinutes": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMinutes",
+        "!doc": "Returns the minutes in the specified date according to local time."
+      },
+      "getUTCMinutes": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date",
+        "!doc": "Creates JavaScript Date instances which let you work with dates and times."
+      },
+      "getSeconds": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getSeconds",
+        "!doc": "Returns the seconds in the specified date according to local time."
+      },
+      "getUTCSeconds": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCSeconds",
+        "!doc": "Returns the seconds in the specified date according to universal time.\n"
+      },
+      "getMilliseconds": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMilliseconds",
+        "!doc": "Returns the milliseconds in the specified date according to local time."
+      },
+      "getUTCMilliseconds": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds",
+        "!doc": "Returns the milliseconds in the specified date according to universal time.\n"
+      },
+      "getTimezoneOffset": {
+        "!type": "fn() -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset",
+        "!doc": "Returns the time-zone offset from UTC, in minutes, for the current locale."
+      },
+      "setTime": {
+        "!type": "fn(date: +Date) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setTime",
+        "!doc": "Sets the Date object to the time represented by a number of milliseconds since January 1, 1970, 00:00:00 UTC.\n"
+      },
+      "setFullYear": {
+        "!type": "fn(year: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setFullYear",
+        "!doc": "Sets the full year for a specified date according to local time.\n"
+      },
+      "setUTCFullYear": {
+        "!type": "fn(year: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCFullYear",
+        "!doc": "Sets the full year for a specified date according to universal time.\n"
+      },
+      "setMonth": {
+        "!type": "fn(month: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMonth",
+        "!doc": "Set the month for a specified date according to local time."
+      },
+      "setUTCMonth": {
+        "!type": "fn(month: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMonth",
+        "!doc": "Sets the month for a specified date according to universal time.\n"
+      },
+      "setDate": {
+        "!type": "fn(day: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setDate",
+        "!doc": "Sets the day of the month for a specified date according to local time."
+      },
+      "setUTCDate": {
+        "!type": "fn(day: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCDate",
+        "!doc": "Sets the day of the month for a specified date according to universal time.\n"
+      },
+      "setHours": {
+        "!type": "fn(hour: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setHours",
+        "!doc": "Sets the hours for a specified date according to local time, and returns the number of milliseconds since 1 January 1970 00:00:00 UTC until the time represented by the updated Date instance."
+      },
+      "setUTCHours": {
+        "!type": "fn(hour: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCHours",
+        "!doc": "Sets the hour for a specified date according to universal time.\n"
+      },
+      "setMinutes": {
+        "!type": "fn(min: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMinutes",
+        "!doc": "Sets the minutes for a specified date according to local time."
+      },
+      "setUTCMinutes": {
+        "!type": "fn(min: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMinutes",
+        "!doc": "Sets the minutes for a specified date according to universal time.\n"
+      },
+      "setSeconds": {
+        "!type": "fn(sec: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setSeconds",
+        "!doc": "Sets the seconds for a specified date according to local time."
+      },
+      "setUTCSeconds": {
+        "!type": "fn(sec: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCSeconds",
+        "!doc": "Sets the seconds for a specified date according to universal time.\n"
+      },
+      "setMilliseconds": {
+        "!type": "fn(ms: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMilliseconds",
+        "!doc": "Sets the milliseconds for a specified date according to local time.\n"
+      },
+      "setUTCMilliseconds": {
+        "!type": "fn(ms: number) -> number",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMilliseconds",
+        "!doc": "Sets the milliseconds for a specified date according to universal time.\n"
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date",
+    "!doc": "Creates JavaScript Date instances which let you work with dates and times."
+  },
+  "Error": {
+    "!type": "fn(message: string)",
+    "prototype": {
+      "name": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error/name",
+        "!doc": "A name for the type of error."
+      },
+      "message": {
+        "!type": "string",
+        "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error/message",
+        "!doc": "A human-readable description of the error."
+      }
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error",
+    "!doc": "Creates an error object."
+  },
+  "SyntaxError": {
+    "!type": "fn(message: string)",
+    "prototype": "Error.prototype",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/SyntaxError",
+    "!doc": "Represents an error when trying to interpret syntactically invalid code."
+  },
+  "ReferenceError": {
+    "!type": "fn(message: string)",
+    "prototype": "Error.prototype",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/ReferenceError",
+    "!doc": "Represents an error when a non-existent variable is referenced."
+  },
+  "URIError": {
+    "!type": "fn(message: string)",
+    "prototype": "Error.prototype",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/URIError",
+    "!doc": "Represents an error when a malformed URI is encountered."
+  },
+  "EvalError": {
+    "!type": "fn(message: string)",
+    "prototype": "Error.prototype",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/EvalError",
+    "!doc": "Represents an error regarding the eval function."
+  },
+  "RangeError": {
+    "!type": "fn(message: string)",
+    "prototype": "Error.prototype",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RangeError",
+    "!doc": "Represents an error when a number is not within the correct range allowed."
+  },
+  "parseInt": {
+    "!type": "fn(string: string, radix?: number) -> number",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/parseInt",
+    "!doc": "Parses a string argument and returns an integer of the specified radix or base."
+  },
+  "parseFloat": {
+    "!type": "fn(string: string) -> number",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/parseFloat",
+    "!doc": "Parses a string argument and returns a floating point number."
+  },
+  "isNaN": {
+    "!type": "fn(value: number) -> bool",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/isNaN",
+    "!doc": "Determines whether a value is NaN or not. Be careful, this function is broken. You may be interested in ECMAScript 6 Number.isNaN."
+  },
+  "eval": {
+    "!type": "fn(code: string) -> ?",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/eval",
+    "!doc": "Evaluates JavaScript code represented as a string."
+  },
+  "encodeURI": {
+    "!type": "fn(uri: string) -> string",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURI",
+    "!doc": "Encodes a Uniform Resource Identifier (URI) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two \"surrogate\" characters)."
+  },
+  "encodeURIComponent": {
+    "!type": "fn(uri: string) -> string",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent",
+    "!doc": "Encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two \"surrogate\" characters)."
+  },
+  "decodeURI": {
+    "!type": "fn(uri: string) -> string",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/decodeURI",
+    "!doc": "Decodes a Uniform Resource Identifier (URI) previously created by encodeURI or by a similar routine."
+  },
+  "decodeURIComponent": {
+    "!type": "fn(uri: string) -> string",
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/decodeURIComponent",
+    "!doc": "Decodes a Uniform Resource Identifier (URI) component previously created by encodeURIComponent or by a similar routine."
+  },
+  "Math": {
+    "E": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/E",
+      "!doc": "The base of natural logarithms, e, approximately 2.718."
+    },
+    "LN2": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LN2",
+      "!doc": "The natural logarithm of 2, approximately 0.693."
+    },
+    "LN10": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LN10",
+      "!doc": "The natural logarithm of 10, approximately 2.302."
+    },
+    "LOG2E": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LOG2E",
+      "!doc": "The base 2 logarithm of E (approximately 1.442)."
+    },
+    "LOG10E": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LOG10E",
+      "!doc": "The base 10 logarithm of E (approximately 0.434)."
+    },
+    "SQRT1_2": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/SQRT1_2",
+      "!doc": "The square root of 1/2; equivalently, 1 over the square root of 2, approximately 0.707."
+    },
+    "SQRT2": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/SQRT2",
+      "!doc": "The square root of 2, approximately 1.414."
+    },
+    "PI": {
+      "!type": "number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/PI",
+      "!doc": "The ratio of the circumference of a circle to its diameter, approximately 3.14159."
+    },
+    "abs": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/abs",
+      "!doc": "Returns the absolute value of a number."
+    },
+    "cos": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/cos",
+      "!doc": "Returns the cosine of a number."
+    },
+    "sin": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/sin",
+      "!doc": "Returns the sine of a number."
+    },
+    "tan": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/tan",
+      "!doc": "Returns the tangent of a number."
+    },
+    "acos": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/acos",
+      "!doc": "Returns the arccosine (in radians) of a number."
+    },
+    "asin": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/asin",
+      "!doc": "Returns the arcsine (in radians) of a number."
+    },
+    "atan": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/atan",
+      "!doc": "Returns the arctangent (in radians) of a number."
+    },
+    "atan2": {
+      "!type": "fn(number, number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/atan2",
+      "!doc": "Returns the arctangent of the quotient of its arguments."
+    },
+    "ceil": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/ceil",
+      "!doc": "Returns the smallest integer greater than or equal to a number."
+    },
+    "floor": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/floor",
+      "!doc": "Returns the largest integer less than or equal to a number."
+    },
+    "round": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/round",
+      "!doc": "Returns the value of a number rounded to the nearest integer."
+    },
+    "exp": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/exp",
+      "!doc": "Returns Ex, where x is the argument, and E is Euler's constant, the base of the natural logarithms."
+    },
+    "log": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/log",
+      "!doc": "Returns the natural logarithm (base E) of a number."
+    },
+    "sqrt": {
+      "!type": "fn(number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/sqrt",
+      "!doc": "Returns the square root of a number."
+    },
+    "pow": {
+      "!type": "fn(number, number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/pow",
+      "!doc": "Returns base to the exponent power, that is, baseexponent."
+    },
+    "max": {
+      "!type": "fn(number, number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/max",
+      "!doc": "Returns the largest of zero or more numbers."
+    },
+    "min": {
+      "!type": "fn(number, number) -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/min",
+      "!doc": "Returns the smallest of zero or more numbers."
+    },
+    "random": {
+      "!type": "fn() -> number",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random",
+      "!doc": "Returns a floating-point, pseudo-random number in the range [0, 1) that is, from 0 (inclusive) up to but not including 1 (exclusive), which you can then scale to your desired range."
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math",
+    "!doc": "A built-in object that has properties and methods for mathematical constants and functions."
+  },
+  "JSON": {
+    "parse": {
+      "!type": "fn(json: string) -> ?",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/parse",
+      "!doc": "Parse a string as JSON, optionally transforming the value produced by parsing."
+    },
+    "stringify": {
+      "!type": "fn(value: ?) -> string",
+      "!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify",
+      "!doc": "Convert a value to JSON, optionally replacing values if a replacer function is specified, or optionally including only the specified properties if a replacer array is specified."
+    },
+    "!url": "https://developer.mozilla.org/en-US/docs/JSON",
+    "!doc": "JSON (JavaScript Object Notation) is a data-interchange format.  It closely resembles a subset of JavaScript syntax, although it is not a strict subset. (See JSON in the JavaScript Reference for full details.)  It is useful when writing any kind of JavaScript-based application, including websites and browser extensions.  For example, you might store user information in JSON format in a cookie, or you might store extension preferences in JSON in a string-valued browser preference."
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/infer.js
@@ -0,0 +1,1429 @@
+// Main type inference engine
+
+// Walks an AST, building up a graph of abstract values and contraints
+// that cause types to flow from one node to another. Also defines a
+// number of utilities for accessing ASTs and scopes.
+
+// Analysis is done in a context, which is tracked by the dynamically
+// bound cx variable. Use withContext to set the current context.
+
+// For memory-saving reasons, individual types export an interface
+// similar to abstract values (which can hold multiple types), and can
+// thus be used in place abstract values that only ever contain a
+// single type.
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports, require("acorn/acorn"), require("acorn/acorn_loose"), require("acorn/util/walk"),
+               require("./def"), require("./signal"));
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports", "acorn/acorn", "acorn/acorn_loose", "acorn/util/walk", "./def", "./signal"], mod);
+  mod(self.tern || (self.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env
+})(function(exports, acorn, acorn_loose, walk, def, signal) {
+  "use strict";
+
+  var toString = exports.toString = function(type, maxDepth, parent) {
+    return !type || type == parent ? "?": type.toString(maxDepth);
+  };
+
+  // A variant of AVal used for unknown, dead-end values. Also serves
+  // as prototype for AVals, Types, and Constraints because it
+  // implements 'empty' versions of all the methods that the code
+  // expects.
+  var ANull = exports.ANull = signal.mixin({
+    addType: function() {},
+    propagate: function() {},
+    getProp: function() { return ANull; },
+    forAllProps: function() {},
+    hasType: function() { return false; },
+    isEmpty: function() { return true; },
+    getFunctionType: function() {},
+    getType: function() {},
+    gatherProperties: function() {},
+    propagatesTo: function() {},
+    typeHint: function() {},
+    propHint: function() {}
+  });
+
+  function extend(proto, props) {
+    var obj = Object.create(proto);
+    if (props) for (var prop in props) obj[prop] = props[prop];
+    return obj;
+  }
+
+  // ABSTRACT VALUES
+
+  var WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10, WG_MULTI_MEMBER = 5,
+      WG_CATCH_ERROR = 5, WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2;
+
+  var AVal = exports.AVal = function() {
+    this.types = [];
+    this.forward = null;
+    this.maxWeight = 0;
+  };
+  AVal.prototype = extend(ANull, {
+    addType: function(type, weight) {
+      weight = weight || WG_DEFAULT;
+      if (this.maxWeight < weight) {
+        this.maxWeight = weight;
+        if (this.types.length == 1 && this.types[0] == type) return;
+        this.types.length = 0;
+      } else if (this.maxWeight > weight || this.types.indexOf(type) > -1) {
+        return;
+      }
+
+      this.signal("addType", type);
+      this.types.push(type);
+      var forward = this.forward;
+      if (forward) withWorklist(function(add) {
+        for (var i = 0; i < forward.length; ++i) add(type, forward[i], weight);
+      });
+    },
+
+    propagate: function(target, weight) {
+      if (target == ANull || (target instanceof Type)) return;
+      if (weight && weight < WG_DEFAULT) target = new Muffle(target, weight);
+      (this.forward || (this.forward = [])).push(target);
+      var types = this.types;
+      if (types.length) withWorklist(function(add) {
+        for (var i = 0; i < types.length; ++i) add(types[i], target, weight);
+      });
+    },
+
+    getProp: function(prop) {
+      if (prop == "__proto__" || prop == "✖") return ANull;
+      var found = (this.props || (this.props = Object.create(null)))[prop];
+      if (!found) {
+        found = this.props[prop] = new AVal;
+        this.propagate(new PropIsSubset(prop, found));
+      }
+      return found;
+    },
+
+    forAllProps: function(c) {
+      this.propagate(new ForAllProps(c));
+    },
+
+    hasType: function(type) {
+      return this.types.indexOf(type) > -1;
+    },
+    isEmpty: function() { return this.types.length == 0; },
+    getFunctionType: function() {
+      for (var i = this.types.length - 1; i >= 0; --i)
+        if (this.types[i] instanceof Fn) return this.types[i];
+    },
+
+    getType: function(guess) {
+      if (this.types.length == 0 && guess !== false) return this.makeupType();
+      if (this.types.length == 1) return this.types[0];
+      return canonicalType(this.types);
+    },
+
+    makeupType: function() {
+      if (!this.forward) return null;
+      for (var i = this.forward.length - 1; i >= 0; --i) {
+        var hint = this.forward[i].typeHint();
+        if (hint && !hint.isEmpty()) {guessing = true; return hint;}
+      }
+
+      var props = Object.create(null), foundProp = null;
+      for (var i = 0; i < this.forward.length; ++i) {
+        var prop = this.forward[i].propHint();
+        if (prop && prop != "length" && prop != "<i>" && prop != "✖") {
+          props[prop] = true;
+          foundProp = prop;
+        }
+      }
+      if (!foundProp) return null;
+
+      var objs = objsWithProp(foundProp);
+      if (objs) {
+        var matches = [];
+        search: for (var i = 0; i < objs.length; ++i) {
+          var obj = objs[i];
+          for (var prop in props) if (!obj.hasProp(prop)) continue search;
+          if (obj.hasCtor) obj = getInstance(obj);
+          matches.push(obj);
+        }
+        var canon = canonicalType(matches);
+        if (canon) {guessing = true; return canon;}
+      }
+    },
+
+    typeHint: function() { return this.types.length ? this.getType() : null; },
+    propagatesTo: function() { return this; },
+
+    gatherProperties: function(f, depth) {
+      for (var i = 0; i < this.types.length; ++i)
+        this.types[i].gatherProperties(f, depth);
+    },
+
+    guessProperties: function(f) {
+      if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
+        var prop = this.forward[i].propHint();
+        if (prop) f(prop, null, 0);
+      }
+    }
+  });
+
+  function canonicalType(types) {
+    var arrays = 0, fns = 0, objs = 0, prim = null;
+    for (var i = 0; i < types.length; ++i) {
+      var tp = types[i];
+      if (tp instanceof Arr) ++arrays;
+      else if (tp instanceof Fn) ++fns;
+      else if (tp instanceof Obj) ++objs;
+      else if (tp instanceof Prim) {
+        if (prim && tp.name != prim.name) return null;
+        prim = tp;
+      }
+    }
+    var kinds = (arrays && 1) + (fns && 1) + (objs && 1) + (prim && 1);
+    if (kinds > 1) return null;
+    if (prim) return prim;
+
+    var maxScore = 0, maxTp = null;
+    for (var i = 0; i < types.length; ++i) {
+      var tp = types[i], score = 0;
+      if (arrays) {
+        score = tp.getProp("<i>").isEmpty() ? 1 : 2;
+      } else if (fns) {
+        score = 1;
+        for (var j = 0; j < tp.args.length; ++j) if (!tp.args[j].isEmpty()) ++score;
+        if (!tp.retval.isEmpty()) ++score;
+      } else if (objs) {
+        score = tp.name ? 100 : 2;
+      }
+      if (score >= maxScore) { maxScore = score; maxTp = tp; }
+    }
+    return maxTp;
+  }
+
+  // PROPAGATION STRATEGIES
+
+  function Constraint() {}
+  Constraint.prototype = extend(ANull, {
+    init: function() { this.origin = cx.curOrigin; }
+  });
+
+  var constraint = exports.constraint = function(props, methods) {
+    var body = "this.init();";
+    props = props ? props.split(", ") : [];
+    for (var i = 0; i < props.length; ++i)
+      body += "this." + props[i] + " = " + props[i] + ";";
+    var ctor = Function.apply(null, props.concat([body]));
+    ctor.prototype = Object.create(Constraint.prototype);
+    for (var m in methods) if (methods.hasOwnProperty(m)) ctor.prototype[m] = methods[m];
+    return ctor;
+  };
+
+  var PropIsSubset = constraint("prop, target", {
+    addType: function(type, weight) {
+      if (type.getProp)
+        type.getProp(this.prop).propagate(this.target, weight);
+    },
+    propHint: function() { return this.prop; },
+    propagatesTo: function() {
+      return {target: this.target, pathExt: "." + this.prop};
+    }
+  });
+
+  var PropHasSubset = exports.PropHasSubset = constraint("prop, type, originNode", {
+    addType: function(type, weight) {
+      if (!(type instanceof Obj)) return;
+      var prop = type.defProp(this.prop, this.originNode);
+      prop.origin = this.origin;
+      this.type.propagate(prop, weight);
+    },
+    propHint: function() { return this.prop; }
+  });
+
+  var ForAllProps = constraint("c", {
+    addType: function(type) {
+      if (!(type instanceof Obj)) return;
+      type.forAllProps(this.c);
+    }
+  });
+
+  function withDisabledComputing(fn, body) {
+    cx.disabledComputing = {fn: fn, prev: cx.disabledComputing};
+    try {
+      return body();
+    } finally {
+      cx.disabledComputing = cx.disabledComputing.prev;
+    }
+  }
+  var IsCallee = exports.IsCallee = constraint("self, args, argNodes, retval", {
+    init: function() {
+      Constraint.prototype.init();
+      this.disabled = cx.disabledComputing;
+    },
+    addType: function(fn, weight) {
+      if (!(fn instanceof Fn)) return;
+      for (var i = 0; i < this.args.length; ++i) {
+        if (i < fn.args.length) this.args[i].propagate(fn.args[i], weight);
+        if (fn.arguments) this.args[i].propagate(fn.arguments, weight);
+      }
+      this.self.propagate(fn.self, this.self == cx.topScope ? WG_GLOBAL_THIS : weight);
+      var compute = fn.computeRet;
+      if (compute) for (var d = this.disabled; d; d = d.prev)
+        if (d.fn == fn || fn.name && d.fn.name == fn.name) compute = null;
+      if (compute)
+        compute(this.self, this.args, this.argNodes).propagate(this.retval, weight);
+      else
+        fn.retval.propagate(this.retval, weight);
+    },
+    typeHint: function() {
+      var names = [];
+      for (var i = 0; i < this.args.length; ++i) names.push("?");
+      return new Fn(null, this.self, this.args, names, ANull);
+    },
+    propagatesTo: function() {
+      return {target: this.retval, pathExt: ".!ret"};
+    }
+  });
+
+  var HasMethodCall = constraint("propName, args, argNodes, retval", {
+    init: function() {
+      Constraint.prototype.init();
+      this.disabled = cx.disabledComputing;
+    },
+    addType: function(obj, weight) {
+      var callee = new IsCallee(obj, this.args, this.argNodes, this.retval);
+      callee.disabled = this.disabled;
+      obj.getProp(this.propName).propagate(callee, weight);
+    },
+    propHint: function() { return this.propName; }
+  });
+
+  var IsCtor = exports.IsCtor = constraint("target, noReuse", {
+    addType: function(f, weight) {
+      if (!(f instanceof Fn)) return;
+      f.getProp("prototype").propagate(new IsProto(this.noReuse ? false : f, this.target), weight);
+    }
+  });
+
+  var getInstance = exports.getInstance = function(obj, ctor) {
+    if (ctor === false) return new Obj(obj);
+
+    if (!ctor) ctor = obj.hasCtor;
+    if (!obj.instances) obj.instances = [];
+    for (var i = 0; i < obj.instances.length; ++i) {
+      var cur = obj.instances[i];
+      if (cur.ctor == ctor) return cur.instance;
+    }
+    var instance = new Obj(obj, ctor && ctor.name);
+    instance.origin = obj.origin;
+    obj.instances.push({ctor: ctor, instance: instance});
+    return instance;
+  };
+
+  var IsProto = exports.IsProto = constraint("ctor, target", {
+    addType: function(o, _weight) {
+      if (!(o instanceof Obj)) return;
+      if ((this.count = (this.count || 0) + 1) > 8) return;
+      if (o == cx.protos.Array)
+        this.target.addType(new Arr);
+      else
+        this.target.addType(getInstance(o, this.ctor));
+    }
+  });
+
+  var FnPrototype = constraint("fn", {
+    addType: function(o, _weight) {
+      if (o instanceof Obj && !o.hasCtor) {
+        o.hasCtor = this.fn;
+        var adder = new SpeculativeThis(o, this.fn);
+        adder.addType(this.fn);
+        o.forAllProps(function(_prop, val, local) {
+          if (local) val.propagate(adder);
+        });
+      }
+    }
+  });
+
+  var IsAdded = constraint("other, target", {
+    addType: function(type, weight) {
+      if (type == cx.str)
+        this.target.addType(cx.str, weight);
+      else if (type == cx.num && this.other.hasType(cx.num))
+        this.target.addType(cx.num, weight);
+    },
+    typeHint: function() { return this.other; }
+  });
+
+  var IfObj = exports.IfObj = constraint("target", {
+    addType: function(t, weight) {
+      if (t instanceof Obj) this.target.addType(t, weight);
+    },
+    propagatesTo: function() { return this.target; }
+  });
+
+  var SpeculativeThis = constraint("obj, ctor", {
+    addType: function(tp) {
+      if (tp instanceof Fn && tp.self && tp.self.isEmpty())
+        tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_THIS);
+    }
+  });
+
+  var Muffle = constraint("inner, weight", {
+    addType: function(tp, weight) {
+      this.inner.addType(tp, Math.min(weight, this.weight));
+    },
+    propagatesTo: function() { return this.inner.propagatesTo(); },
+    typeHint: function() { return this.inner.typeHint(); },
+    propHint: function() { return this.inner.propHint(); }
+  });
+
+  // TYPE OBJECTS
+
+  var Type = exports.Type = function() {};
+  Type.prototype = extend(ANull, {
+    propagate: function(c, w) { c.addType(this, w); },
+    hasType: function(other) { return other == this; },
+    isEmpty: function() { return false; },
+    typeHint: function() { return this; },
+    getType: function() { return this; }
+  });
+
+  var Prim = exports.Prim = function(proto, name) { this.name = name; this.proto = proto; };
+  Prim.prototype = extend(Type.prototype, {
+    toString: function() { return this.name; },
+    getProp: function(prop) {return this.proto.hasProp(prop) || ANull;},
+    gatherProperties: function(f, depth) {
+      if (this.proto) this.proto.gatherProperties(f, depth);
+    }
+  });
+
+  var Obj = exports.Obj = function(proto, name) {
+    if (!this.props) this.props = Object.create(null);
+    this.proto = proto === true ? cx.protos.Object : proto;
+    if (proto && !name && proto.name && !(this instanceof Fn)) {
+      var match = /^(.*)\.prototype$/.exec(this.proto.name);
+      if (match) name = match[1];
+    }
+    this.name = name;
+    this.maybeProps = null;
+    this.origin = cx.curOrigin;
+  };
+  Obj.prototype = extend(Type.prototype, {
+    toString: function(maxDepth) {
+      if (!maxDepth && this.name) return this.name;
+      var props = [], etc = false;
+      for (var prop in this.props) if (prop != "<i>") {
+        if (props.length > 5) { etc = true; break; }
+        if (maxDepth)
+          props.push(prop + ": " + toString(this.props[prop].getType(), maxDepth - 1));
+        else
+          props.push(prop);
+      }
+      props.sort();
+      if (etc) props.push("...");
+      return "{" + props.join(", ") + "}";
+    },
+    hasProp: function(prop, searchProto) {
+      var found = this.props[prop];
+      if (searchProto !== false)
+        for (var p = this.proto; p && !found; p = p.proto) found = p.props[prop];
+      return found;
+    },
+    defProp: function(prop, originNode) {
+      var found = this.hasProp(prop, false);
+      if (found) {
+        if (originNode && !found.originNode) found.originNode = originNode;
+        return found;
+      }
+      if (prop == "__proto__" || prop == "✖") return ANull;
+
+      var av = this.maybeProps && this.maybeProps[prop];
+      if (av) {
+        delete this.maybeProps[prop];
+        this.maybeUnregProtoPropHandler();
+      } else {
+        av = new AVal;
+      }
+
+      this.props[prop] = av;
+      av.originNode = originNode;
+      av.origin = cx.curOrigin;
+      this.broadcastProp(prop, av, true);
+      return av;
+    },
+    getProp: function(prop) {
+      var found = this.hasProp(prop, true) || (this.maybeProps && this.maybeProps[prop]);
+      if (found) return found;
+      if (prop == "__proto__" || prop == "✖") return ANull;
+      return this.ensureMaybeProps()[prop] = new AVal;
+    },
+    broadcastProp: function(prop, val, local) {
+      if (local) {
+        this.signal("addProp", prop, val);
+        // If this is a scope, it shouldn't be registered
+        if (!(this instanceof Scope)) registerProp(prop, this);
+      }
+
+      if (this.onNewProp) for (var i = 0; i < this.onNewProp.length; ++i) {
+        var h = this.onNewProp[i];
+        h.onProtoProp ? h.onProtoProp(prop, val, local) : h(prop, val, local);
+      }
+    },
+    onProtoProp: function(prop, val, _local) {
+      var maybe = this.maybeProps && this.maybeProps[prop];
+      if (maybe) {
+        delete this.maybeProps[prop];
+        this.maybeUnregProtoPropHandler();
+        this.proto.getProp(prop).propagate(maybe);
+      }
+      this.broadcastProp(prop, val, false);
+    },
+    ensureMaybeProps: function() {
+      if (!this.maybeProps) {
+        if (this.proto) this.proto.forAllProps(this);
+        this.maybeProps = Object.create(null);
+      }
+      return this.maybeProps;
+    },
+    removeProp: function(prop) {
+      var av = this.props[prop];
+      delete this.props[prop];
+      this.ensureMaybeProps()[prop] = av;
+    },
+    forAllProps: function(c) {
+      if (!this.onNewProp) {
+        this.onNewProp = [];
+        if (this.proto) this.proto.forAllProps(this);
+      }
+      this.onNewProp.push(c);
+      for (var o = this; o; o = o.proto) for (var prop in o.props) {
+        if (c.onProtoProp)
+          c.onProtoProp(prop, o.props[prop], o == this);
+        else
+          c(prop, o.props[prop], o == this);
+      }
+    },
+    maybeUnregProtoPropHandler: function() {
+      if (this.maybeProps) {
+        for (var _n in this.maybeProps) return;
+        this.maybeProps = null;
+      }
+      if (!this.proto || this.onNewProp && this.onNewProp.length) return;
+      this.proto.unregPropHandler(this);
+    },
+    unregPropHandler: function(handler) {
+      for (var i = 0; i < this.onNewProp.length; ++i)
+        if (this.onNewProp[i] == handler) { this.onNewProp.splice(i, 1); break; }
+      this.maybeUnregProtoPropHandler();
+    },
+    gatherProperties: function(f, depth) {
+      for (var prop in this.props) if (prop != "<i>")
+        f(prop, this, depth);
+      if (this.proto) this.proto.gatherProperties(f, depth + 1);
+    }
+  });
+
+  var Fn = exports.Fn = function(name, self, args, argNames, retval) {
+    Obj.call(this, cx.protos.Function, name);
+    this.self = self;
+    this.args = args;
+    this.argNames = argNames;
+    this.retval = retval;
+  };
+  Fn.prototype = extend(Obj.prototype, {
+    toString: function(maxDepth) {
+      if (maxDepth) maxDepth--;
+      var str = "fn(";
+      for (var i = 0; i < this.args.length; ++i) {
+        if (i) str += ", ";
+        var name = this.argNames[i];
+        if (name && name != "?") str += name + ": ";
+        str += toString(this.args[i].getType(), maxDepth, this);
+      }
+      str += ")";
+      if (!this.retval.isEmpty())
+        str += " -> " + toString(this.retval.getType(), maxDepth, this);
+      return str;
+    },
+    getProp: function(prop) {
+      if (prop == "prototype") {
+        var known = this.hasProp(prop, false);
+        if (!known) {
+          known = this.defProp(prop);
+          var proto = new Obj(true, this.name && this.name + ".prototype");
+          proto.origin = this.origin;
+          known.addType(proto, WG_MADEUP_PROTO);
+        }
+        return known;
+      }
+      return Obj.prototype.getProp.call(this, prop);
+    },
+    defProp: function(prop, originNode) {
+      if (prop == "prototype") {
+        var found = this.hasProp(prop, false);
+        if (found) return found;
+        found = Obj.prototype.defProp.call(this, prop, originNode);
+        found.origin = this.origin;
+        found.propagate(new FnPrototype(this));
+        return found;
+      }
+      return Obj.prototype.defProp.call(this, prop, originNode);
+    },
+    getFunctionType: function() { return this; }
+  });
+
+  var Arr = exports.Arr = function(contentType) {
+    Obj.call(this, cx.protos.Array);
+    var content = this.defProp("<i>");
+    if (contentType) contentType.propagate(content);
+  };
+  Arr.prototype = extend(Obj.prototype, {
+    toString: function(maxDepth) {
+      return "[" + toString(this.getProp("<i>").getType(), maxDepth, this) + "]";
+    }
+  });
+
+  // THE PROPERTY REGISTRY
+
+  function registerProp(prop, obj) {
+    var data = cx.props[prop] || (cx.props[prop] = []);
+    data.push(obj);
+  }
+
+  function objsWithProp(prop) {
+    return cx.props[prop];
+  }
+
+  // INFERENCE CONTEXT
+
+  exports.Context = function(defs, parent) {
+    this.parent = parent;
+    this.props = Object.create(null);
+    this.protos = Object.create(null);
+    this.origins = [];
+    this.curOrigin = "ecma5";
+    this.paths = Object.create(null);
+    this.definitions = Object.create(null);
+    this.purgeGen = 0;
+    this.workList = null;
+    this.disabledComputing = null;
+
+    exports.withContext(this, function() {
+      cx.protos.Object = new Obj(null, "Object.prototype");
+      cx.topScope = new Scope();
+      cx.topScope.name = "<top>";
+      cx.protos.Array = new Obj(true, "Array.prototype");
+      cx.protos.Function = new Obj(true, "Function.prototype");
+      cx.protos.RegExp = new Obj(true, "RegExp.prototype");
+      cx.protos.String = new Obj(true, "String.prototype");
+      cx.protos.Number = new Obj(true, "Number.prototype");
+      cx.protos.Boolean = new Obj(true, "Boolean.prototype");
+      cx.str = new Prim(cx.protos.String, "string");
+      cx.bool = new Prim(cx.protos.Boolean, "bool");
+      cx.num = new Prim(cx.protos.Number, "number");
+      cx.curOrigin = null;
+
+      if (defs) for (var i = 0; i < defs.length; ++i)
+        def.load(defs[i]);
+    });
+  };
+
+  var cx = null;
+  exports.cx = function() { return cx; };
+
+  exports.withContext = function(context, f) {
+    var old = cx;
+    cx = context;
+    try { return f(); }
+    finally { cx = old; }
+  };
+
+  exports.addOrigin = function(origin) {
+    if (cx.origins.indexOf(origin) < 0) cx.origins.push(origin);
+  };
+
+  var baseMaxWorkDepth = 20, reduceMaxWorkDepth = .0001;
+  function withWorklist(f) {
+    if (cx.workList) return f(cx.workList);
+
+    var list = [], depth = 0;
+    var add = cx.workList = function(type, target, weight) {
+      if (depth < baseMaxWorkDepth - reduceMaxWorkDepth * list.length)
+        list.push(type, target, weight, depth);
+    };
+    try {
+      var ret = f(add);
+      for (var i = 0; i < list.length; i += 4) {
+        depth = list[i + 3] + 1;
+        list[i + 1].addType(list[i], list[i + 2]);
+      }
+      return ret;
+    } finally {
+      cx.workList = null;
+    }
+  }
+
+  // SCOPES
+
+  var Scope = exports.Scope = function(prev) {
+    Obj.call(this, prev || true);
+    this.prev = prev;
+  };
+  Scope.prototype = extend(Obj.prototype, {
+    defVar: function(name, originNode) {
+      for (var s = this; ; s = s.proto) {
+        var found = s.props[name];
+        if (found) return found;
+        if (!s.prev) return s.defProp(name, originNode);
+      }
+    }
+  });
+
+  // RETVAL COMPUTATION HEURISTICS
+
+  function maybeInstantiate(scope, score) {
+    if (scope.fnType)
+      scope.fnType.instantiateScore = (scope.fnType.instantiateScore || 0) + score;
+  }
+
+  var NotSmaller = {};
+  function nodeSmallerThan(node, n) {
+    try {
+      walk.simple(node, {Expression: function() { if (--n <= 0) throw NotSmaller; }});
+      return true;
+    } catch(e) {
+      if (e == NotSmaller) return false;
+      throw e;
+    }
+  }
+
+  function maybeTagAsInstantiated(node, scope) {
+    var score = scope.fnType.instantiateScore;
+    if (!cx.disabledComputing && score && scope.fnType.args.length && nodeSmallerThan(node, score * 5)) {
+      maybeInstantiate(scope.prev, score / 2);
+      setFunctionInstantiated(node, scope);
+      return true;
+    } else {
+      scope.fnType.instantiateScore = null;
+    }
+  }
+
+  function setFunctionInstantiated(node, scope) {
+    var fn = scope.fnType;
+    // Disconnect the arg avals, so that we can add info to them without side effects
+    for (var i = 0; i < fn.args.length; ++i) fn.args[i] = new AVal;
+    fn.self = new AVal;
+    fn.computeRet = function(self, args) {
+      // Prevent recursion
+      return withDisabledComputing(fn, function() {
+        var oldOrigin = cx.curOrigin;
+        cx.curOrigin = fn.origin;
+        var scopeCopy = new Scope(scope.prev);
+        for (var v in scope.props) {
+          var local = scopeCopy.defProp(v);
+          for (var i = 0; i < args.length; ++i) if (fn.argNames[i] == v && i < args.length)
+            args[i].propagate(local);
+        }
+        var argNames = fn.argNames.length != args.length ? fn.argNames.slice(0, args.length) : fn.argNames;
+        while (argNames.length < args.length) argNames.push("?");
+        scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull);
+        if (fn.arguments) {
+          var argset = scopeCopy.fnType.arguments = new AVal;
+          scopeCopy.defProp("arguments").addType(new Arr(argset));
+          for (var i = 0; i < args.length; ++i) args[i].propagate(argset);
+        }
+        node.body.scope = scopeCopy;
+        walk.recursive(node.body, scopeCopy, null, scopeGatherer);
+        walk.recursive(node.body, scopeCopy, null, inferWrapper);
+        cx.curOrigin = oldOrigin;
+        return scopeCopy.fnType.retval;
+      });
+    };
+  }
+
+  function maybeTagAsGeneric(scope) {
+    var fn = scope.fnType, target = fn.retval;
+    if (target == ANull) return;
+    var targetInner, asArray;
+    if (!target.isEmpty() && (targetInner = target.getType()) instanceof Arr)
+      target = asArray = targetInner.getProp("<i>");
+
+    function explore(aval, path, depth) {
+      if (depth > 3 || !aval.forward) return;
+      for (var i = 0; i < aval.forward.length; ++i) {
+        var prop = aval.forward[i].propagatesTo();
+        if (!prop) continue;
+        var newPath = path, dest;
+        if (prop instanceof AVal) {
+          dest = prop;
+        } else if (prop.target instanceof AVal) {
+          newPath += prop.pathExt;
+          dest = prop.target;
+        } else continue;
+        if (dest == target) return newPath;
+        var found = explore(dest, newPath, depth + 1);
+        if (found) return found;
+      }
+    }
+
+    var foundPath = explore(fn.self, "!this", 0);
+    for (var i = 0; !foundPath && i < fn.args.length; ++i)
+      foundPath = explore(fn.args[i], "!" + i, 0);
+
+    if (foundPath) {
+      if (asArray) foundPath = "[" + foundPath + "]";
+      var p = new def.TypeParser(foundPath);
+      fn.computeRet = p.parseRetType();
+      fn.computeRetSource = foundPath;
+      return true;
+    }
+  }
+
+  // SCOPE GATHERING PASS
+
+  function addVar(scope, nameNode) {
+    var val = scope.defProp(nameNode.name, nameNode);
+    if (val.maybePurge) val.maybePurge = false;
+    return val;
+  }
+
+  var scopeGatherer = walk.make({
+    Function: function(node, scope, c) {
+      var inner = node.body.scope = new Scope(scope);
+      inner.node = node;
+      var argVals = [], argNames = [];
+      for (var i = 0; i < node.params.length; ++i) {
+        var param = node.params[i];
+        argNames.push(param.name);
+        argVals.push(addVar(inner, param));
+      }
+      inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull);
+      inner.fnType.originNode = node;
+      if (node.id) {
+        var decl = node.type == "FunctionDeclaration";
+        addVar(decl ? scope : inner, node.id);
+      }
+      c(node.body, inner, "ScopeBody");
+    },
+    TryStatement: function(node, scope, c) {
+      c(node.block, scope, "Statement");
+      if (node.handler) {
+        var v = addVar(scope, node.handler.param);
+        c(node.handler.body, scope, "ScopeBody");
+        var e5 = cx.definitions.ecma5;
+        if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR);
+      }
+      if (node.finalizer) c(node.finalizer, scope, "Statement");
+    },
+    VariableDeclaration: function(node, scope, c) {
+      for (var i = 0; i < node.declarations.length; ++i) {
+        var decl = node.declarations[i];
+        addVar(scope, decl.id);
+        if (decl.init) c(decl.init, scope, "Expression");
+      }
+    }
+  });
+
+  // CONSTRAINT GATHERING PASS
+
+  function propName(node, scope, c) {
+    var prop = node.property;
+    if (!node.computed) return prop.name;
+    if (prop.type == "Literal" && typeof prop.value == "string") return prop.value;
+    if (c) infer(prop, scope, c, ANull);
+    return "<i>";
+  }
+
+  function unopResultType(op) {
+    switch (op) {
+    case "+": case "-": case "~": return cx.num;
+    case "!": return cx.bool;
+    case "typeof": return cx.str;
+    case "void": case "delete": return ANull;
+    }
+  }
+  function binopIsBoolean(op) {
+    switch (op) {
+    case "==": case "!=": case "===": case "!==": case "<": case ">": case ">=": case "<=":
+    case "in": case "instanceof": return true;
+    }
+  }
+  function literalType(val) {
+    switch (typeof val) {
+    case "boolean": return cx.bool;
+    case "number": return cx.num;
+    case "string": return cx.str;
+    case "object":
+    case "function":
+      if (!val) return ANull;
+      return getInstance(cx.protos.RegExp);
+    }
+  }
+
+  function ret(f) {
+    return function(node, scope, c, out, name) {
+      var r = f(node, scope, c, name);
+      if (out) r.propagate(out);
+      return r;
+    };
+  }
+  function fill(f) {
+    return function(node, scope, c, out, name) {
+      if (!out) out = new AVal;
+      f(node, scope, c, out, name);
+      return out;
+    };
+  }
+
+  var inferExprVisitor = {
+    ArrayExpression: ret(function(node, scope, c) {
+      var eltval = new AVal;
+      for (var i = 0; i < node.elements.length; ++i) {
+        var elt = node.elements[i];
+        if (elt) infer(elt, scope, c, eltval);
+      }
+      return new Arr(eltval);
+    }),
+    ObjectExpression: ret(function(node, scope, c, name) {
+      var obj = node.objType = new Obj(true, name);
+      obj.originNode = node;
+
+      for (var i = 0; i < node.properties.length; ++i) {
+        var prop = node.properties[i], key = prop.key, name;
+        if (key.type == "Identifier") {
+          name = key.name;
+        } else if (typeof key.value == "string") {
+          name = key.value;
+        } else {
+          infer(prop.value, scope, c, ANull);
+          continue;
+        }
+        var val = obj.defProp(name, key);
+        val.initializer = true;
+        infer(prop.value, scope, c, val, name);
+      }
+      return obj;
+    }),
+    FunctionExpression: ret(function(node, scope, c, name) {
+      var inner = node.body.scope, fn = inner.fnType;
+      if (name && !fn.name) fn.name = name;
+      c(node.body, scope, "ScopeBody");
+      maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
+      if (node.id) inner.getProp(node.id.name).addType(fn);
+      return fn;
+    }),
+    SequenceExpression: ret(function(node, scope, c) {
+      for (var i = 0, l = node.expressions.length - 1; i < l; ++i)
+        infer(node.expressions[i], scope, c, ANull);
+      return infer(node.expressions[l], scope, c);
+    }),
+    UnaryExpression: ret(function(node, scope, c) {
+      infer(node.argument, scope, c, ANull);
+      return unopResultType(node.operator);
+    }),
+    UpdateExpression: ret(function(node, scope, c) {
+      infer(node.argument, scope, c, ANull);
+      return cx.num;
+    }),
+    BinaryExpression: ret(function(node, scope, c) {
+      if (node.operator == "+") {
+        var lhs = infer(node.left, scope, c);
+        var rhs = infer(node.right, scope, c);
+        if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
+        if (lhs.hasType(cx.num) && rhs.hasType(cx.num)) return cx.num;
+        var result = new AVal;
+        lhs.propagate(new IsAdded(rhs, result));
+        rhs.propagate(new IsAdded(lhs, result));
+        return result;
+      } else {
+        infer(node.left, scope, c, ANull);
+        infer(node.right, scope, c, ANull);
+        return binopIsBoolean(node.operator) ? cx.bool : cx.num;
+      }
+    }),
+    AssignmentExpression: ret(function(node, scope, c) {
+      var rhs, name, pName;
+      if (node.left.type == "MemberExpression") {
+        pName = propName(node.left, scope, c);
+        if (node.left.object.type == "Identifier")
+          name = node.left.object.name + "." + pName;
+      } else {
+        name = node.left.name;
+      }
+
+      if (node.operator != "=" && node.operator != "+=") {
+        infer(node.right, scope, c, ANull);
+        rhs = cx.num;
+      } else {
+        rhs = infer(node.right, scope, c, null, name);
+      }
+
+      if (node.left.type == "MemberExpression") {
+        var obj = infer(node.left.object, scope, c);
+        if (pName == "prototype") maybeInstantiate(scope, 20);
+        if (pName == "<i>") {
+          // This is a hack to recognize for/in loops that copy
+          // properties, and do the copying ourselves, insofar as we
+          // manage, because such loops tend to be relevant for type
+          // information.
+          var v = node.left.property.name, local = scope.props[v], over = local && local.iteratesOver;
+          if (over) {
+            maybeInstantiate(scope, 20);
+            var fromRight = node.right.type == "MemberExpression" && node.right.computed && node.right.property.name == v;
+            over.forAllProps(function(prop, val, local) {
+              if (local && prop != "prototype" && prop != "<i>")
+                obj.propagate(new PropHasSubset(prop, fromRight ? val : ANull));
+            });
+            return rhs;
+          }
+        }
+        obj.propagate(new PropHasSubset(pName, rhs, node.left.property));
+      } else { // Identifier
+        var v = scope.defVar(node.left.name, node.left);
+        if (v.maybePurge) v.maybePurge = false;
+        rhs.propagate(v);
+      }
+      return rhs;
+    }),
+    LogicalExpression: fill(function(node, scope, c, out) {
+      infer(node.left, scope, c, out);
+      infer(node.right, scope, c, out);
+    }),
+    ConditionalExpression: fill(function(node, scope, c, out) {
+      infer(node.test, scope, c, ANull);
+      infer(node.consequent, scope, c, out);
+      infer(node.alternate, scope, c, out);
+    }),
+    NewExpression: fill(function(node, scope, c, out, name) {
+      if (node.callee.type == "Identifier" && node.callee.name in scope.props)
+        maybeInstantiate(scope, 20);
+
+      for (var i = 0, args = []; i < node.arguments.length; ++i)
+        args.push(infer(node.arguments[i], scope, c));
+      var callee = infer(node.callee, scope, c);
+      var self = new AVal;
+      callee.propagate(new IsCtor(self, name && /\.prototype$/.test(name)));
+      self.propagate(out, WG_NEW_INSTANCE);
+      callee.propagate(new IsCallee(self, args, node.arguments, new IfObj(out)));
+    }),
+    CallExpression: fill(function(node, scope, c, out) {
+      for (var i = 0, args = []; i < node.arguments.length; ++i)
+        args.push(infer(node.arguments[i], scope, c));
+      if (node.callee.type == "MemberExpression") {
+        var self = infer(node.callee.object, scope, c);
+        var pName = propName(node.callee, scope, c);
+        if ((pName == "call" || pName == "apply") &&
+            scope.fnType && scope.fnType.args.indexOf(self) > -1)
+          maybeInstantiate(scope, 30);
+        self.propagate(new HasMethodCall(pName, args, node.arguments, out));
+      } else {
+        var callee = infer(node.callee, scope, c);
+        if (scope.fnType && scope.fnType.args.indexOf(callee) > -1)
+          maybeInstantiate(scope, 30);
+        var knownFn = callee.getFunctionType();
+        if (knownFn && knownFn.instantiateScore && scope.fnType)
+          maybeInstantiate(scope, knownFn.instantiateScore / 5);
+        callee.propagate(new IsCallee(cx.topScope, args, node.arguments, out));
+      }
+    }),
+    MemberExpression: fill(function(node, scope, c, out) {
+      var name = propName(node, scope);
+      var obj = infer(node.object, scope, c);
+      var prop = obj.getProp(name);
+      if (name == "<i>") {
+        var propType = infer(node.property, scope, c);
+        if (!propType.hasType(cx.num))
+          return prop.propagate(out, WG_MULTI_MEMBER);
+      }
+      prop.propagate(out);
+    }),
+    Identifier: ret(function(node, scope) {
+      if (node.name == "arguments" && scope.fnType && !(node.name in scope.props))
+        scope.defProp(node.name, scope.fnType.originNode)
+          .addType(new Arr(scope.fnType.arguments = new AVal));
+      return scope.getProp(node.name);
+    }),
+    ThisExpression: ret(function(_node, scope) {
+      return scope.fnType ? scope.fnType.self : cx.topScope;
+    }),
+    Literal: ret(function(node) {
+      return literalType(node.value);
+    })
+  };
+
+  function infer(node, scope, c, out, name) {
+    return inferExprVisitor[node.type](node, scope, c, out, name);
+  }
+
+  var inferWrapper = walk.make({
+    Expression: function(node, scope, c) {
+      infer(node, scope, c, ANull);
+    },
+
+    FunctionDeclaration: function(node, scope, c) {
+      var inner = node.body.scope, fn = inner.fnType;
+      c(node.body, scope, "ScopeBody");
+      maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
+      var prop = scope.getProp(node.id.name);
+      prop.addType(fn);
+    },
+
+    VariableDeclaration: function(node, scope, c) {
+      for (var i = 0; i < node.declarations.length; ++i) {
+        var decl = node.declarations[i], prop = scope.getProp(decl.id.name);
+        if (decl.init)
+          infer(decl.init, scope, c, prop, decl.id.name);
+      }
+    },
+
+    ReturnStatement: function(node, scope, c) {
+      if (node.argument && scope.fnType) {
+        if (scope.fnType.retval == ANull) scope.fnType.retval = new AVal;
+        infer(node.argument, scope, c, scope.fnType.retval);
+      }
+    },
+
+    ForInStatement: function(node, scope, c) {
+      var source = infer(node.right, scope, c);
+      if ((node.right.type == "Identifier" && node.right.name in scope.props) ||
+          (node.right.type == "MemberExpression" && node.right.property.name == "prototype")) {
+        maybeInstantiate(scope, 5);
+        var varName;
+        if (node.left.type == "Identifier") {
+          varName = node.left.name;
+        } else if (node.left.type == "VariableDeclaration") {
+          varName = node.left.declarations[0].id.name;
+        }
+        if (varName && varName in scope.props)
+          scope.getProp(varName).iteratesOver = source;
+      }
+      c(node.body, scope, "Statement");
+    },
+
+    ScopeBody: function(node, scope, c) { c(node, node.scope || scope); }
+  });
+
+  // PARSING
+
+  function runPasses(passes, pass) {
+    var arr = passes && passes[pass];
+    var args = Array.prototype.slice.call(arguments, 2);
+    if (arr) for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+  }
+
+  var parse = exports.parse = function(text, passes, options) {
+    var ast;
+    try { ast = acorn.parse(text, options); }
+    catch(e) { ast = acorn_loose.parse_dammit(text, options); }
+    runPasses(passes, "postParse", ast, text);
+    return ast;
+  };
+
+  // ANALYSIS INTERFACE
+
+  exports.analyze = function(ast, name, scope, passes) {
+    if (typeof ast == "string") ast = parse(ast);
+
+    if (!name) name = "file#" + cx.origins.length;
+    exports.addOrigin(cx.curOrigin = name);
+
+    if (!scope) scope = cx.topScope;
+    walk.recursive(ast, scope, null, scopeGatherer);
+    runPasses(passes, "preInfer", ast, scope);
+    walk.recursive(ast, scope, null, inferWrapper);
+    runPasses(passes, "postInfer", ast, scope);
+
+    cx.curOrigin = null;
+  };
+
+  // PURGING
+
+  exports.purgeTypes = function(origins, start, end) {
+    var test = makePredicate(origins, start, end);
+    ++cx.purgeGen;
+    cx.topScope.purge(test);
+    for (var prop in cx.props) {
+      var list = cx.props[prop];
+      for (var i = 0; i < list.length; ++i) {
+        var obj = list[i], av = obj.props[prop];
+        if (!av || test(av, av.originNode)) list.splice(i--, 1);
+      }
+      if (!list.length) delete cx.props[prop];
+    }
+  };
+
+  function makePredicate(origins, start, end) {
+    var arr = Array.isArray(origins);
+    if (arr && origins.length == 1) { origins = origins[0]; arr = false; }
+    if (arr) {
+      if (end == null) return function(n) { return origins.indexOf(n.origin) > -1; };
+      return function(n, pos) { return pos && pos.start >= start && pos.end <= end && origins.indexOf(n.origin) > -1; };
+    } else {
+      if (end == null) return function(n) { return n.origin == origins; };
+      return function(n, pos) { return pos && pos.start >= start && pos.end <= end && n.origin == origins; };
+    }
+  }
+
+  AVal.prototype.purge = function(test) {
+    if (this.purgeGen == cx.purgeGen) return;
+    this.purgeGen = cx.purgeGen;
+    for (var i = 0; i < this.types.length; ++i) {
+      var type = this.types[i];
+      if (test(type, type.originNode))
+        this.types.splice(i--, 1);
+      else
+        type.purge(test);
+    }
+    if (this.forward) for (var i = 0; i < this.forward.length; ++i) {
+      var f = this.forward[i];
+      if (test(f)) {
+        this.forward.splice(i--, 1);
+        if (this.props) this.props = null;
+      } else if (f.purge) {
+        f.purge(test);
+      }
+    }
+  };
+  ANull.purge = function() {};
+  Obj.prototype.purge = function(test) {
+    if (this.purgeGen == cx.purgeGen) return true;
+    this.purgeGen = cx.purgeGen;
+    for (var p in this.props) {
+      var av = this.props[p];
+      if (test(av, av.originNode))
+        this.removeProp(p);
+      av.purge(test);
+    }
+  };
+  Fn.prototype.purge = function(test) {
+    if (Obj.prototype.purge.call(this, test)) return;
+    this.self.purge(test);
+    this.retval.purge(test);
+    for (var i = 0; i < this.args.length; ++i) this.args[i].purge(test);
+  };
+
+  exports.markVariablesDefinedBy = function(scope, origins, start, end) {
+    var test = makePredicate(origins, start, end);
+    for (var s = scope; s; s = s.prev) for (var p in s.props) {
+      var prop = s.props[p];
+      if (test(prop, prop.originNode)) {
+        prop.maybePurge = true;
+        if (start == null && prop.originNode) prop.originNode = null;
+      }
+    }
+  };
+
+  exports.purgeMarkedVariables = function(scope) {
+    for (var s = scope; s; s = s.prev) for (var p in s.props)
+      if (s.props[p].maybePurge) delete s.props[p];
+  };
+
+  // EXPRESSION TYPE DETERMINATION
+
+  function findByPropertyName(name) {
+    guessing = true;
+    var found = objsWithProp(name);
+    if (found) for (var i = 0; i < found.length; ++i) {
+      var val = found[i].getProp(name);
+      if (!val.isEmpty()) return val;
+    }
+    return ANull;
+  }
+
+  var typeFinder = {
+    ArrayExpression: function(node, scope) {
+      var eltval = new AVal;
+      for (var i = 0; i < node.elements.length; ++i) {
+        var elt = node.elements[i];
+        if (elt) findType(elt, scope).propagate(eltval);
+      }
+      return new Arr(eltval);
+    },
+    ObjectExpression: function(node) {
+      return node.objType;
+    },
+    FunctionExpression: function(node) {
+      return node.body.scope.fnType;
+    },
+    SequenceExpression: function(node, scope) {
+      return findType(node.expressions[node.expressions.length-1], scope);
+    },
+    UnaryExpression: function(node) {
+      return unopResultType(node.operator);
+    },
+    UpdateExpression: function() {
+      return cx.num;
+    },
+    BinaryExpression: function(node, scope) {
+      if (binopIsBoolean(node.operator)) return cx.bool;
+      if (node.operator == "+") {
+        var lhs = findType(node.left, scope);
+        var rhs = findType(node.right, scope);
+        if (lhs.hasType(cx.str) || rhs.hasType(cx.str)) return cx.str;
+      }
+      return cx.num;
+    },
+    AssignmentExpression: function(node, scope) {
+      return findType(node.right, scope);
+    },
+    LogicalExpression: function(node, scope) {
+      var lhs = findType(node.left, scope);
+      return lhs.isEmpty() ? findType(node.right, scope) : lhs;
+    },
+    ConditionalExpression: function(node, scope) {
+      var lhs = findType(node.consequent, scope);
+      return lhs.isEmpty() ? findType(node.alternate, scope) : lhs;
+    },
+    NewExpression: function(node, scope) {
+      var f = findType(node.callee, scope).getFunctionType();
+      var proto = f && f.getProp("prototype").getType();
+      if (!proto) return ANull;
+      return getInstance(proto, f);
+    },
+    CallExpression: function(node, scope) {
+      var f = findType(node.callee, scope).getFunctionType();
+      if (!f) return ANull;
+      if (f.computeRet) {
+        for (var i = 0, args = []; i < node.arguments.length; ++i)
+          args.push(findType(node.arguments[i], scope));
+        var self = ANull;
+        if (node.callee.type == "MemberExpression")
+          self = findType(node.callee.object, scope);
+        return f.computeRet(self, args, node.arguments);
+      } else {
+        return f.retval;
+      }
+    },
+    MemberExpression: function(node, scope) {
+      var propN = propName(node, scope), obj = findType(node.object, scope).getType();
+      if (obj) return obj.getProp(propN);
+      if (propN == "<i>") return ANull;
+      return findByPropertyName(propN);
+    },
+    Identifier: function(node, scope) {
+      return scope.hasProp(node.name) || ANull;
+    },
+    ThisExpression: function(_node, scope) {
+      return scope.fnType ? scope.fnType.self : cx.topScope;
+    },
+    Literal: function(node) {
+      return literalType(node.value);
+    }
+  };
+
+  function findType(node, scope) {
+    var found = typeFinder[node.type](node, scope);
+    return found;
+  }
+
+  var searchVisitor = exports.searchVisitor = walk.make({
+    Function: function(node, _st, c) {
+      var scope = node.body.scope;
+      if (node.id) c(node.id, scope);
+      for (var i = 0; i < node.params.length; ++i)
+        c(node.params[i], scope);
+      c(node.body, scope, "ScopeBody");
+    },
+    TryStatement: function(node, st, c) {
+      if (node.handler)
+        c(node.handler.param, st);
+      walk.base.TryStatement(node, st, c);
+    },
+    VariableDeclaration: function(node, st, c) {
+      for (var i = 0; i < node.declarations.length; ++i) {
+        var decl = node.declarations[i];
+        c(decl.id, st);
+        if (decl.init) c(decl.init, st, "Expression");
+      }
+    }
+  });
+  exports.fullVisitor = walk.make({
+    MemberExpression: function(node, st, c) {
+      c(node.object, st, "Expression");
+      c(node.property, st, node.computed ? "Expression" : null);
+    },
+    ObjectExpression: function(node, st, c) {
+      for (var i = 0; i < node.properties.length; ++i) {
+        c(node.properties[i].value, st, "Expression");
+        c(node.properties[i].key, st);
+      }
+    }
+  }, searchVisitor);
+
+  exports.findExpressionAt = function(ast, start, end, defaultScope, filter) {
+    var test = filter || function(_t, node) {return typeFinder.hasOwnProperty(node.type);};
+    return walk.findNodeAt(ast, start, end, test, searchVisitor, defaultScope || cx.topScope);
+  };
+
+  exports.findExpressionAround = function(ast, start, end, defaultScope, filter) {
+    var test = filter || function(_t, node) {
+      if (start != null && node.start > start) return false;
+      return typeFinder.hasOwnProperty(node.type);
+    };
+    return walk.findNodeAround(ast, end, test, searchVisitor, defaultScope || cx.topScope);
+  };
+
+  exports.expressionType = function(found) {
+    return findType(found.node, found.state);
+  };
+
+  // Flag used to indicate that some wild guessing was used to produce
+  // a type or set of completions.
+  var guessing = false;
+
+  exports.resetGuessing = function(val) { guessing = val; };
+  exports.didGuess = function() { return guessing; };
+
+  exports.forAllPropertiesOf = function(type, f) {
+    type.gatherProperties(f, 0);
+  };
+
+  var refFindWalker = walk.make({}, searchVisitor);
+
+  exports.findRefs = function(ast, baseScope, name, refScope, f) {
+    refFindWalker.Identifier = function(node, scope) {
+      if (node.name != name) return;
+      for (var s = scope; s; s = s.prev) {
+        if (s == refScope) f(node, scope);
+        if (name in s.props) return;
+      }
+    };
+    walk.recursive(ast, baseScope, null, refFindWalker);
+  };
+
+  var simpleWalker = walk.make({
+    Function: function(node, _st, c) { c(node.body, node.body.scope, "ScopeBody"); }
+  });
+
+  exports.findPropRefs = function(ast, scope, objType, propName, f) {
+    walk.simple(ast, {
+      MemberExpression: function(node, scope) {
+        if (node.computed || node.property.name != propName) return;
+        if (findType(node.object, scope).getType() == objType) f(node.property);
+      },
+      ObjectExpression: function(node, scope) {
+        if (findType(node, scope).getType() != objType) return;
+        for (var i = 0; i < node.properties.length; ++i)
+          if (node.properties[i].key.name == propName) f(node.properties[i].key);
+      }
+    }, simpleWalker, scope);
+  };
+
+  // LOCAL-VARIABLE QUERIES
+
+  var scopeAt = exports.scopeAt = function(ast, pos, defaultScope) {
+    var found = walk.findNodeAround(ast, pos, function(tp, node) {
+      return tp == "ScopeBody" && node.scope;
+    });
+    if (found) return found.node.scope;
+    else return defaultScope || cx.topScope;
+  };
+
+  exports.forAllLocalsAt = function(ast, pos, defaultScope, f) {
+    var scope = scopeAt(ast, pos, defaultScope);
+    scope.gatherProperties(f, 0);
+  };
+
+  // INIT DEF MODULE
+
+  // Delayed initialization because of cyclic dependencies.
+  def = exports.def = def.init({}, exports);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+JS_MODULES_PATH = 'modules/devtools/tern'
+
+EXTRA_JS_MODULES += [
+    'browser.js',
+    'comment.js',
+    'condense.js',
+    'def.js',
+    'ecma5.js',
+    'infer.js',
+    'signal.js',
+    'tern.js',
+]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/signal.js
@@ -0,0 +1,26 @@
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports);
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports"], mod);
+  mod((self.tern || (self.tern = {})).signal = {}); // Plain browser env
+})(function(exports) {
+  function on(type, f) {
+    var handlers = this._handlers || (this._handlers = Object.create(null));
+    (handlers[type] || (handlers[type] = [])).push(f);
+  }
+  function off(type, f) {
+    var arr = this._handlers && this._handlers[type];
+    if (arr) for (var i = 0; i < arr.length; ++i)
+      if (arr[i] == f) { arr.splice(i, 1); break; }
+  }
+  function signal(type, a1, a2, a3, a4) {
+    var arr = this._handlers && this._handlers[type];
+    if (arr) for (var i = 0; i < arr.length; ++i) arr[i].call(this, a1, a2, a3, a4);
+  }
+
+  exports.mixin = function(obj) {
+    obj.on = on; obj.off = off; obj.signal = signal;
+    return obj;
+  };
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tern/tern.js
@@ -0,0 +1,814 @@
+// The Tern server object
+
+// A server is a stateful object that manages the analysis for a
+// project, and defines an interface for querying the code in the
+// project.
+
+(function(mod) {
+  if (typeof exports == "object" && typeof module == "object") // CommonJS
+    return mod(exports, require("./infer"), require("./signal"),
+               require("acorn/acorn"), require("acorn/util/walk"));
+  if (typeof define == "function" && define.amd) // AMD
+    return define(["exports", "./infer", "./signal", "acorn/acorn", "acorn/util/walk"], mod);
+  mod(self.tern || (self.tern = {}), tern, tern.signal, acorn, acorn.walk); // Plain browser env
+})(function(exports, infer, signal, acorn, walk) {
+  "use strict";
+
+  var plugins = Object.create(null);
+  exports.registerPlugin = function(name, init) { plugins[name] = init; };
+
+  var defaultOptions = {
+    debug: false,
+    async: false,
+    getFile: function(_f, c) { if (this.async) c(null, null); },
+    defs: [],
+    plugins: {},
+    fetchTimeout: 1000
+  };
+
+  var queryTypes = {
+    completions: {
+      takesFile: true,
+      run: findCompletions
+    },
+    properties: {
+      run: findProperties
+    },
+    type: {
+      takesFile: true,
+      run: findTypeAt
+    },
+    documentation: {
+      takesFile: true,
+      run: findDocs
+    },
+    definition: {
+      takesFile: true,
+      run: findDef
+    },
+    refs: {
+      takesFile: true,
+      fullFile: true,
+      run: findRefs
+    },
+    rename: {
+      takesFile: true,
+      fullFile: true,
+      run: buildRename
+    },
+    files: {
+      run: listFiles
+    }
+  };
+
+  exports.defineQueryType = function(name, desc) { queryTypes[name] = desc; };
+
+  function File(name) {
+    this.name = name;
+    this.scope = this.text = this.ast = this.lineOffsets = null;
+  }
+  File.prototype.asLineChar = function(pos) { return asLineChar(this, pos); };
+
+  function updateText(file, text, srv) {
+    file.text = text;
+    file.ast = infer.parse(text, srv.passes, {directSourceFile: file});
+    file.lineOffsets = null;
+  }
+
+  var Server = exports.Server = function(options) {
+    this.cx = null;
+    this.options = options || {};
+    for (var o in defaultOptions) if (!options.hasOwnProperty(o))
+      options[o] = defaultOptions[o];
+
+    this.handlers = Object.create(null);
+    this.files = [];
+    this.uses = 0;
+    this.pending = 0;
+    this.asyncError = null;
+    this.passes = Object.create(null);
+
+    this.defs = options.defs.slice(0);
+    for (var plugin in options.plugins) if (options.plugins.hasOwnProperty(plugin) && plugin in plugins) {
+      var init = plugins[plugin](this, options.plugins[plugin]);
+      if (init && init.defs) {
+        if (init.loadFirst) this.defs.unshift(init.defs);
+        else this.defs.push(init.defs);
+      }
+      if (init && init.passes) for (var type in init.passes) if (init.passes.hasOwnProperty(type))
+        (this.passes[type] || (this.passes[type] = [])).push(init.passes[type]);
+    }
+
+    this.reset();
+  };
+  Server.prototype = signal.mixin({
+    addFile: function(name, /*optional*/ text) {
+      ensureFile(this, name, text);
+    },
+    delFile: function(name) {
+      for (var i = 0, f; i < this.files.length; ++i) if ((f = this.files[i]).name == name) {
+        clearFile(this, f);
+        this.files.splice(i--, 1);
+        return;
+      }
+    },
+    reset: function() {
+      this.signal("reset");
+      this.cx = new infer.Context(this.defs, this);
+      this.uses = 0;
+      for (var i = 0; i < this.files.length; ++i) {
+        var file = this.files[i];
+        file.scope = null;
+      }
+    },
+
+    request: function(doc, c) {
+      var inv = invalidDoc(doc);
+      if (inv) return c(inv);
+
+      var self = this;
+      doRequest(this, doc, function(err, data) {
+        c(err, data);
+        if (self.uses > 40) {
+          self.reset();
+          analyzeAll(self, function(){});
+        }
+      });
+    },
+
+    findFile: function(name) {
+      return findFile(this.files, name);
+    },
+
+    flush: function(c) {
+      var cx = this.cx;
+      analyzeAll(this, function(err) {
+        if (err) return c(err);
+        infer.withContext(cx, c);
+      });
+    },
+
+    startAsyncAction: function() {
+      ++this.pending;
+    },
+    finishAsyncAction: function(err) {
+      if (err) this.asyncError = err;
+      if (--this.pending == 0) this.signal("everythingFetched");
+    }
+  });
+
+  function doRequest(srv, doc, c) {
+    if (doc.query && !queryTypes.hasOwnProperty(doc.query.type))
+      return c("No query type '" + doc.query.type + "' defined");
+
+    var query = doc.query;
+    // Respond as soon as possible when this just uploads files
+    if (!query) c(null, {});
+
+    var files = doc.files || [];
+    if (files.length) ++srv.uses;
+    for (var i = 0; i < files.length; ++i) {
+      var file = files[i];
+      ensureFile(srv, file.name, file.type == "full" ? file.text : null);
+    }
+
+    if (!query) {
+      analyzeAll(srv, function(){});
+      return;
+    }
+
+    var queryType = queryTypes[query.type];
+    if (queryType.takesFile) {
+      if (typeof query.file != "string") return c(".query.file must be a string");
+      if (!/^#/.test(query.file)) ensureFile(srv, query.file);
+    }
+
+    analyzeAll(srv, function(err) {
+      if (err) return c(err);
+      var file = queryType.takesFile && resolveFile(srv, files, query.file);
+      if (queryType.fullFile && file.type == "part")
+        return c("Can't run a " + query.type + " query on a file fragment");
+
+      infer.withContext(srv.cx, function() {
+        var result;
+        try {
+          result = queryType.run(srv, query, file);
+        } catch (e) {
+          if (srv.options.debug && e.name != "TernError") console.error(e.stack);
+          return c(e);
+        }
+        c(null, result);
+      });
+    });
+  }
+
+  function analyzeFile(srv, file) {