merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 17 Dec 2015 11:57:06 +0100
changeset 276777 f143af51f6e35932927b8ccac2509facbbe7b539
parent 276723 77ab820ce477438af4b32cea83f82fd55744cec7 (current diff)
parent 276776 1afbb237019de001797d566cd33dd8fa5a9395d1 (diff)
child 276778 0711218a018d912036f7d3be2ae2649e213cfb85
push id69273
push usercbook@mozilla.com
push dateThu, 17 Dec 2015 11:03:52 +0000
treeherdermozilla-inbound@565d7ae436ba [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone46.0a1
first release with
nightly linux32
f143af51f6e3 / 46.0a1 / 20151217030207 / files
nightly linux64
f143af51f6e3 / 46.0a1 / 20151217030207 / files
nightly mac
f143af51f6e3 / 46.0a1 / 20151217030207 / files
nightly win32
f143af51f6e3 / 46.0a1 / 20151217030207 / files
nightly win64
f143af51f6e3 / 46.0a1 / 20151217030207 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
devtools/client/sourceeditor/tern/comment.js
devtools/client/sourceeditor/tern/condense.js
devtools/client/sourceeditor/tern/def.js
devtools/client/sourceeditor/tern/infer.js
devtools/client/sourceeditor/tern/signal.js
devtools/client/sourceeditor/tern/tern.js
mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPane.java
mobile/android/base/resources/layout/firstrun_pane.xml
toolkit/mozapps/extensions/test/browser/browser_debug_button.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -190,16 +190,17 @@ toolkit/modules/tests/xpcshell/test_task
 
 # Not yet updated
 toolkit/components/osfile/**
 toolkit/components/passwordmgr/**
 toolkit/components/places/**
 
 # Uses preprocessing
 toolkit/content/contentAreaUtils.js
+toolkit/content/widgets/videocontrols.xml
 toolkit/components/jsdownloads/src/DownloadIntegration.jsm
 toolkit/components/search/nsSearchService.js
 toolkit/components/url-classifier/**
 toolkit/components/urlformatter/nsURLFormatter.js
 toolkit/identity/FirefoxAccounts.jsm
 toolkit/modules/AppConstants.jsm
 toolkit/modules/SessionRecorder.jsm
 toolkit/mozapps/downloads/nsHelperAppDlg.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1318,18 +1318,16 @@ sticky_pref("lightweightThemes.selectedT
 #endif
 
 // Whether the character encoding menu is under the main Firefox button. This
 // preference is a string so that localizers can alter it.
 pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
 
 // Allow using tab-modal prompts when possible.
 pref("prompts.tab_modal.enabled", true);
-// Whether the Panorama should animate going in/out of tabs
-pref("browser.panorama.animate_zoom", true);
 
 // Activates preloading of the new tab url.
 pref("browser.newtab.preload", true);
 
 // Remembers if the about:newtab intro has been shown
 pref("browser.newtabpage.introShown", false);
 
 // Toggles the content of 'about:newtab'. Shows the grid when enabled.
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -499,21 +499,16 @@
                         accesskey="&addons.accesskey;"
                         key="key_openAddons"
                         command="Tools:Addons"/>
               <menuitem id="menu_openApps"
                         label="&webapps.label;"
                         accesskey="&webapps.accesskey;"
                         oncommand="BrowserOpenApps();"/>
 
-              <menuitem id="menu_openLoop"
-                        label="&loopMenuItem.label;"
-                        accesskey = "&loopMenuItem.accesskey;"
-                        oncommand="LoopUI.togglePanel();"/>
-
               <!-- only one of sync-setup, sync-syncnowitem or sync-reauthitem will be showing at once -->
               <menuitem id="sync-setup"
                         label="&syncSignIn.label;"
                         accesskey="&syncSignIn.accesskey;"
                         observes="sync-setup-state"
                         oncommand="gSyncUI.openSetup(null, 'menubar')"/>
               <menuitem id="sync-syncnowitem"
                         label="&syncSyncNowItem.label;"
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -329,17 +329,17 @@ var AboutReaderListener = {
   /**
    * NB: this function will update the state of the reader button asynchronously
    * after the next mozAfterPaint call (assuming reader mode is enabled and
    * this is a suitable document). Calling it on things which won't be
    * painted is not going to work.
    */
   updateReaderButton: function(forceNonArticle) {
     if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
-        !(content.document instanceof content.HTMLDocument) ||
+        !content || !(content.document instanceof content.HTMLDocument) ||
         content.document.mozSyntheticDocument) {
       return;
     }
 
     this.scheduleReadabilityCheckPostPaint(forceNonArticle);
   },
 
   cancelPotentialPendingReadabilityCheck: function() {
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -649,17 +649,19 @@
 
                   // If the browser is loading it must not be crashed anymore
                   this.mTab.removeAttribute("crashed");
                 }
 
                 if (this._shouldShowProgress(aRequest)) {
                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                     this.mTab.setAttribute("busy", "true");
-                    if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
+
+                    if (aWebProgress.isTopLevel &&
+                        !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
                       this.mTabBrowser.setTabTitleLoading(this.mTab);
                   }
 
                   if (this.mTab.selected)
                     this.mTabBrowser.mIsBusy = true;
                 }
               }
               else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
--- a/browser/base/content/test/general/browser_urlbarSearchSuggestions.js
+++ b/browser/base/content/test/general/browser_urlbarSearchSuggestions.js
@@ -17,26 +17,28 @@ add_task(function* prepare() {
 
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
   });
 });
 
 add_task(function* clickSuggestion() {
+  gBrowser.selectedTab = gBrowser.addTab();
   gURLBar.focus();
   yield promiseAutocompleteResultPopup("foo");
   let [idx, suggestion] = yield promiseFirstSuggestion();
   let item = gURLBar.popup.richlistbox.getItemAtIndex(idx);
   let loadPromise = promiseTabLoaded(gBrowser.selectedTab);
   item.click();
   yield loadPromise;
   let uri = Services.search.currentEngine.getSubmission(suggestion).uri;
   Assert.ok(uri.equals(gBrowser.currentURI),
             "The search results page should have loaded");
+  gBrowser.removeTab(gBrowser.selectedTab);
 });
 
 function getFirstSuggestion() {
   let controller = gURLBar.popup.input.controller;
   let matchCount = controller.matchCount;
   let present = false;
   for (let i = 0; i < matchCount; i++) {
     let url = controller.getValueAt(i);
--- a/browser/base/content/test/general/browser_urlbarSearchTelemetry.js
+++ b/browser/base/content/test/general/browser_urlbarSearchTelemetry.js
@@ -22,36 +22,40 @@ add_task(function* prepare() {
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
   });
 });
 
 add_task(function* heuristicResult() {
   yield compareCounts(function* () {
+    gBrowser.selectedTab = gBrowser.addTab();
     yield promiseAutocompleteResultPopup("heuristicResult");
     let action = getActionAtIndex(0);
     Assert.ok(!!action, "there should be an action at index 0");
     Assert.equal(action.type, "searchengine", "type should be searchengine");
     let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
     let loadPromise = promiseTabLoaded(gBrowser.selectedTab);
     item.click();
     yield loadPromise;
+    gBrowser.removeTab(gBrowser.selectedTab);
   });
 });
 
 add_task(function* searchSuggestion() {
   yield compareCounts(function* () {
+    gBrowser.selectedTab = gBrowser.addTab();
     yield promiseAutocompleteResultPopup("searchSuggestion");
     let idx = getFirstSuggestionIndex();
     Assert.ok(idx >= 0, "there should be a first suggestion");
     let item = gURLBar.popup.richlistbox.getItemAtIndex(idx);
     let loadPromise = promiseTabLoaded(gBrowser.selectedTab);
     item.click();
     yield loadPromise;
+    gBrowser.removeTab(gBrowser.selectedTab);
   });
 });
 
 /**
  * This does three things: gets current telemetry/FHR counts, calls
  * clickCallback, gets telemetry/FHR counts again to compare them to the old
  * counts.
  *
--- a/browser/base/content/test/general/searchSuggestionEngine.xml
+++ b/browser/base/content/test/general/searchSuggestionEngine.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
 <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
-<Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}" rel="searchform"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
 </SearchPlugin>
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -30,16 +30,17 @@ var WindowListener = {
    *
    * @param {Object} window The window to inject the integration into.
    */
   setupBrowserUI: function(window) {
     let document = window.document;
     let gBrowser = window.gBrowser;
     let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
     let FileReader = window.FileReader;
+    let menuItem = null;
 
     // the "exported" symbols
     var LoopUI = {
       /**
        * @var {XULWidgetSingleWrapper} toolbarButton Getter for the Loop toolbarbutton
        *                                             instance for this window. This should
        *                                             not be used in the hidden window.
        */
@@ -65,30 +66,16 @@ var WindowListener = {
         if (browser) {
           delete this.browser;
           this.browser = browser;
         }
         return browser;
       },
 
       /**
-       * @var {String|null} selectedTab Getter for the name of the currently selected
-       *                                tab inside the Loop panel. Will be NULL if
-       *                                the panel hasn't loaded yet.
-       */
-      get selectedTab() {
-        if (!this.browser) {
-          return null;
-        }
-
-        let selectedTab = this.browser.contentDocument.querySelector(".tab-view > .selected");
-        return selectedTab && selectedTab.getAttribute("data-tab-name");
-      },
-
-      /**
        * @return {Promise}
        */
       promiseDocumentVisible(aDocument) {
         if (!aDocument.hidden) {
           return Promise.resolve(aDocument);
         }
 
         return new Promise((resolve) => {
@@ -273,32 +260,63 @@ var WindowListener = {
         this.MozLoopService.initialize().catch(ex => {
           if (!ex.message ||
               (!ex.message.contains("not enabled") &&
                !ex.message.contains("not needed"))) {
             console.error(ex);
           }
         });
 
+        this.addMenuItem();
+
         // Don't do the rest if this is for the hidden window - we don't
         // have a toolbar there.
         if (window == Services.appShell.hiddenDOMWindow) {
           return;
         }
 
         // Cleanup when the window unloads.
         window.addEventListener("unload", () => {
           Services.obs.removeObserver(this, "loop-status-changed");
         });
 
         Services.obs.addObserver(this, "loop-status-changed", false);
 
         this.updateToolbarState();
       },
 
+      /**
+       * Adds a menu item to the browsers' Tools menu that open the Loop panel
+       * when selected.
+       */
+      addMenuItem: function() {
+        let menu = document.getElementById("menu_ToolsPopup");
+        if (!menu || menuItem) {
+          return;
+        }
+
+        menuItem = document.createElementNS(kNSXUL, "menuitem");
+        menuItem.setAttribute("id", "menu_openLoop");
+        menuItem.setAttribute("label", this._getString("loopMenuItem_label"));
+        menuItem.setAttribute("accesskey", this._getString("loopMenuItem_accesskey"));
+
+        menuItem.addEventListener("command", () => this.togglePanel());
+
+        menu.insertBefore(menuItem, document.getElementById("sync-setup"));
+      },
+
+      /**
+       * Removes the menu item from the browsers' Tools menu.
+       */
+      removeMenuItem: function() {
+        if (menuItem) {
+          menuItem.parentNode.removeChild(menuItem);
+        }
+      },
+
       // Implements nsIObserver
       observe: function(subject, topic, data) {
         if (topic != "loop-status-changed") {
           return;
         }
         this.updateToolbarState(data);
       },
 
@@ -669,17 +687,20 @@ var WindowListener = {
     window.LoopUI = LoopUI;
   },
 
   tearDownBrowserUI: function(window) {
     let document = window.document;
 
     // Take any steps to remove UI or anything from the browser window
     // document.getElementById() etc. will work here
-    // XXX Add in tear-down of the panel.
+    if (window.LoopUI) {
+      window.LoopUI.removeMenuItem();
+      // XXX Add in tear-down of the panel.
+    }
   },
 
   // nsIWindowMediatorListener functions.
   onOpenWindow: function(xulWindow) {
     // A new window has opened.
     let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindow);
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -247,18 +247,16 @@ These should match what Safari and other
 <!ENTITY downloads.accesskey          "D">
 <!ENTITY downloads.commandkey         "j">
 <!ENTITY downloadsUnix.commandkey     "y">
 <!ENTITY addons.label                 "Add-ons">
 <!ENTITY addons.accesskey             "A">
 <!ENTITY addons.commandkey            "A">
 <!ENTITY webapps.label                "Apps">
 <!ENTITY webapps.accesskey            "p">
-<!ENTITY loopMenuItem.label           "Start a conversation…">
-<!ENTITY loopMenuItem.accesskey       "t">
 
 <!ENTITY webDeveloperMenu.label       "Web Developer">
 <!ENTITY webDeveloperMenu.accesskey   "W">
 
 <!ENTITY devToolsCmd.keycode          "VK_F12">
 <!ENTITY devToolsCmd.keytext          "F12">
 
 <!ENTITY devtoolsConnect.label        "Connect…">
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -4,16 +4,22 @@
 
 # Panel Strings
 
 ## LOCALIZATION NOTE(clientShortname2): This should not be localized and
 ## should remain "Firefox Hello" for all locales.
 clientShortname2=Firefox Hello
 clientSuperShortname=Hello
 
+## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
+## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
+## use "..." if \u2026 doesn't suit traditions in your locale.
+loopMenuItem_label=Start a conversation…
+loopMenuItem_accesskey=t
+
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
 ## These are displayed together at the top of the panel when a user is needed to
 ## sign-in again. The emphesis is on the first line to get the user to sign-in again,
 ## and this is displayed in slightly larger font. Please arrange as necessary for
 ## your locale.
 ## {{clientShortname2}} will be replaced by the brand name for either string.
 sign_in_again_title_line_one=Please sign in again
 sign_in_again_title_line_two2=to continue using {{clientShortname2}}
--- a/browser/modules/TabGroupsMigrator.jsm
+++ b/browser/modules/TabGroupsMigrator.jsm
@@ -139,16 +139,19 @@ this.TabGroupsMigrator = {
           if (!groupData) {
             let title = (groupInfo[group] && groupInfo[group].title) || "";
             groupData = {
               tabs: [],
               tabGroupsMigrationTitle: title,
             };
             if (!title) {
               groupData.anonGroupID = ++globalAnonGroupID;
+              groupData.tabGroupsMigrationTitle =
+                gBrowserBundle.formatStringFromName("tabgroups.migration.anonGroup",
+                                                    [groupData.anonGroupID], 1);
             }
             // If this is the active group, set the active group ID and add
             // all the already-known tabs (that didn't list a group ID), if any.
             if (!activeGroupID && !tab.hidden) {
               activeGroupID = group;
               groupData.tabs = tabsWithoutGroup;
               tabsWithoutGroup = null;
             }
@@ -174,42 +177,43 @@ this.TabGroupsMigrator = {
   _createBackup(stateStr) {
     let dest = Services.dirsvc.get("ProfD", Ci.nsIFile);
     dest.append("tabgroups-session-backup.json");
     let promise = OS.File.writeAtomic(dest.path, stateStr, {encoding: "utf-8"});
     AsyncShutdown.webWorkersShutdown.addBlocker("TabGroupsMigrator", promise);
     return promise;
   },
 
+  _groupSorter(a, b) {
+    if (!a.anonGroupID) {
+      return -1;
+    }
+    if (!b.anonGroupID) {
+      return 1;
+    }
+    return a.anonGroupID - b.anonGroupID;
+  },
+
   _bookmarkAllGroupsFromState: Task.async(function*(groupData) {
     // First create a folder in which to put all these bookmarks:
     this.bookmarkedGroupsPromise = PlacesUtils.bookmarks.insert({
       parentGuid: PlacesUtils.bookmarks.menuGuid,
       type: PlacesUtils.bookmarks.TYPE_FOLDER,
       index: 0,
       title: gBrowserBundle.GetStringFromName("tabgroups.migration.tabGroupBookmarkFolderName"),
     }).catch(Cu.reportError);
     let tabgroupsFolder = yield this.bookmarkedGroupsPromise;
 
     for (let [, windowGroupMap] of groupData) {
-      let windowGroups = [... windowGroupMap.values()].sort((a, b) => {
-        if (!a.anonGroupID) {
-          return -1;
-        }
-        if (!b.anonGroupID) {
-          return 1;
-        }
-        return a.anonGroupID - b.anonGroupID;
-      });
+      let windowGroups = [... windowGroupMap.values()].sort(this._groupSorter);
       for (let group of windowGroups) {
         let groupFolder = yield PlacesUtils.bookmarks.insert({
           parentGuid: tabgroupsFolder.guid,
           type: PlacesUtils.bookmarks.TYPE_FOLDER,
-          title: group.tabGroupsMigrationTitle ||
-            gBrowserBundle.formatStringFromName("tabgroups.migration.anonGroup", [group.anonGroupID], 1),
+          title: group.tabGroupsMigrationTitle
         }).catch(Cu.reportError);
 
         for (let tab of group.tabs) {
           let entry = tab.entries[tab.index - 1];
           yield PlacesUtils.bookmarks.insert({
             parentGuid: groupFolder.guid,
             title: tab.title || entry.title,
             url: entry.url,
@@ -248,22 +252,25 @@ this.TabGroupsMigrator = {
           // Make sure we unhide it, or it won't get restored correctly
           tab.hidden = false;
         }
       }
 
       // We then convert any hidden groups into windows for the state object
       // we show in about:tabgroupsdata
       if (groupInfoForWindow) {
+        let windowsToReturn = [];
         for (let groupID of hiddenGroupIDs) {
           let group = groupInfoForWindow.get("" + groupID);
           if (group) {
-            stateToReturn.windows.push(group);
+            windowsToReturn.push(group);
           }
         }
+        windowsToReturn.sort(this._groupSorter);
+        stateToReturn.windows = stateToReturn.windows.concat(windowsToReturn);
       }
 
       // Finally we remove tab groups data from the window:
       if (win.extData) {
         delete win.extData["tabview-group"];
         delete win.extData["tabview-groups"];
         delete win.extData["tabview-ui"];
         delete win.extData["tabview-visibility"];
--- a/browser/modules/test/xpcshell/test_TabGroupsMigrator.js
+++ b/browser/modules/test/xpcshell/test_TabGroupsMigrator.js
@@ -160,27 +160,109 @@ const TEST_STATES = {
           }
         ],
         extData: {
           "tabview-group": "{\"2\":{}}",
         },
       }
     ],
   },
+  SORTING_NAMING_RESTORE_PAGE: {
+    windows: [
+      {
+        tabs: [
+          {
+            entries: [{
+              url: "about:robots",
+              title: "Robots 1",
+            }],
+            index: 1,
+            hidden: false,
+            extData: {
+              "tabview-tab": "{\"groupID\":2,\"active\":true}",
+            },
+          },
+          {
+            entries: [{
+              url: "about:robots",
+              title: "Robots 2",
+            }],
+            index: 1,
+            hidden: false,
+            extData: {
+              "tabview-tab": "{\"groupID\":2}",
+            },
+          },
+          {
+            entries: [{
+              url: "about:robots",
+              title: "Robots 3",
+            }],
+            index: 1,
+            hidden: true,
+            extData: {
+              "tabview-tab": "{\"groupID\":13}",
+            },
+          },
+          {
+            entries: [{
+              url: "about:robots",
+              title: "Robots 4",
+            }],
+            index: 1,
+            hidden: true,
+            extData: {
+              "tabview-tab": "{\"groupID\":15}",
+            },
+          },
+          {
+            entries: [{
+              url: "about:robots",
+              title: "Robots 5",
+            }],
+            index: 1,
+            hidden: true,
+            extData: {
+              "tabview-tab": "{\"groupID\":16}",
+            },
+          },
+          {
+            entries: [{
+              url: "about:robots",
+              title: "Robots 6",
+            }],
+            index: 1,
+            hidden: true,
+            extData: {
+              "tabview-tab": "{\"groupID\":17}",
+            },
+          }
+        ],
+        extData: {
+          "tabview-group": "{\"2\":{},\"13\":{\"title\":\"Foopy\"}, \"15\":{\"title\":\"Barry\"}, \"16\":{}, \"17\":{}}",
+          "tabview-groups": "{\"nextID\":20,\"activeGroupId\":2,\"totalNumber\":5}",
+          "tabview-visibility": "false"
+        },
+      },
+    ]
+  },
 };
 
 add_task(function* gatherGroupDataTest() {
   let groupInfo = TabGroupsMigrator._gatherGroupData(TEST_STATES.TWO_GROUPS);
   Assert.equal(groupInfo.size, 1, "Information about 1 window");
   let singleWinGroups = [... groupInfo.values()][0];
   Assert.equal(singleWinGroups.size, 2, "2 groups");
   let group2 = singleWinGroups.get("2");
   Assert.ok(!!group2, "group 2 should exist");
   Assert.equal(group2.tabs.length, 2, "2 tabs in group 2");
-  Assert.equal(group2.tabGroupsMigrationTitle, "", "We don't assign titles to untitled groups");
+  // Note that this has groupID 2 in the internal representation of tab groups,
+  // but because it was the first group we encountered when migrating, it was
+  // labeled "group 1" for the user
+  Assert.equal(group2.tabGroupsMigrationTitle, "Group 1", "We assign a numeric title to untitled groups");
   Assert.equal(group2.anonGroupID, "1", "We mark an untitled group with an anonymous id");
   let group13 = singleWinGroups.get("13");
   Assert.ok(!!group13, "group 13 should exist");
   Assert.equal(group13.tabs.length, 1, "1 tabs in group 13");
   Assert.equal(group13.tabGroupsMigrationTitle, "Foopy", "Group with title has correct title");
   Assert.ok(!("anonGroupID" in group13), "We don't mark a titled group with an anonymous id");
 });
 
@@ -303,8 +385,22 @@ add_task(function* dealWithNoGroupInfo()
   Assert.equal(groupInfo.size, 1, "Should have 1 window");
   let windowGroups = [...groupInfo][0][1];
   Assert.equal(windowGroups.size, 1, "Window should have 1 group");
   let fallbackActiveGroup = windowGroups.get("active group");
   Assert.ok(fallbackActiveGroup, "Fallback group should exist");
   Assert.equal(fallbackActiveGroup.tabs.length, 3, "There should be 3 tabs in the fallback group");
 });
 
+add_task(function* groupSortingInRemovedDataUsedForRestorePage() {
+  let stateClone = JSON.parse(JSON.stringify(TEST_STATES.SORTING_NAMING_RESTORE_PAGE));
+  let groupInfo = TabGroupsMigrator._gatherGroupData(stateClone);
+  let removedGroups = TabGroupsMigrator._removeHiddenTabGroupsFromState(stateClone, groupInfo);
+  Assert.equal(stateClone.windows.length, 1, "Should still only have 1 window");
+  Assert.equal(stateClone.windows[0].tabs.length, 2, "Should now have 2 tabs");
+
+  let restoredWindowTitles = removedGroups.windows.map(win => win.tabGroupsMigrationTitle);
+  // Note that group 1 is the active group and as such it won't appear in the list of
+  // things the user can restore:
+  Assert.deepEqual(restoredWindowTitles,
+    ["Barry", "Foopy", "Group 2", "Group 3"]);
+});
+
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -90,16 +90,17 @@ browser.jar:
   skin/classic/browser/customizableui/customizeMode-gridTexture.png  (customizableui/customizeMode-gridTexture.png)
   skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png  (customizableui/customizeMode-separatorHorizontal.png)
   skin/classic/browser/customizableui/customizeMode-separatorVertical.png  (customizableui/customizeMode-separatorVertical.png)
   skin/classic/browser/customizableui/menu-arrow.svg           (customizableui/menu-arrow.svg)
 * skin/classic/browser/customizableui/panelUIOverlay.css       (customizableui/panelUIOverlay.css)
   skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
   skin/classic/browser/downloads/buttons.png                   (downloads/buttons.png)
   skin/classic/browser/downloads/buttons-XP.png                (downloads/buttons-XP.png)
+  skin/classic/browser/downloads/download-glow-menuPanel.png   (downloads/download-glow-menuPanel.png)
   skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png   (downloads/download-glow-menuPanel-XPVista7.png)
   skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
   skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
   skin/classic/browser/downloads/download-summary.png          (downloads/download-summary.png)
   skin/classic/browser/downloads/downloads.css                 (downloads/downloads.css)
   skin/classic/browser/feeds/feedIcon.png                      (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png                    (feeds/feedIcon16.png)
   skin/classic/browser/feeds/feedIcon-XP.png                   (feeds/feedIcon-XP.png)
--- a/devtools/client/canvasdebugger/test/browser.ini
+++ b/devtools/client/canvasdebugger/test/browser.ini
@@ -7,16 +7,18 @@ support-files =
   doc_no-canvas.html
   doc_raf-no-canvas.html
   doc_simple-canvas.html
   doc_simple-canvas-bitmasks.html
   doc_simple-canvas-deep-stack.html
   doc_simple-canvas-transparent.html
   doc_webgl-bindings.html
   doc_webgl-enum.html
+  doc_webgl-drawArrays.html
+  doc_webgl-drawElements.html
   head.js
 
 [browser_canvas-actor-test-01.js]
 [browser_canvas-actor-test-02.js]
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
--- a/devtools/client/canvasdebugger/test/browser_profiling-webgl.js
+++ b/devtools/client/canvasdebugger/test/browser_profiling-webgl.js
@@ -3,17 +3,18 @@
 
 /**
  * Tests if functions inside a single animation frame are recorded and stored
  * for a canvas context profiling.
  */
 
 function* ifTestingSupported() {
   let currentTime = window.performance.now();
-  let { target, front } = yield initCanvasDebuggerBackend(WEBGL_ENUM_URL);
+  info("Start to estimate WebGL drawArrays function.");
+  var { target, front } = yield initCanvasDebuggerBackend(WEBGL_DRAW_ARRAYS);
 
   let navigated = once(target, "navigate");
 
   yield front.setup({ reload: true });
   ok(true, "The front was setup up successfully.");
 
   yield navigated;
   ok(true, "Target automatically navigated when the front was set up.");
@@ -24,18 +25,63 @@ function* ifTestingSupported() {
 
   let animationOverview = yield snapshotActor.getOverview();
   ok(animationOverview,
     "An animation overview could be retrieved after recording.");
 
   let functionCalls = animationOverview.calls;
   ok(functionCalls,
     "An array of function call actors was sent after recording.");
-  is(functionCalls.length, 3,
-    "The number of function call actors is correct.");
+
+  testFunctionCallTimestamp(functionCalls, currentTime);
+
+  info("Check triangle and vertex counts in drawArrays()");
+  is(animationOverview.primitive.tris, 5, "The count of triangles is correct.");
+  is(animationOverview.primitive.vertices, 26, "The count of vertices is correct.");
+  is(animationOverview.primitive.points, 4, "The count of points is correct.");
+  is(animationOverview.primitive.lines, 8, "The count of lines is correct.");
+
+  yield removeTab(target.tab);
+
+  info("Start to estimate WebGL drawElements function.");
+  var { target, front } = yield initCanvasDebuggerBackend(WEBGL_DRAW_ELEMENTS);
+
+  navigated = once(target, "navigate");
+
+  yield front.setup({ reload: true });
+  ok(true, "The front was setup up successfully.");
+
+  yield navigated;
+  ok(true, "Target automatically navigated when the front was set up.");
+
+  snapshotActor = yield front.recordAnimationFrame();
+  ok(snapshotActor,
+    "A snapshot actor was sent after recording.");
+  
+  animationOverview = yield snapshotActor.getOverview();
+  ok(animationOverview,
+    "An animation overview could be retrieved after recording.");
+  
+  functionCalls = animationOverview.calls;
+  ok(functionCalls,
+    "An array of function call actors was sent after recording.");
+  
+  testFunctionCallTimestamp(functionCalls, currentTime);
+  
+  info("Check triangle and vertex counts in drawElements()");
+  is(animationOverview.primitive.tris, 5, "The count of triangles is correct.");
+  is(animationOverview.primitive.vertices, 26, "The count of vertices is correct.");
+  is(animationOverview.primitive.points, 4, "The count of points is correct.");
+  is(animationOverview.primitive.lines, 8, "The count of lines is correct.");
+  
+  yield removeTab(target.tab);
+  finish();
+}
+
+function testFunctionCallTimestamp(functionCalls, currentTime) {
 
   info("Check the timestamps of function calls");
 
   for ( let i = 0; i < functionCalls.length-1; i += 2 ) {
     ok( functionCalls[i].timestamp > 0, "The timestamp of the called function is larger than 0." );
     ok( functionCalls[i].timestamp < currentTime, "The timestamp has been minus the frame start time." );
     ok( functionCalls[i+1].timestamp > functionCalls[i].timestamp, "The timestamp of the called function is correct." );
   }
new file mode 100644
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_webgl-drawArrays.html
@@ -0,0 +1,187 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>WebGL editor test page</title>
+  </head>
+
+  <body>
+    <canvas id="canvas" width="128" height="128"></canvas>
+    <script id="shader-fs" type="x-shader/x-fragment">
+      precision mediump float;
+      uniform vec4 mtrColor;
+
+      void main(void) {
+        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * mtrColor;
+      }
+    </script>
+    <script id="shader-vs" type="x-shader/x-vertex">
+      attribute vec3 aVertexPosition;
+
+      void main(void) {
+        gl_PointSize = 5.0;
+        gl_Position = vec4(aVertexPosition, 1.0);
+      }
+    </script>
+    <script type="text/javascript;version=1.8">
+      "use strict";
+
+      let canvas, gl, shaderProgram;
+      let triangleVertexPositionBuffer, squareVertexPositionBuffer;
+     
+      window.onload = function() {
+        canvas = document.querySelector("canvas");
+        gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+        gl.viewportWidth = canvas.width;
+        gl.viewportHeight = canvas.height;
+       
+        initShaders();
+        initBuffers();
+
+        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
+        gl.clearColor(0.0, 0.0, 0.0, 1.0);
+        gl.disable(gl.DEPTH_TEST);
+        drawScene();
+      }
+
+      function getShader(gl, id) {
+        var shaderScript = document.getElementById(id);
+        if (!shaderScript) {
+            return null;
+        }
+
+        var str = "";
+        var k = shaderScript.firstChild;
+        while (k) {
+            if (k.nodeType == 3) {
+                str += k.textContent;
+            }
+            k = k.nextSibling;
+        }
+
+        var shader;
+        if (shaderScript.type == "x-shader/x-fragment") {
+            shader = gl.createShader(gl.FRAGMENT_SHADER);
+        } else if (shaderScript.type == "x-shader/x-vertex") {
+            shader = gl.createShader(gl.VERTEX_SHADER);
+        } else {
+            return null;
+        }
+
+        gl.shaderSource(shader, str);
+        gl.compileShader(shader);
+
+        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+            alert(gl.getShaderInfoLog(shader));
+            return null;
+        }
+
+        return shader;
+      }
+      
+      function initShaders() {
+        var fragmentShader = getShader(gl, "shader-fs");
+        var vertexShader = getShader(gl, "shader-vs");
+
+        shaderProgram = gl.createProgram();
+        gl.attachShader(shaderProgram, vertexShader);
+        gl.attachShader(shaderProgram, fragmentShader);
+        gl.linkProgram(shaderProgram);
+
+        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
+            alert("Could not initialise shaders");
+        }
+
+        gl.useProgram(shaderProgram);
+
+        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
+        shaderProgram.pMaterialColor = gl.getUniformLocation(shaderProgram, "mtrColor");
+        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
+      }
+
+      function initBuffers() {
+          // Create triangle vertex/index buffer
+          triangleVertexPositionBuffer = gl.createBuffer();
+          gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+          var vertices = [
+               0.0,  0.5,  0.0,
+              -0.5, -0.5,  0.0,
+               0.5, -0.5,  0.0
+          ];
+          gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+          triangleVertexPositionBuffer.itemSize = 3;
+          triangleVertexPositionBuffer.numItems = 3;
+
+          // Create square vertex/index buffer
+          squareVertexPositionBuffer = gl.createBuffer();
+          gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+          vertices = [
+               0.8,  0.8,  0.0,
+              -0.8,  0.8,  0.0,
+               0.8, -0.8,  0.0,
+              -0.8, -0.8,  0.0
+          ];
+          gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+          squareVertexPositionBuffer.itemSize = 3;
+          squareVertexPositionBuffer.numItems = 4;
+      }
+
+      function drawScene() {        
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+
+        // DrawArrays
+        // --------------
+        // draw square - triangle strip
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 1, 1, 1, 1);
+        gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
+
+        // draw square - triangle fan
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 0, 1);
+        gl.drawArrays(gl.TRIANGLE_FAN, 0, squareVertexPositionBuffer.numItems);
+
+        // draw triangle
+        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 0, 1);
+        gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
+
+        // draw points
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 1, 1);
+        gl.drawArrays(gl.POINTS, 0, squareVertexPositionBuffer.numItems);
+
+        // draw lines
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0, 0, 1, 1);
+        gl.lineWidth(8.0);
+        gl.drawArrays(gl.LINES, 0, squareVertexPositionBuffer.numItems);
+
+        // draw line strip
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0.9, 0.6, 0, 1);
+        gl.lineWidth(3.0);
+        gl.drawArrays(gl.LINE_STRIP, 0, squareVertexPositionBuffer.numItems);
+
+        // draw line loop
+        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 1, 1);
+        gl.lineWidth(3.0);
+        gl.drawArrays(gl.LINE_LOOP, 0, triangleVertexPositionBuffer.numItems);
+
+        window.requestAnimationFrame(drawScene);
+      }
+    </script>
+  </body>
+
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/canvasdebugger/test/doc_webgl-drawElements.html
@@ -0,0 +1,225 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>WebGL editor test page</title>
+  </head>
+
+  <body>
+    <canvas id="canvas" width="128" height="128"></canvas>
+    <script id="shader-fs" type="x-shader/x-fragment">
+      precision mediump float;
+      uniform vec4 mtrColor;
+
+      void main(void) {
+        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * mtrColor;
+      }
+    </script>
+    <script id="shader-vs" type="x-shader/x-vertex">
+      attribute vec3 aVertexPosition;
+
+      void main(void) {
+        gl_PointSize = 5.0;
+        gl_Position = vec4(aVertexPosition, 1.0);
+      }
+    </script>
+    <script type="text/javascript;version=1.8">
+      "use strict";
+
+      let canvas, gl, shaderProgram;
+      let triangleVertexPositionBuffer, squareVertexPositionBuffer;
+      let triangleIndexBuffer;
+      let squareIndexBuffer, squareStripIndexBuffer, squareFanIndexBuffer
+     
+      window.onload = function() {
+        canvas = document.querySelector("canvas");
+        gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
+        gl.viewportWidth = canvas.width;
+        gl.viewportHeight = canvas.height;
+       
+        initShaders();
+        initBuffers();
+
+        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
+        gl.clearColor(0.0, 0.0, 0.0, 1.0);
+        gl.disable(gl.DEPTH_TEST);
+        drawScene();
+      }
+
+      function getShader(gl, id) {
+        var shaderScript = document.getElementById(id);
+        if (!shaderScript) {
+            return null;
+        }
+
+        var str = "";
+        var k = shaderScript.firstChild;
+        while (k) {
+            if (k.nodeType == 3) {
+                str += k.textContent;
+            }
+            k = k.nextSibling;
+        }
+
+        var shader;
+        if (shaderScript.type == "x-shader/x-fragment") {
+            shader = gl.createShader(gl.FRAGMENT_SHADER);
+        } else if (shaderScript.type == "x-shader/x-vertex") {
+            shader = gl.createShader(gl.VERTEX_SHADER);
+        } else {
+            return null;
+        }
+
+        gl.shaderSource(shader, str);
+        gl.compileShader(shader);
+
+        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+            alert(gl.getShaderInfoLog(shader));
+            return null;
+        }
+
+        return shader;
+      }
+      
+      function initShaders() {
+        var fragmentShader = getShader(gl, "shader-fs");
+        var vertexShader = getShader(gl, "shader-vs");
+
+        shaderProgram = gl.createProgram();
+        gl.attachShader(shaderProgram, vertexShader);
+        gl.attachShader(shaderProgram, fragmentShader);
+        gl.linkProgram(shaderProgram);
+
+        if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
+            alert("Could not initialise shaders");
+        }
+
+        gl.useProgram(shaderProgram);
+
+        shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
+        shaderProgram.pMaterialColor = gl.getUniformLocation(shaderProgram, "mtrColor");
+        gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
+      }
+
+      function initBuffers() {
+          // Create triangle vertex/index buffer
+          triangleVertexPositionBuffer = gl.createBuffer();
+          gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+          var vertices = [
+             0.0,  0.5,  0.0,
+            -0.5, -0.5,  0.0,
+             0.5, -0.5,  0.0
+          ];
+          gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+          triangleVertexPositionBuffer.itemSize = 3;
+          triangleVertexPositionBuffer.numItems = 3;
+
+          triangleIndexBuffer = gl.createBuffer();
+          gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
+          var indices = [
+              0, 1, 2
+          ];
+          gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+          triangleIndexBuffer.itemSize = 1;
+          triangleIndexBuffer.numItems = 3;
+
+          // Create square vertex/index buffer
+          squareVertexPositionBuffer = gl.createBuffer();
+          gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+          vertices = [
+             0.8,  0.8,  0.0,
+            -0.8,  0.8,  0.0,
+             0.8, -0.8,  0.0,
+            -0.8, -0.8,  0.0
+          ];
+          gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+          squareVertexPositionBuffer.itemSize = 3;
+          squareVertexPositionBuffer.numItems = 4;
+
+          squareIndexBuffer = gl.createBuffer();
+          gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);
+          indices = [ 
+            0, 1, 2,
+            1, 3, 2
+          ];
+          gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+          squareIndexBuffer.itemSize = 1;
+          squareIndexBuffer.numItems = 6;
+
+          squareStripIndexBuffer = gl.createBuffer();
+          gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+          indices = [ 
+            0, 1, 2, 3
+          ];
+          gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+          squareStripIndexBuffer.itemSize = 1;
+          squareStripIndexBuffer.numItems = 4;
+
+      }
+
+      function drawScene() {
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+
+        // DrawElements
+        // --------------
+        // draw triangle
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 1, 1, 1, 1);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareIndexBuffer);
+        gl.drawElements(gl.TRIANGLES, squareIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+        // draw square - triangle strip
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 0, 1);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+        gl.drawElements(gl.TRIANGLE_FAN, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+        // draw square - triangle fan
+        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 0, 1);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
+        gl.drawElements(gl.TRIANGLE_FAN, triangleIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+        // draw points
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 1, 0, 1, 1);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+        gl.drawElements(gl.POINTS, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+        // draw lines
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0, 0, 1, 1);
+        gl.lineWidth(8.0);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+        gl.drawElements(gl.LINES, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+        // draw line strip
+        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0.9, 0.6, 0, 1);
+        gl.lineWidth(3.0);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, squareStripIndexBuffer);
+        gl.drawElements(gl.LINE_STRIP, squareStripIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+        // draw line loop
+        gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
+        gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
+        gl.uniform4f(shaderProgram.pMaterialColor, 0, 1, 1, 1);
+        gl.lineWidth(3.0);
+        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
+        gl.drawElements(gl.LINE_LOOP, triangleIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
+
+        window.requestAnimationFrame(drawScene);
+      }
+    </script>
+  </body>
+
+</html>
\ No newline at end of file
--- a/devtools/client/canvasdebugger/test/head.js
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -34,16 +34,18 @@ const SET_TIMEOUT_URL = EXAMPLE_URL + "d
 const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html";
 const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html";
 const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
 const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
 const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
 const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html";
 const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html";
 const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html";
+const WEBGL_DRAW_ARRAYS = EXAMPLE_URL + "doc_webgl-drawArrays.html";
+const WEBGL_DRAW_ELEMENTS = EXAMPLE_URL + "doc_webgl-drawElements.html";
 const RAF_BEGIN_URL = EXAMPLE_URL + "doc_raf-begin.html";
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 var gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
 
 DevToolsUtils.testing = true;
--- a/devtools/client/memory/initializer.js
+++ b/devtools/client/memory/initializer.js
@@ -10,43 +10,47 @@ Cu.import("resource://devtools/client/sh
 const { require } = BrowserLoaderModule.BrowserLoader("resource://devtools/client/memory/", this);
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 const App = createFactory(require("devtools/client/memory/app"));
 const Store = require("devtools/client/memory/store");
 const { assert } = require("devtools/shared/DevToolsUtils");
+const Telemetry = require("devtools/client/shared/telemetry");
 
 /**
  * The current target, toolbox, MemoryFront, and HeapAnalysesClient, set by this tool's host.
  */
 var gToolbox, gTarget, gFront, gHeapAnalysesClient;
 
 /**
  * Variables set by `initialize()`
  */
-var gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted;
+var gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted, telemetry;
 
 var initialize = Task.async(function*() {
+  telemetry = new Telemetry();
+  telemetry.toolOpened("memory");
   gRoot = document.querySelector("#app");
   gStore = Store();
   gApp = createElement(App, { toolbox: gToolbox, front: gFront, heapWorker: gHeapAnalysesClient });
   gProvider = createElement(Provider, { store: gStore }, gApp);
   ReactDOM.render(gProvider, gRoot);
   unsubscribe = gStore.subscribe(onStateChange);
 });
 
 var destroy = Task.async(function*() {
   const ok = ReactDOM.unmountComponentAtNode(gRoot);
   assert(ok, "Should successfully unmount the memory tool's top level React component");
 
+  telemetry.toolClosed("memory");
   unsubscribe();
 
-  gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted = null;
+  gStore, gRoot, gApp, gProvider, unsubscribe, isHighlighted, telemetry = null;
 });
 
 /**
  * Fired on any state change, currently only handles toggling
  * the highlighting of the tool when recording allocations.
  */
 function onStateChange () {
   let isRecording = gStore.getState().allocations.recording;
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -140,16 +140,21 @@ Telemetry.prototype = {
       userHistogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS"
     },
     performance: {
       histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN",
       userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS"
     },
+    memory: {
+      histogram: "DEVTOOLS_MEMORY_OPENED_BOOLEAN",
+      userHistogram: "DEVTOOLS_MEMORY_OPENED_PER_USER_FLAG",
+      timerHistogram: "DEVTOOLS_MEMORY_TIME_ACTIVE_SECONDS"
+    },
     netmonitor: {
       histogram: "DEVTOOLS_NETMONITOR_OPENED_BOOLEAN",
       userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS"
     },
     storage: {
       histogram: "DEVTOOLS_STORAGE_OPENED_BOOLEAN",
       userHistogram: "DEVTOOLS_STORAGE_OPENED_PER_USER_FLAG",
--- a/devtools/client/shared/widgets/TableWidget.js
+++ b/devtools/client/shared/widgets/TableWidget.js
@@ -470,17 +470,17 @@ function Column(table, id, header) {
   this.tbody.appendChild(this.splitter);
 
   this.column = this.document.createElementNS(HTML_NS, "div");
   this.column.id = id;
   this.column.className = "table-widget-column";
   this.wrapper.appendChild(this.column);
 
   this.header = this.document.createElementNS(XUL_NS, "label");
-  this.header.className = "plain devtools-toolbar table-widget-column-header";
+  this.header.className = "devtools-toolbar table-widget-column-header";
   this.header.setAttribute("value", header);
   this.column.appendChild(this.header);
   if (table.headersContextMenu) {
     this.header.setAttribute("context", table.headersContextMenu);
   }
   this.toggleColumn = this.toggleColumn.bind(this);
   this.table.on(EVENTS.HEADER_CONTEXT_MENU, this.toggleColumn);
 
--- a/devtools/client/sourceeditor/tern/README
+++ b/devtools/client/sourceeditor/tern/README
@@ -2,9 +2,12 @@ This is the Tern code-analysis engine pa
 
 Tern is a stand-alone code-analysis engine for JavaScript. It is intended to be used with a code editor plugin to enhance the editor's support for intelligent JavaScript editing
 
 
 # Upgrade
 
 Currently used version is 0.6.2.  To upgrade, download the latest release from http://ternjs.net/, and copy the files from lib/ into this directory.
 
-You may also need to update the CodeMirror plugin found in devtools/client/sourceeditor/codemirror/tern, but it will most likely work without updating.
+You may also need to update the CodeMirror plugin found in devtools/client/sourceeditor/codemirror/addon/tern, but it will most likely work without updating.
+
+Replace instances of `require("acorn")` with `require("acorn/acorn")`
+Replace instances of `acorn/dist/` with `acorn/`
\ No newline at end of file
old mode 100644
new mode 100755
old mode 100644
new mode 100755
--- a/devtools/client/sourceeditor/tern/condense.js
+++ b/devtools/client/sourceeditor/tern/condense.js
@@ -2,57 +2,57 @@
 
 // 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) {
+(function(root, 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) {
+  mod(root.tern || (root.tern = {}), tern); // Plain browser env
+})(this, 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.server.signal("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);
+    state.server.signal("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);
+    state.server.signal("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.server = options.server || this.cx.parent || {signal: function() {}}
     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 = [];
@@ -108,34 +108,40 @@
       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.doc = type.doc || (actual != type && state.isTarget(actual.origin) && actual.doc) || data.doc;
         data.data = actual.metaData;
         data.byName = data.byName == null ? !!byName : data.byName && byName;
         state.types[newPath] = data;
       }
     } else {
       if (relevant) state.altPaths[newPath] = actual;
     }
   }
   function reachByName(aval, path, id, state) {
     var type = aval.getType();
     if (type) reach(type, path, id, state, true);
   }
 
   infer.Prim.prototype.reached = function() {return true;};
 
   infer.Arr.prototype.reached = function(path, state, concrete) {
-    if (!concrete) reachByName(this.getProp("<i>"), path, "<i>", state);
+    if (concrete) return true
+    if (this.tuple) {
+      for (var i = 0; i < this.tuple; i++)
+        reachByName(this.getProp(String(i)), path, String(i), state)
+    } else {
+      reachByName(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)
         reachByName(this.args[i], path, "!" + i, state);
@@ -172,22 +178,20 @@
     }
     for (var prop in obj.props)
       reach(obj.props[prop], path, prop, state);
   }
 
   function createPath(parts, state) {
     var base = state.output, defs = state.output["!define"];
     for (var i = 0, path; i < parts.length; ++i) {
-      var part = parts[i], known = path && state.types[path];
+      var part = parts[i];
       path = path ? path + "." + part : part;
       var me = state.types[path];
-      if (part.charAt(0) == "!" ||
-          known && known.type.constructor != infer.Obj ||
-          me && me.byName) {
+      if (part.charAt(0) == "!" || me && me.byName) {
         if (hop(defs, path)) base = defs[path];
         else defs[path] = base = {};
       } else {
         if (hop(base, parts[i])) base = base[part];
         else base = base[part] = {};
       }
     }
     return base;
@@ -211,47 +215,65 @@
     if (last[0] == "!") return;
     var known = state.types[parts.join(".")];
     var base = createPath(parts, state);
     if (known && known.type.constructor != infer.Obj) return;
     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();
+  function typeName(value) {
+    var isType = value instanceof infer.Type;
+    if (isType) {
+      if (typeNameStack.indexOf(value) > -1)
+        return value.path || "?";
+      typeNameStack.push(value);
+    }
+    var name = value.typeName();
+    if (isType) typeNameStack.pop();
     return name;
   }
 
+  infer.AVal.prototype.typeName = function() {
+    if (this.types.length == 0) return "?";
+    if (this.types.length == 1) return typeName(this.types[0]);
+    var simplified = infer.simplifyTypes(this.types);
+    if (simplified.length > 2) return "?";
+    for (var strs = [], i = 0; i < simplified.length; i++)
+      strs.push(typeName(simplified[i]));
+    return strs.join("|");
+  };
+
+  infer.ANull.typeName = function() { return "?"; };
+
   infer.Prim.prototype.typeName = function() { return this.name; };
 
+  infer.Sym.prototype.typeName = function() { return this.asPropName }
+
   infer.Arr.prototype.typeName = function() {
-    return "[" + typeName(this.getProp("<i>")) + "]";
+    if (!this.tuple) return "[" + typeName(this.getProp("<i>")) + "]"
+    var content = []
+    for (var i = 0; i < this.tuple; i++)
+      content.push(typeName(this.getProp(String(i))))
+    return "[" + content.join(", ") + "]"
   };
 
   infer.Fn.prototype.typeName = function() {
-    var out = "fn(";
+    var out = this.generator ? "fn*(" : "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) {
+    if (this.computeRetSource)
       out += " -> " + this.computeRetSource;
-    } else if (!this.retval.isEmpty()) {
-      var rettype = this.retval.getType(false);
-      if (rettype) out += " -> " + typeName(rettype);
-    }
+    else if (!this.retval.isEmpty())
+      out += " -> " + typeName(this.retval);
     return out;
   };
 
   infer.Obj.prototype.typeName = function() {
     if (this.nameOverride) return this.nameOverride;
     if (!this.path) return "?";
     return this.path;
   };
@@ -274,14 +296,9 @@
     for (var prop in obj) props.push(prop);
     props.sort();
     for (var i = 0; i < props.length; ++i) {
       var prop = props[i];
       out[prop] = obj[prop];
     }
     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));
-  }
 });
old mode 100644
new mode 100755
--- a/devtools/client/sourceeditor/tern/def.js
+++ b/devtools/client/sourceeditor/tern/def.js
@@ -22,149 +22,229 @@
   }
 
   var TypeParser = exports.TypeParser = function(spec, start, base, forceNew) {
     this.pos = start || 0;
     this.spec = spec;
     this.base = base;
     this.forceNew = forceNew;
   };
+
+  function unwrapType(type, self, args) {
+    return type.call ? type(self, args) : type;
+  }
+
+  function extractProp(type, prop) {
+    if (prop == "!ret") {
+      if (type.retval) return type.retval;
+      var rv = new infer.AVal;
+      type.propagate(new infer.IsCallee(infer.ANull, [], null, rv));
+      return rv;
+    } else {
+      return type.getProp(prop);
+    }
+  }
+
+  function computedFunc(name, args, retType, generator) {
+    return function(self, cArgs) {
+      var realArgs = [];
+      for (var i = 0; i < args.length; i++) realArgs.push(unwrapType(args[i], self, cArgs));
+      return new infer.Fn(name, infer.ANull, realArgs, unwrapType(retType, self, cArgs), generator);
+    };
+  }
+  function computedUnion(types) {
+    return function(self, args) {
+      var union = new infer.AVal;
+      for (var i = 0; i < types.length; i++) unwrapType(types[i], self, args).propagate(union);
+      union.maxWeight = 1e5;
+      return union;
+    };
+  }
+  function computedArray(inner) {
+    return function(self, args) {
+      return new infer.Arr(inner(self, args));
+    };
+  }
+  function computedTuple(types) {
+    return function(self, args) {
+      return new infer.Arr(types.map(function(tp) { return unwrapType(tp, self, args) }))
+    }
+  }
+
   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 = [];
+    parseFnType: function(comp, name, top, generator) {
+      var args = [], names = [], computed = false;
       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());
+        var argType = this.parseType(comp);
+        if (argType.call) computed = true;
+        args.push(argType);
         if (!this.eat(", ")) {
           this.eat(")") || this.error();
           break;
         }
       }
       var retType, computeRet, computeRetStart, fn;
       if (this.eat(" -> ")) {
-        if (top && this.spec.indexOf("!", this.pos) > -1) {
+        var retStart = this.pos;
+        retType = this.parseType(true);
+        if (retType.call && !computed) {
+          computeRet = retType;
           retType = infer.ANull;
-          computeRetStart = this.pos;
-          computeRet = this.parseRetType();
-        } else retType = this.parseType();
-      } else retType = infer.ANull;
+          computeRetStart = retStart;
+        }
+      } else {
+        retType = infer.ANull;
+      }
+      if (computed) return computedFunc(name, args, retType, generator);
+
       if (top && (fn = this.base))
-        infer.Fn.call(this.base, name, infer.ANull, args, names, retType);
+        infer.Fn.call(this.base, name, infer.ANull, args, names, retType, generator);
       else
-        fn = new infer.Fn(name, infer.ANull, args, names, retType);
+        fn = new infer.Fn(name, infer.ANull, args, names, retType, generator);
       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);
+    parseType: function(comp, name, top) {
+      var main = this.parseTypeMaybeProp(comp, name, top);
+      if (!this.eat("|")) return main;
+      var types = [main], computed = main.call;
+      for (;;) {
+        var next = this.parseTypeMaybeProp(comp, name, top);
+        types.push(next);
+        if (next.call) computed = true;
+        if (!this.eat("|")) break;
+      }
+      if (computed) return computedUnion(types);
+      var union = new infer.AVal;
+      for (var i = 0; i < types.length; i++) types[i].propagate(union);
+      union.maxWeight = 1e5;
+      return union;
+    },
+    parseTypeMaybeProp: function(comp, name, top) {
+      var result = this.parseTypeInner(comp, name, top);
+      while (comp && this.eat(".")) result = this.extendWithProp(result);
+      return result;
+    },
+    extendWithProp: function(base) {
+      var propName = this.word(/[\w<>$!:]/) || this.error();
+      if (base.apply) return function(self, args) {
+        return extractProp(base(self, args), propName);
+      };
+      return extractProp(base, propName);
+    },
+    parseTypeInner: function(comp, name, top) {
+      var gen
+      if (this.eat("fn(") || (gen = this.eat("fn*("))) {
+        return this.parseFnType(comp, name, top, gen);
       } else if (this.eat("[")) {
-        var inner = this.parseType();
-        if (inner == infer.ANull && this.spec == "[b.<i>]") {
-          var b = parsePath("b");
-          console.log(b.props["<i>"].types.length);
+        var inner = this.parseType(comp), types, computed = inner.call
+        while (this.eat(", ")) {
+          if (!types) types = [inner]
+          var next = this.parseType(comp)
+          types.push(next)
+          computed = computed || next.call
         }
-        this.eat("]") || this.error();
+        this.eat("]") || this.error()
+        if (computed) return types ? computedTuple(types) : computedArray(inner)
         if (top && this.base) {
-          infer.Arr.call(this.base, inner);
-          return this.base;
+          infer.Arr.call(this.base, types || inner)
+          return this.base
         }
-        return new infer.Arr(inner);
+        return new infer.Arr(types || 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;
+        var path = this.word(/[\w$<>\.:!]/)
+        var base = infer.cx().localDefs[path + ".prototype"]
+        if (!base) {
+          var base = parsePath(path);
+          if (!(base instanceof infer.Obj)) return base;
+          var proto = descendProps(base, ["prototype"])
+          if (proto && (proto = proto.getObjType()))
+            base = proto
+        }
+        if (comp && this.eat("[")) return this.parsePoly(base);
         if (top && this.forceNew) return new infer.Obj(base);
         return infer.getInstance(base);
+      } else if (this.eat(":")) {
+        var name = this.word(/[\w$\.]/)
+        return infer.getSymbol(name)
+      } else if (comp && 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("!" + this.word(/[\w$<>\.!:]/));
+        }
       } else if (this.eat("?")) {
         return infer.ANull;
       } else {
-        return this.fromWord(this.word(/[\w$<>\.!`]/));
+        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$<>\.!]/));
-        }
+    parsePoly: function(base) {
+      var propName = "<i>", match;
+      if (match = this.spec.slice(this.pos).match(/^\s*([\w$:]+)\s*=\s*/)) {
+        propName = match[1];
+        this.pos += match[0].length;
       }
-      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;
+      var value = this.parseType(true);
+      if (!this.eat("]")) this.error();
+      if (value.call) return function(self, args) {
+        var instance = new infer.Obj(base);
+        value(self, args).propagate(instance.defProp(propName));
+        return instance;
       };
-      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;
+      var instance = new infer.Obj(base);
+      value.propagate(instance.defProp(propName));
+      return instance;
     }
   };
 
   function parseType(spec, name, base, forceNew) {
-    var type = new TypeParser(spec, null, base, forceNew).parseType(name, true);
+    var type = new TypeParser(spec, null, base, forceNew).parseType(false, 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;
@@ -178,77 +258,84 @@
       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();
+      var origin = p.parseType(true);
       if (!p.eat(" ")) p.error();
-      var getTarget = p.parseRetType();
+      var target = p.parseType(true);
       addEffect(fn, function(self, args) {
-        getOrigin(self, args).propagate(getTarget(self, args));
+        unwrapType(origin, self, args).propagate(unwrapType(target, 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());
+      var getCallee = p.parseType(true), getSelf = null, getArgs = [];
+      if (p.eat(" this=")) getSelf = p.parseType(true);
+      while (p.eat(" ")) getArgs.push(p.parseType(true));
       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 callee = unwrapType(getCallee, self, args);
+        var slf = getSelf ? unwrapType(getSelf, self, args) : infer.ANull, as = [];
+        for (var i = 0; i < getArgs.length; ++i) as.push(unwrapType(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();
+      var getFrom = p.parseType(true);
       p.eat(" ");
-      var getTo = p.parseRetType();
+      var getTo = p.parseType(true);
       addEffect(fn, function(self, args) {
-        var from = getFrom(self, args), to = getTo(self, args);
+        var from = unwrapType(getFrom, self, args), to = unwrapType(getTo, self, args);
         from.forAllProps(function(prop, val, local) {
           if (local && prop != "<i>")
-            to.propagate(new infer.PropHasSubset(prop, val));
+            to.propagate(new infer.DefProp(prop, val));
         });
       });
     } else {
       throw new Error("Unknown effect type: " + effect);
     }
   };
 
   var currentTopScope;
 
-  var parsePath = exports.parsePath = function(path) {
+  var parsePath = exports.parsePath = function(path, scope) {
     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;
+    var base = scope || 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(".");
+    var result = descendProps(base, path.split("."))
+    // Uncomment this to get feedback on your poorly written .json files
+    // if (result == infer.ANull) console.error("bad path: " + origPath + " (" + cx.curOrigin + ")")
+    cx.paths[origPath] = result == infer.ANull ? null : result
+    return result
+  }
+
+  function descendProps(base, parts) {
     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) {
@@ -263,21 +350,18 @@
       } 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;
   }
 
@@ -303,17 +387,17 @@
       }
       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(false), inner, path ? path + "." + name : name).propagate(prop);
+      passOne(prop.getObjType(), inner, path ? path + "." + name : name).propagate(prop);
     }
     return base;
   }
 
   function passTwo(base, spec, path) {
     if (base.isShell) {
       delete base.isShell;
       var tp = spec["!type"];
@@ -327,54 +411,47 @@
 
     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(false);
       if (typeof inner == "string") {
-        if (type) continue;
-        parseType(inner, innerPath).propagate(known);
+        if (known.isEmpty()) parseType(inner, innerPath).propagate(known);
       } else {
-        if (!isSimpleAnnotation(inner)) {
-          passTwo(type, inner, innerPath);
-        } else if (!type) {
+        if (!isSimpleAnnotation(inner))
+          passTwo(known.getObjType(), inner, innerPath);
+        else if (known.isEmpty())
           parseType(inner["!type"], innerPath, null, true).propagate(known);
-          type = known.getType(false);
-          if (type instanceof infer.Obj) copyInfo(inner, type);
-        } else continue;
+        else
+          continue;
         if (inner["!doc"]) known.doc = inner["!doc"];
         if (inner["!url"]) known.url = inner["!url"];
         if (inner["!span"]) known.span = inner["!span"];
       }
     }
+    return base;
   }
 
   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();
+    var cx = infer.cx(), server = cx.parent
 
     infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + cx.origins.length);
     cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null);
 
-    runPasses("preLoadDef", data);
+    if (server) server.signal("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);
@@ -382,42 +459,64 @@
       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);
+    if (server) server.signal("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;
     }
   };
 
+  exports.parse = function(data, origin, path) {
+    var cx = infer.cx();
+    if (origin) {
+      cx.origin = origin;
+      cx.localDefs = cx.definitions[origin];
+    }
+
+    try {
+      if (typeof data == "string")
+        return parseType(data, path);
+      else
+        return passTwo(passOne(null, data, path), data, path);
+    } finally {
+      if (origin) cx.origin = cx.localDefs = null;
+    }
+  };
+
   // 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", {
+  var IsCreated = infer.constraint({
+    construct: function(created, target, spec) {
+      this.created = created;
+      this.target = target;
+      this.spec = 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(false);
+        if (spec instanceof infer.AVal) spec = spec.getObjType(false);
         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(false);
             if (vtp) p.addType(vtp);
           }
         }
@@ -430,21 +529,58 @@
     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", {
+  var PropSpec = infer.constraint({
+    construct: function(target) { this.target = target; },
+    addType: function(tp) {
+      if (!(tp instanceof infer.Obj)) return;
+      if (tp.hasProp("value"))
+        tp.getProp("value").propagate(this.target);
+      else if (tp.hasProp("get"))
+        tp.getProp("get").propagate(new infer.IsCallee(infer.ANull, [], null, this.target));
+    }
+  });
+
+  infer.registerFunction("Object_defineProperty", function(_self, args, argNodes) {
+    if (argNodes && argNodes.length >= 3 && argNodes[1].type == "Literal" &&
+        typeof argNodes[1].value == "string") {
+      var obj = args[0], connect = new infer.AVal;
+      obj.propagate(new infer.DefProp(argNodes[1].value, connect, argNodes[1]));
+      args[2].propagate(new PropSpec(connect));
+    }
+    return infer.ANull;
+  });
+
+  infer.registerFunction("Object_defineProperties", function(_self, args, argNodes) {
+    if (args.length >= 2) {
+      var obj = args[0];
+      args[1].forAllProps(function(prop, val, local) {
+        if (!local) return;
+        var connect = new infer.AVal;
+        obj.propagate(new infer.DefProp(prop, connect, argNodes && argNodes[1]));
+        val.propagate(new PropSpec(connect));
+      });
+    }
+    return infer.ANull;
+  });
+
+  var IsBound = infer.constraint({
+    construct: function(self, args, target) {
+      this.self = self; this.args = args; this.target = 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.target.addType(new infer.Fn(tp.name, infer.ANull, tp.args.slice(this.args.length),
+                                       tp.argNames.slice(this.args.length), tp.retval, tp.generator));
       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;
@@ -457,10 +593,64 @@
     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;
   });
 
+  infer.registerFunction("Promise_ctor", function(_self, args, argNodes) {
+    var defs6 = infer.cx().definitions.ecma6
+    if (!defs6 || args.length < 1) return infer.ANull;
+    var self = new infer.Obj(defs6["Promise.prototype"]);
+    var valProp = self.defProp(":t", argNodes && argNodes[0]);
+    var valArg = new infer.AVal;
+    valArg.propagate(valProp);
+    var exec = new infer.Fn("execute", infer.ANull, [valArg], ["value"], infer.ANull);
+    var reject = defs6.Promise_reject;
+    args[0].propagate(new infer.IsCallee(infer.ANull, [exec, reject], null, infer.ANull));
+    return self;
+  });
+
+  var PromiseResolvesTo = infer.constraint({
+    construct: function(output) { this.output = output; },
+    addType: function(tp) {
+      if (tp.constructor == infer.Obj && tp.name == "Promise" && tp.hasProp(":t"))
+        tp.getProp(":t").propagate(this.output);
+      else
+        tp.propagate(this.output);
+    }
+  });
+
+  var WG_PROMISE_KEEP_VALUE = 50;
+
+  infer.registerFunction("Promise_then", function(self, args, argNodes) {
+    var fn = args.length && args[0].getFunctionType();
+    var defs6 = infer.cx().definitions.ecma6
+    if (!fn || !defs6) return self;
+
+    var result = new infer.Obj(defs6["Promise.prototype"]);
+    var value = result.defProp(":t", argNodes && argNodes[0]), ty;
+    if (fn.retval.isEmpty() && (ty = self.getType()) instanceof infer.Obj && ty.hasProp(":t"))
+      ty.getProp(":t").propagate(value, WG_PROMISE_KEEP_VALUE);
+    fn.retval.propagate(new PromiseResolvesTo(value));
+    return result;
+  });
+
+  infer.registerFunction("getOwnPropertySymbols", function(_self, args) {
+    if (!args.length) return infer.ANull
+    var result = new infer.AVal
+    args[0].forAllProps(function(prop, _val, local) {
+      if (local && prop.charAt(0) == ":") result.addType(infer.getSymbol(prop.slice(1)))
+    })
+    return result
+  })
+
+  infer.registerFunction("getSymbol", function(_self, _args, argNodes) {
+    if (argNodes.length && argNodes[0].type == "Literal" && typeof argNodes[0].value == "string")
+      return infer.getSymbol(argNodes[0].value)
+    else
+      return infer.ANull
+  })
+
   return exports;
 });
old mode 100644
new mode 100755
--- a/devtools/client/sourceeditor/tern/infer.js
+++ b/devtools/client/sourceeditor/tern/infer.js
@@ -1,65 +1,71 @@
 // Main type inference engine
 
-// Walks an AST, building up a graph of abstract values and contraints
+// Walks an AST, building up a graph of abstract values and constraints
 // 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) {
+(function(root, mod) {
   if (typeof exports == "object" && typeof module == "object") // CommonJS
-    return mod(exports, require("acorn/acorn"), require("acorn/acorn_loose"), require("acorn/util/walk"),
+    return mod(exports, require("acorn/acorn"), require("acorn/acorn_loose"), require("acorn/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) {
+    return define(["exports", "acorn/acorn", "acorn/acorn_loose", "acorn/walk", "./def", "./signal"], mod);
+  mod(root.tern || (root.tern = {}), acorn, acorn, acorn.walk, tern.def, tern.signal); // Plain browser env
+})(this, 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);
+    if (!type || type == parent || maxDepth && maxDepth < -3) return "?";
+    return type.toString(maxDepth, parent);
   };
 
   // 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() {},
+    getObjType: function() {},
+    getSymbolType: function() {},
     getType: function() {},
     gatherProperties: function() {},
     propagatesTo: function() {},
     typeHint: function() {},
-    propHint: function() {}
+    propHint: function() {},
+    toString: function() { return "?"; }
   });
 
   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 WG_DEFAULT = 100, WG_NEW_INSTANCE = 90, WG_MADEUP_PROTO = 10,
+      WG_MULTI_MEMBER = 6, WG_CATCH_ERROR = 6,
+      WG_PHANTOM_OBJ = 1,
+      WG_GLOBAL_THIS = 90, WG_SPECULATIVE_THIS = 2, WG_SPECULATIVE_PROTO_THIS = 4;
 
   var AVal = exports.AVal = function() {
     this.types = [];
     this.forward = null;
     this.maxWeight = 0;
   };
   AVal.prototype = extend(ANull, {
     addType: function(type, weight) {
@@ -76,63 +82,99 @@
       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);
+      if (target == ANull || (target instanceof Type && this.forward && this.forward.length > 2)) 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));
+        this.propagate(new GetProp(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; },
+    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];
     },
+    getObjType: function() {
+      var seen = null;
+      for (var i = this.types.length - 1; i >= 0; --i) {
+        var type = this.types[i];
+        if (!(type instanceof Obj)) continue;
+        if (type.name) return type;
+        if (!seen) seen = type;
+      }
+      return seen;
+    },
+
+    getSymbolType: function() {
+      for (var i = this.types.length - 1; i >= 0; --i)
+        if (this.types[i] instanceof Sym) 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];
+      if (this.types.length === 0 && guess !== false) return this.makeupType();
+      if (this.types.length === 1) return this.types[0];
       return canonicalType(this.types);
     },
 
-    computedPropType: function() {
-      if (!this.propertyOf || !this.propertyOf.hasProp("<i>")) return null;
-      var computedProp = this.propertyOf.getProp("<i>");
-      if (computedProp == this) return null;
-      return computedProp.getType();
+    toString: function(maxDepth, parent) {
+      if (this.types.length == 0) return toString(this.makeupType(), maxDepth, parent);
+      if (this.types.length == 1) return toString(this.types[0], maxDepth, parent);
+      var simplified = simplifyTypes(this.types);
+      if (simplified.length > 2) return "?";
+      return simplified.map(function(tp) { return toString(tp, maxDepth, parent); }).join("|");
+    },
+
+    makeupPropType: function(obj) {
+      var propName = this.propertyName;
+
+      var protoProp = obj.proto && obj.proto.hasProp(propName);
+      if (protoProp) {
+        var fromProto = protoProp.getType();
+        if (fromProto) return fromProto;
+      }
+
+      if (propName != "<i>") {
+        var computedProp = obj.hasProp("<i>");
+        if (computedProp) return computedProp.getType();
+      } else if (obj.props["<i>"] != this) {
+        for (var prop in obj.props) {
+          var val = obj.props[prop];
+          if (!val.isEmpty()) return val.getType();
+        }
+      }
     },
 
     makeupType: function() {
-      var computed = this.computedPropType();
+      var computed = this.propertyOf && this.makeupPropType(this.propertyOf);
       if (computed) return computed;
 
       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;}
       }
 
@@ -173,16 +215,69 @@
         var prop = this.forward[i].propHint();
         if (prop) f(prop, null, 0);
       }
       var guessed = this.makeupType();
       if (guessed) guessed.gatherProperties(f);
     }
   });
 
+  function similarAVal(a, b, depth) {
+    var typeA = a.getType(false), typeB = b.getType(false);
+    if (!typeA || !typeB) return true;
+    return similarType(typeA, typeB, depth);
+  }
+
+  function similarType(a, b, depth) {
+    if (!a || depth >= 5) return b;
+    if (!a || a == b) return a;
+    if (!b) return a;
+    if (a.constructor != b.constructor) return false;
+    if (a.constructor == Arr) {
+      var innerA = a.getProp("<i>").getType(false);
+      if (!innerA) return b;
+      var innerB = b.getProp("<i>").getType(false);
+      if (!innerB || similarType(innerA, innerB, depth + 1)) return b;
+    } else if (a.constructor == Obj) {
+      var propsA = 0, propsB = 0, same = 0;
+      for (var prop in a.props) {
+        propsA++;
+        if (prop in b.props && similarAVal(a.props[prop], b.props[prop], depth + 1))
+          same++;
+      }
+      for (var prop in b.props) propsB++;
+      if (propsA && propsB && same < Math.max(propsA, propsB) / 2) return false;
+      return propsA > propsB ? a : b;
+    } else if (a.constructor == Fn) {
+      if (a.args.length != b.args.length ||
+          !a.args.every(function(tp, i) { return similarAVal(tp, b.args[i], depth + 1); }) ||
+          !similarAVal(a.retval, b.retval, depth + 1) || !similarAVal(a.self, b.self, depth + 1))
+        return false;
+      return a;
+    } else {
+      return false;
+    }
+  }
+
+  var simplifyTypes = exports.simplifyTypes = function(types) {
+    var found = [];
+    outer: for (var i = 0; i < types.length; ++i) {
+      var tp = types[i];
+      for (var j = 0; j < found.length; j++) {
+        var similar = similarType(tp, found[j], 0);
+        if (similar) {
+          found[j] = similar;
+          continue outer;
+        }
+      }
+      found.push(tp);
+    }
+    return found;
+  };
+
   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) {
@@ -208,115 +303,121 @@
       }
       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);
+  var constraint = exports.constraint = function(methods) {
+    var ctor = function() {
+      this.origin = cx.curOrigin;
+      this.construct.apply(this, arguments);
+    };
+    ctor.prototype = Object.create(ANull);
     for (var m in methods) if (methods.hasOwnProperty(m)) ctor.prototype[m] = methods[m];
     return ctor;
   };
 
-  var PropIsSubset = constraint("prop, target", {
+  var GetProp = constraint({
+    construct: function(prop, target) {
+      this.prop = prop; this.target = target;
+    },
     addType: function(type, weight) {
       if (type.getProp)
         type.getProp(this.prop).propagate(this.target, weight);
     },
     propHint: function() { return this.prop; },
     propagatesTo: function() {
       if (this.prop == "<i>" || !/[^\w_]/.test(this.prop))
         return {target: this.target, pathExt: "." + this.prop};
     }
   });
 
-  var PropHasSubset = exports.PropHasSubset = constraint("prop, type, originNode", {
+  var DefProp = exports.PropHasSubset = exports.DefProp = constraint({
+    construct: function(prop, type, originNode) {
+      this.prop = prop; this.type = type; this.originNode = originNode;
+    },
     addType: function(type, weight) {
       if (!(type instanceof Obj)) return;
       var prop = type.defProp(this.prop, this.originNode);
-      prop.origin = this.origin;
+      if (!prop.origin) prop.origin = this.origin;
       this.type.propagate(prop, weight);
     },
     propHint: function() { return this.prop; }
   });
 
-  var ForAllProps = constraint("c", {
+  var ForAllProps = constraint({
+    construct: function(c) { this.c = 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 result = body();
+    cx.disabledComputing = cx.disabledComputing.prev;
+    return result;
   }
-  var IsCallee = exports.IsCallee = constraint("self, args, argNodes, retval", {
-    init: function() {
-      Constraint.prototype.init();
+  var IsCallee = exports.IsCallee = constraint({
+    construct: function(self, args, argNodes, retval) {
+      this.self = self; this.args = args; this.argNodes = argNodes; this.retval = retval;
       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;
+      var compute = fn.computeRet, result = fn.retval
       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);
+        if (d.fn == fn || fn.originNode && d.fn.originNode == fn.originNode) compute = null;
+      if (compute) {
+        var old = cx.disabledComputing;
+        cx.disabledComputing = this.disabled;
+        result = compute(this.self, this.args, this.argNodes)
+        cx.disabledComputing = old;
+      }
+      maybeIterator(fn, result).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();
+  var HasMethodCall = constraint({
+    construct: function(propName, args, argNodes, retval) {
+      this.propName = propName; this.args = args; this.argNodes = argNodes; this.retval = retval;
       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", {
+  var IsCtor = exports.IsCtor = constraint({
+    construct: function(target, noReuse) {
+      this.target = target; this.noReuse = noReuse;
+    },
     addType: function(f, weight) {
       if (!(f instanceof Fn)) return;
+      if (cx.parent && !cx.parent.options.reuseInstances) this.noReuse = true;
       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;
@@ -326,65 +427,85 @@
       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", {
+  var IsProto = exports.IsProto = constraint({
+    construct: function(ctor, target) {
+      this.ctor = ctor; this.target = 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", {
+  var FnPrototype = constraint({
+    construct: function(fn) { this.fn = 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", {
+  var IsAdded = constraint({
+    construct: function(other, target) {
+      this.other = other; this.target = 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", {
+  var IfObj = exports.IfObj = constraint({
+    construct: function(target) { this.target = target; },
     addType: function(t, weight) {
       if (t instanceof Obj) this.target.addType(t, weight);
     },
     propagatesTo: function() { return this.target; }
   });
 
-  var SpeculativeThis = constraint("obj, ctor", {
+  var SpeculativeThis = constraint({
+    construct: function(obj, ctor) { this.obj = obj; this.ctor = ctor; },
     addType: function(tp) {
-      if (tp instanceof Fn && tp.self && tp.self.isEmpty())
-        tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_THIS);
+      if (tp instanceof Fn && tp.self)
+        tp.self.addType(getInstance(this.obj, this.ctor), WG_SPECULATIVE_PROTO_THIS);
     }
   });
 
-  var Muffle = constraint("inner, weight", {
+  var HasProto = constraint({
+    construct: function(obj) { this.obj = obj },
+    addType: function(tp) {
+      if (tp instanceof Obj && this.obj.proto == cx.protos.Object)
+        this.obj.replaceProto(tp)
+    }
+  });
+
+  var Muffle = constraint({
+    construct: function(inner, weight) {
+      this.inner = inner; this.weight = 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(); }
   });
 
@@ -405,81 +526,93 @@
     constructor: Prim,
     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);
     }
   });
 
+  function isInteger(str) {
+    var c0 = str.charCodeAt(0)
+    if (c0 >= 48 && c0 <= 57) return !/\D/.test(str)
+    else return false
+  }
+
   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, {
     constructor: Obj,
     toString: function(maxDepth) {
-      if (!maxDepth && this.name) return this.name;
+      if (maxDepth == null) maxDepth = 0;
+      if (maxDepth <= 0 && 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));
+          props.push(prop + ": " + toString(this.props[prop], maxDepth - 1, this));
         else
           props.push(prop);
       }
       props.sort();
       if (etc) props.push("...");
       return "{" + props.join(", ") + "}";
     },
     hasProp: function(prop, searchProto) {
+      if (isInteger(prop)) prop = this.normalizeIntegerProp(prop)
       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 (found.maybePurge) found.maybePurge = false;
         if (originNode && !found.originNode) found.originNode = originNode;
         return found;
       }
       if (prop == "__proto__" || prop == "✖") return ANull;
+      if (isInteger(prop)) prop = this.normalizeIntegerProp(prop)
 
       var av = this.maybeProps && this.maybeProps[prop];
       if (av) {
         delete this.maybeProps[prop];
         this.maybeUnregProtoPropHandler();
       } else {
         av = new AVal;
         av.propertyOf = this;
+        av.propertyName = prop;
       }
 
       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;
+      if (isInteger(prop)) prop = this.normalizeIntegerProp(prop)
       var av = this.ensureMaybeProps()[prop] = new AVal;
       av.propertyOf = this;
+      av.propertyName = prop;
       return av;
     },
+    normalizeIntegerProp: function(_) { return "<i>" },
     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) {
@@ -491,27 +624,35 @@
       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);
     },
+    replaceProto: function(proto) {
+      if (this.proto && this.maybeProps)
+        this.proto.unregPropHandler(this)
+      this.proto = proto
+      if (this.maybeProps)
+        this.proto.forAllProps(this)
+    },
     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;
+      av.types.length = 0;
     },
     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) {
@@ -530,43 +671,45 @@
       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>")
+      for (var prop in this.props) if (prop != "<i>" && prop.charAt(0) != ":")
         f(prop, this, depth);
       if (this.proto) this.proto.gatherProperties(f, depth + 1);
-    }
+    },
+    getObjType: function() { return this; }
   });
 
-  var Fn = exports.Fn = function(name, self, args, argNames, retval) {
+  var Fn = exports.Fn = function(name, self, args, argNames, retval, generator) {
     Obj.call(this, cx.protos.Function, name);
     this.self = self;
     this.args = args;
     this.argNames = argNames;
     this.retval = retval;
+    this.generator = generator
   };
   Fn.prototype = extend(Obj.prototype, {
     constructor: Fn,
     toString: function(maxDepth) {
-      if (maxDepth) maxDepth--;
-      var str = "fn(";
+      if (maxDepth == null) maxDepth = 0;
+      var str = this.generator ? "fn*(" : "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 += maxDepth > -3 ? toString(this.args[i], maxDepth - 1, this) : "?";
       }
       str += ")";
       if (!this.retval.isEmpty())
-        str += " -> " + toString(this.retval.getType(), maxDepth, this);
+        str += " -> " + (maxDepth > -3 ? toString(this.retval, maxDepth - 1, 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");
@@ -587,27 +730,81 @@
         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);
+    Obj.call(this, cx.protos.Array)
+    var content = this.defProp("<i>")
+    if (Array.isArray(contentType)) {
+      this.tuple = contentType.length
+      for (var i = 0; i < contentType.length; i++) {
+        var prop = this.defProp(String(i))
+        contentType[i].propagate(prop)
+        prop.propagate(content)
+      }
+    } else if (contentType) {
+      this.tuple = 0
+      contentType.propagate(content)
+    }
   };
   Arr.prototype = extend(Obj.prototype, {
     constructor: Arr,
     toString: function(maxDepth) {
-      return "[" + toString(this.getProp("<i>").getType(), maxDepth, this) + "]";
+      if (maxDepth == null) maxDepth = 0
+      if (maxDepth <= -3) return "[?]"
+      var content = ""
+      if (this.tuple) {
+        var similar
+        for (var i = 0; i in this.props; i++) {
+          var type = toString(this.getProp(String(i)), maxDepth - 1, this)
+          if (similar == null)
+            similar = type
+          else if (similar != type)
+            similar = false
+          else
+            similar = type
+          content += (content ? ", " : "") + type
+        }
+        if (similar) content = similar
+      } else {
+        content = toString(this.getProp("<i>"), maxDepth - 1, this)
+      }
+      return "[" + content + "]"
+    },
+    normalizeIntegerProp: function(prop) {
+      if (+prop < this.tuple) return prop
+      else return "<i>"
     }
   });
 
+  var Sym = exports.Sym = function(name, originNode) {
+    Prim.call(this, cx.protos.Symbol, "Symbol")
+    this.symName = name
+    this.originNode = originNode
+  }
+  Sym.prototype = extend(Prim.prototype, {
+    constructor: Sym,
+    asPropName: function() { return ":" + this.symName },
+    getSymbolType: function() { return this }
+  })
+
+  exports.getSymbol = function(name, originNode) {
+    var cleanName = name.replace(/[^\w$\.]/g, "_")
+    var known = cx.symbols[cleanName]
+    if (known) {
+      if (originNode && !known.originNode) known.originNode = originNode
+      return known
+    }
+    return cx.symbols[cleanName] = new Sym(cleanName, originNode)
+  }
+
   // THE PROPERTY REGISTRY
 
   function registerProp(prop, obj) {
     var data = cx.props[prop] || (cx.props[prop] = []);
     data.push(obj);
   }
 
   function objsWithProp(prop) {
@@ -622,51 +819,59 @@
     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;
+    this.curSuperCtor = this.curSuper = null;
+    this.symbols = Object.create(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.Function = new Fn("Function.prototype", ANull, [], [], ANull);
+      cx.protos.Function.proto = cx.protos.Object;
       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.protos.Symbol = new Obj(true, "Symbol.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]);
     });
   };
 
+  exports.Context.prototype.startAnalysis = function() {
+    this.disabledComputing = this.workList = this.curSuperCtor = this.curSuper = null;
+  };
+
   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.TimedOut = function() {
     this.message = "Timed out";
     this.stack = (new Error()).stack;
-  }
+  };
   exports.TimedOut.prototype = Object.create(Error.prototype);
   exports.TimedOut.prototype.name = "infer.TimedOut";
 
   var timeout;
   exports.withTimeout = function(ms, f) {
     var end = +new Date + ms;
     var oldEnd = timeout;
     if (oldEnd && oldEnd < end) return f();
@@ -674,121 +879,134 @@
     try { return f(); }
     finally { timeout = oldEnd; }
   };
 
   exports.addOrigin = function(origin) {
     if (cx.origins.indexOf(origin) < 0) cx.origins.push(origin);
   };
 
-  var baseMaxWorkDepth = 20, reduceMaxWorkDepth = .0001;
+  var baseMaxWorkDepth = 20, reduceMaxWorkDepth = 0.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) {
-        if (timeout && +new Date >= timeout)
-          throw new exports.TimedOut();
-        depth = list[i + 3] + 1;
-        list[i + 1].addType(list[i], list[i + 2]);
-      }
-      return ret;
-    } finally {
-      cx.workList = null;
+    var ret = f(add);
+    for (var i = 0; i < list.length; i += 4) {
+      if (timeout && +new Date >= timeout)
+        throw new exports.TimedOut();
+      depth = list[i + 3] + 1;
+      list[i + 1].addType(list[i], list[i + 2]);
     }
+    cx.workList = null;
+    return ret;
+  }
+
+  function withSuper(ctor, obj, f) {
+    var oldCtor = cx.curSuperCtor, oldObj = cx.curSuper
+    cx.curSuperCtor = ctor; cx.curSuper = obj
+    var result = f()
+    cx.curSuperCtor = oldCtor; cx.curSuper = oldObj
+    return result
   }
 
   // SCOPES
 
-  var Scope = exports.Scope = function(prev) {
+  var Scope = exports.Scope = function(prev, originNode, isBlock) {
     Obj.call(this, prev || true);
     this.prev = prev;
+    this.originNode = originNode
+    this.isBlock = !!isBlock
   };
   Scope.prototype = extend(Obj.prototype, {
     constructor: Scope,
     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);
       }
     }
   });
 
+  function functionScope(scope) {
+    while (scope.isBlock) scope = scope.prev
+    return scope
+  }
+
+
   // RETVAL COMPUTATION HEURISTICS
 
   function maybeInstantiate(scope, score) {
-    if (scope.fnType)
-      scope.fnType.instantiateScore = (scope.fnType.instantiateScore || 0) + score;
+    var fn = functionScope(scope).fnType
+    if (fn) fn.instantiateScore = (fn.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);
+  function maybeTagAsInstantiated(node, fn) {
+    var score = fn.instantiateScore;
+    if (!cx.disabledComputing && score && fn.args.length && nodeSmallerThan(node, score * 5)) {
+      maybeInstantiate(functionScope(fn.originNode.scope.prev), score / 2);
+      setFunctionInstantiated(node, fn);
       return true;
     } else {
-      scope.fnType.instantiateScore = null;
+      fn.instantiateScore = null;
     }
   }
 
-  function setFunctionInstantiated(node, scope) {
-    var fn = scope.fnType;
+  function setFunctionInstantiated(node, fn) {
     // 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);
-        scopeCopy.originNode = scope.originNode;
+        var scope = node.scope
+        var scopeCopy = new Scope(scope.prev, scope.originNode);
         for (var v in scope.props) {
           var local = scopeCopy.defProp(v, scope.props[v].originNode);
           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);
+        scopeCopy.fnType = new Fn(fn.name, self, args, argNames, ANull, fn.generator);
+        scopeCopy.fnType.originNode = fn.originNode;
         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;
+        node.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;
+  function maybeTagAsGeneric(fn) {
+    var 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) {
@@ -809,383 +1027,701 @@
 
     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();
+      var parsed = p.parseType(true);
+      fn.computeRet = parsed.apply ? parsed : function() { return parsed; };
       fn.computeRetSource = foundPath;
       return true;
     }
   }
 
   // SCOPE GATHERING PASS
 
   function addVar(scope, nameNode) {
     return scope.defProp(nameNode.name, nameNode);
   }
+  function patternName(node) {
+    if (node.type == "Identifier") return node.name
+    if (node.type == "AssignmentPattern") return patternName(node.left)
+    if (node.type == "ObjectPattern") return "{" + node.properties.map(function(e) { return patternName(e.value) }).join(", ") + "}"
+    if (node.type == "ArrayPattern") return "[" + node.elements.map(patternName).join(", ") + "]"
+    if (node.type == "RestElement") return "..." + patternName(node.argument)
+    return "_"
+  }
 
-  var scopeGatherer = walk.make({
+  function isBlockScopedDecl(node) {
+    return node.type == "VariableDeclaration" && node.kind != "var" ||
+      node.type == "FunctionDeclaration" ||
+      node.type == "ClassDeclaration";
+  }
+
+  function patternScopes(inner, outer) {
+    return {inner: inner, outer: outer || inner}
+  }
+
+  var scopeGatherer = exports.scopeGatherer = walk.make({
+    VariablePattern: function(node, scopes) {
+      if (scopes.inner) addVar(scopes.inner, node)
+    },
+    AssignmentPattern: function(node, scopes, c) {
+      c(node.left, scopes, "Pattern")
+      c(node.right, scopes.outer, "Expression")
+    },
+    AssignmentExpression: function(node, scope, c) {
+      if (node.left.type == "MemberExpression")
+        c(node.left, scope, "Expression")
+      else
+        c(node.left, patternScopes(false, scope), "Pattern")
+      c(node.right, scope, "Expression")
+    },
     Function: function(node, scope, c) {
-      var inner = node.body.scope = new Scope(scope);
-      inner.originNode = node;
-      var argVals = [], argNames = [];
+      var inner = node.scope = new Scope(scope, 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));
+        var param = node.params[i]
+        argNames.push(patternName(param))
+        if (param.type == "Identifier") {
+          argVals.push(addVar(inner, param))
+        } else {
+          var arg = new AVal
+          argVals.push(arg)
+          arg.originNode = param
+          c(param, patternScopes(inner), "Pattern")
+        }
       }
-      inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull);
+      inner.fnType = new Fn(node.id && node.id.name, new AVal, argVals, argNames, ANull, node.generator)
       inner.fnType.originNode = node;
       if (node.id) {
         var decl = node.type == "FunctionDeclaration";
         addVar(decl ? scope : inner, node.id);
       }
-      c(node.body, inner, "ScopeBody");
+      c(node.body, inner, node.expression ? "Expression" : "Statement");
+    },
+    BlockStatement: function(node, scope, c) {
+      if (!node.scope && node.body.some(isBlockScopedDecl))
+        scope = node.scope = new Scope(scope, node, true)
+      walk.base.BlockStatement(node, scope, c)
     },
     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.handler.param.type == "Identifier") {
+          var v = addVar(scope, node.handler.param);
+          c(node.handler.body, scope, "Statement");
+          var e5 = cx.definitions.ecma5;
+          if (e5 && v.isEmpty()) getInstance(e5["Error.prototype"]).propagate(v, WG_CATCH_ERROR);
+        } else {
+          c(node.handler.param, patternScopes(scope), "Pattern")
+        }
       }
       if (node.finalizer) c(node.finalizer, scope, "Statement");
     },
     VariableDeclaration: function(node, scope, c) {
+      var targetScope = node.kind == "var" ? functionScope(scope) : scope
       for (var i = 0; i < node.declarations.length; ++i) {
         var decl = node.declarations[i];
-        addVar(scope, decl.id);
+        c(decl.id, patternScopes(targetScope, scope), "Pattern")
         if (decl.init) c(decl.init, scope, "Expression");
       }
+    },
+    ClassDeclaration: function(node, scope, c) {
+      addVar(scope, node.id)
+      if (node.superClass) c(node.superClass, scope, "Expression")
+      for (var i = 0; i < node.body.body.length; i++)
+        c(node.body.body[i], scope)
+    },
+    ForInStatement: function(node, scope, c) {
+      if (!node.scope && isBlockScopedDecl(node.left))
+        scope = node.scope = new Scope(scope, node, true)
+      walk.base.ForInStatement(node, scope, c)
+    },
+    ForStatement: function(node, scope, c) {
+      if (!node.scope && node.init && isBlockScopedDecl(node.init))
+        scope = node.scope = new Scope(scope, node, true)
+      walk.base.ForStatement(node, scope, c)
+    },
+    ImportDeclaration: function(node, scope) {
+      for (var i = 0; i < node.specifiers.length; i++)
+        addVar(scope, node.specifiers[i].local)
     }
   });
+  scopeGatherer.ForOfStatement = scopeGatherer.ForInStatement
 
   // 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);
+  var propName = exports.propName = function(node, inferInScope) {
+    var key = node.property || node.key;
+    if (!node.computed && key.type == "Identifier") return key.name;
+    if (key.type == "Literal") {
+      if (typeof key.value == "string") return key.value
+      if (typeof key.value == "number") return String(key.value)
+    }
+    if (inferInScope) {
+      var symName = symbolName(infer(key, inferInScope))
+      if (symName) return node.propName = symName
+    } else if (node.propName) {
+      return node.propName
+    }
     return "<i>";
   }
+  function symbolName(val) {
+    var sym = val.getSymbolType()
+    if (sym) return sym.asPropName()
+  }
 
   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) {
+  function literalType(node) {
+    if (node.regex) return getInstance(cx.protos.RegExp);
+    switch (typeof node.value) {
     case "boolean": return cx.bool;
     case "number": return cx.num;
     case "string": return cx.str;
     case "object":
     case "function":
-      if (!val) return ANull;
+      if (!node.value) return ANull;
       return getInstance(cx.protos.RegExp);
     }
   }
 
+  function join(a, b) {
+    if (a == b || b == ANull) return a
+    if (a == ANull) return b
+    var joined = new AVal
+    a.propagate(joined)
+    b.propagate(joined)
+    return joined
+  }
+
+  function connectParams(node, scope) {
+    for (var i = 0; i < node.params.length; i++) {
+      var param = node.params[i]
+      if (param.type == "Identifier") continue
+      connectPattern(param, scope, node.scope.fnType.args[i])
+    }
+  }
+
+  function ensureVar(node, scope) {
+    return scope.hasProp(node.name) || cx.topScope.defProp(node.name, node)
+  }
+
+  var inferPatternVisitor = exports.inferPatternVisitor = {
+    Identifier: function(node, scope, source) {
+      source.propagate(ensureVar(node, scope))
+    },
+    MemberExpression: function(node, scope, source) {
+      var obj = infer(node.object, scope)
+      var pName = propName(node, scope)
+      obj.propagate(new DefProp(pName, source, node.property))
+    },
+    RestElement: function(node, scope, source) {
+      connectPattern(node.argument, scope, new Arr(source))
+    },
+    ObjectPattern: function(node, scope, source) {
+      for (var i = 0; i < node.properties.length; ++i) {
+        var prop = node.properties[i]
+        connectPattern(prop.value, scope, source.getProp(prop.key.name))
+      }
+    },
+    ArrayPattern: function(node, scope, source) {
+      for (var i = 0; i < node.elements.length; i++)
+        if (node.elements[i])
+          connectPattern(node.elements[i], scope, source.getProp(String(i)))
+    },
+    AssignmentPattern: function(node, scope, source) {
+      connectPattern(node.left, scope, join(source, infer(node.right, scope)))
+    }
+  }
+
+  function connectPattern(node, scope, source) {
+    var connecter = inferPatternVisitor[node.type]
+    if (connecter) connecter(node, scope, source)
+  }
+
+  function getThis(scope) {
+    var fnScope = functionScope(scope)
+    return fnScope.fnType ? fnScope.fnType.self : fnScope
+  }
+
+  function maybeAddPhantomObj(obj) {
+    if (!obj.isEmpty() || !obj.propertyOf) return
+    obj.propertyOf.getProp(obj.propertyName).addType(new Obj, WG_PHANTOM_OBJ)
+    maybeAddPhantomObj(obj.propertyOf)
+  }
+
+  function inferClass(node, scope, name) {
+    if (!name && node.id) name = node.id.name
+
+    var sup = cx.protos.Object, supCtor, delayed
+    if (node.superClass) {
+      if (node.superClass.type == "Literal" && node.superClass.value == null) {
+        sup = null
+      } else {
+        var supVal = infer(node.superClass, scope), supProto
+        supCtor = supVal.getFunctionType()
+        if (supCtor && (supProto = supCtor.getProp("prototype").getObjType())) {
+          sup = supProto
+        } else {
+          supCtor = supVal
+          delayed = supVal.getProp("prototype")
+        }
+      }
+    }
+    var proto = new Obj(sup, name && name + ".prototype")
+    if (delayed) delayed.propagate(new HasProto(proto))
+
+    return withSuper(supCtor, delayed || sup, function() {
+      var ctor, body = node.body.body
+      for (var i = 0; i < body.length; i++)
+        if (body[i].kind == "constructor") ctor = body[i].value
+      var fn = node.objType = ctor ? infer(ctor, scope) : new Fn(name, ANull, [], null, ANull)
+      fn.originNode = node.id || ctor || node
+
+      var inst = getInstance(proto, fn)
+      fn.self.addType(inst)
+      fn.defProp("prototype", node).addType(proto)
+      for (var i = 0; i < body.length; i++) {
+        var method = body[i], target
+        if (method.kind == "constructor") continue
+        var pName = propName(method, scope)
+        if (pName == "<i>" || method.kind == "set") {
+          target = ANull
+        } else {
+          target = (method.static ? fn : proto).defProp(pName, method.key)
+          target.initializer = true
+          if (method.kind == "get") target = new IsCallee(inst, [], null, target)
+        }
+        infer(method.value, scope, target)
+        var methodFn = target.getFunctionType()
+        if (methodFn) methodFn.self.addType(inst)
+      }
+      return fn
+    })
+  }
+
+  function arrayLiteralType(elements, scope, inner) {
+    var tuple = elements.length > 1 && elements.length < 6
+    if (tuple) {
+      var homogenous = true, litType
+      for (var i = 0; i < elements.length; i++) {
+        var elt = elements[i]
+        if (!elt)
+          tuple = false
+        else if (elt.type != "Literal" || (litType && litType != typeof elt.value))
+          homogenous = false
+        else
+          litType = typeof elt.value
+      }
+      if (homogenous) tuple = false
+    }
+
+    if (tuple) {
+      var types = []
+      for (var i = 0; i < elements.length; ++i)
+        types.push(inner(elements[i], scope))
+      return new Arr(types)
+    } else if (elements.length < 2) {
+      return new Arr(elements[0] && inner(elements[0], scope))
+    } else {
+      var eltVal = new AVal
+      for (var i = 0; i < elements.length; i++)
+        if (elements[i]) inner(elements[i], scope).propagate(eltVal)
+      return new Arr(eltVal)
+    }
+  }
+
   function ret(f) {
-    return function(node, scope, c, out, name) {
-      var r = f(node, scope, c, name);
+    return function(node, scope, out, name) {
+      var r = f(node, scope, name);
       if (out) r.propagate(out);
       return r;
     };
   }
   function fill(f) {
-    return function(node, scope, c, out, name) {
+    return function(node, scope, out, name) {
       if (!out) out = new AVal;
-      f(node, scope, c, out, name);
+      f(node, scope, 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);
+  var inferExprVisitor = exports.inferExprVisitor = {
+    ArrayExpression: ret(function(node, scope) {
+      return arrayLiteralType(node.elements, scope, infer)
+    }),
+    ObjectExpression: ret(function(node, scope, name) {
+      var proto = true, waitForProto
+      for (var i = 0; i < node.properties.length; ++i) {
+        var prop = node.properties[i]
+        if (prop.key.name == "__proto__") {
+          if (prop.value.type == "Literal" && prop.value.value == null) {
+            proto = null
+          } else {
+            var protoVal = infer(prop.value, scope), known = protoVal.getObjType()
+            if (known) proto = known
+            else waitForProto = protoVal
+          }
+        }
       }
-      return new Arr(eltval);
-    }),
-    ObjectExpression: ret(function(node, scope, c, name) {
-      var obj = node.objType = new Obj(true, name);
+
+      var obj = node.objType = new Obj(proto, name);
+      if (waitForProto) waitForProto.propagate(new HasProto(obj))
       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;
+      withSuper(null, waitForProto || proto, function() {
+        for (var i = 0; i < node.properties.length; ++i) {
+          var prop = node.properties[i], key = prop.key;
+          if (prop.value.name == "✖" || prop.key.name == "__proto__") continue;
+
+          var name = propName(prop, scope), target
+          if (name == "<i>" || prop.kind == "set") {
+            target = ANull;
+          } else {
+            var val = target = obj.defProp(name, key);
+            val.initializer = true;
+            if (prop.kind == "get")
+              target = new IsCallee(obj, [], null, val);
+          }
+          infer(prop.value, scope, target, name);
+          if (prop.value.type == "FunctionExpression")
+            prop.value.scope.fnType.self.addType(obj, WG_SPECULATIVE_THIS);
         }
-        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;
+    FunctionExpression: ret(function(node, scope, name) {
+      var inner = node.scope, fn = inner.fnType;
       if (name && !fn.name) fn.name = name;
-      c(node.body, scope, "ScopeBody");
-      maybeTagAsInstantiated(node, inner) || maybeTagAsGeneric(inner);
+      connectParams(node, inner)
+      if (node.expression)
+        infer(node.body, inner, inner.fnType.retval = new AVal)
+      else
+        walk.recursive(node.body, inner, null, inferWrapper, "Statement")
+      if (node.type == "ArrowFunctionExpression") {
+        getThis(scope).propagate(fn.self)
+        fn.self = ANull
+      }
+      maybeTagAsInstantiated(node, fn) || maybeTagAsGeneric(fn);
       if (node.id) inner.getProp(node.id.name).addType(fn);
       return fn;
     }),
-    SequenceExpression: ret(function(node, scope, c) {
+    ClassExpression: ret(inferClass),
+    SequenceExpression: ret(function(node, scope) {
       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);
+        infer(node.expressions[i], scope, ANull);
+      return infer(node.expressions[l], scope);
     }),
-    UnaryExpression: ret(function(node, scope, c) {
-      infer(node.argument, scope, c, ANull);
+    UnaryExpression: ret(function(node, scope) {
+      infer(node.argument, scope, ANull);
       return unopResultType(node.operator);
     }),
-    UpdateExpression: ret(function(node, scope, c) {
-      infer(node.argument, scope, c, ANull);
+    UpdateExpression: ret(function(node, scope) {
+      infer(node.argument, scope, ANull);
       return cx.num;
     }),
-    BinaryExpression: ret(function(node, scope, c) {
+    BinaryExpression: ret(function(node, scope) {
       if (node.operator == "+") {
-        var lhs = infer(node.left, scope, c);
-        var rhs = infer(node.right, scope, c);
+        var lhs = infer(node.left, scope);
+        var rhs = infer(node.right, scope);
         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);
+        infer(node.left, scope, ANull);
+        infer(node.right, scope, ANull);
         return binopIsBoolean(node.operator) ? cx.bool : cx.num;
       }
     }),
-    AssignmentExpression: ret(function(node, scope, c) {
-      var rhs, name, pName;
+    AssignmentExpression: ret(function(node, scope, name) {
+      var rhs, 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;
+        pName = propName(node.left, scope)
+        if (!name)
+          name = node.left.object.type == "Identifier" ? node.left.object.name + "." + pName : pName
+      } else if (!name && node.left.type == "Identifier") {
+        name = node.left.name
       }
 
-      if (node.operator != "=" && node.operator != "+=") {
-        infer(node.right, scope, c, ANull);
+      if (node.operator && node.operator != "=" && node.operator != "+=") {
+        infer(node.right, scope, ANull);
         rhs = cx.num;
       } else {
-        rhs = infer(node.right, scope, c, null, name);
+        rhs = infer(node.right, scope, null, name);
       }
 
       if (node.left.type == "MemberExpression") {
-        var obj = infer(node.left.object, scope, c);
+        var obj = infer(node.left.object, scope);
         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));
+                obj.propagate(new DefProp(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);
+
+        obj.propagate(new DefProp(pName, rhs, node.left.property));
+        maybeAddPhantomObj(obj)
+        if (node.right.type == "FunctionExpression")
+          obj.propagate(node.right.scope.fnType.self, WG_SPECULATIVE_THIS);
+      } else {
+        connectPattern(node.left, scope, rhs)
       }
       return rhs;
     }),
-    LogicalExpression: fill(function(node, scope, c, out) {
-      infer(node.left, scope, c, out);
-      infer(node.right, scope, c, out);
+    LogicalExpression: fill(function(node, scope, out) {
+      infer(node.left, scope, out);
+      infer(node.right, scope, 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);
+    ConditionalExpression: fill(function(node, scope, out) {
+      infer(node.test, scope, ANull);
+      infer(node.consequent, scope, out);
+      infer(node.alternate, scope, out);
     }),
-    NewExpression: fill(function(node, scope, c, out, name) {
+    NewExpression: fill(function(node, scope, 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);
+        args.push(infer(node.arguments[i], scope));
+      var callee = infer(node.callee, scope);
       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) {
+    CallExpression: fill(function(node, scope, out) {
       for (var i = 0, args = []; i < node.arguments.length; ++i)
-        args.push(infer(node.arguments[i], scope, c));
+        args.push(infer(node.arguments[i], scope));
+      var outerFn = functionScope(scope).fnType
       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)
+        var self = infer(node.callee.object, scope);
+        var pName = propName(node.callee, scope)
+        if (outerFn && (pName == "call" || pName == "apply") &&
+            outerFn.args.indexOf(self) > -1)
           maybeInstantiate(scope, 30);
         self.propagate(new HasMethodCall(pName, args, node.arguments, out));
+      } else if (node.callee.type == "Super" && cx.curSuperCtor) {
+        cx.curSuperCtor.propagate(new IsCallee(getThis(scope), args, node.arguments, out))
       } else {
-        var callee = infer(node.callee, scope, c);
-        if (scope.fnType && scope.fnType.args.indexOf(callee) > -1)
+        var callee = infer(node.callee, scope);
+        if (outerFn && outerFn.args.indexOf(callee) > -1)
           maybeInstantiate(scope, 30);
         var knownFn = callee.getFunctionType();
-        if (knownFn && knownFn.instantiateScore && scope.fnType)
+        if (knownFn && knownFn.instantiateScore && outerFn)
           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);
+    MemberExpression: fill(function(node, scope, out) {
+      var name = propName(node), wg;
       if (name == "<i>") {
-        var propType = infer(node.property, scope, c);
-        if (!propType.hasType(cx.num))
-          return prop.propagate(out, WG_MULTI_MEMBER);
+        var propType = infer(node.property, scope)
+        var symName = symbolName(propType)
+        if (symName)
+          name = node.propName = symName
+        else if (!propType.hasType(cx.num))
+          wg = WG_MULTI_MEMBER
       }
-      prop.propagate(out);
+      infer(node.object, scope).getProp(name).propagate(out, wg)
     }),
     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));
+      if (node.name == "arguments") {
+        var fnScope = functionScope(scope)
+        if (fnScope.fnType && !(node.name in fnScope.props))
+          scope.defProp(node.name, fnScope.fnType.originNode)
+            .addType(new Arr(fnScope.fnType.arguments = new AVal));
+      }
       return scope.getProp(node.name);
     }),
     ThisExpression: ret(function(_node, scope) {
-      return scope.fnType ? scope.fnType.self : cx.topScope;
+      return getThis(scope)
+    }),
+    Super: ret(function(node) {
+      return node.superType = cx.curSuper || ANull
     }),
     Literal: ret(function(node) {
-      return literalType(node.value);
+      return literalType(node);
+    }),
+    TemplateLiteral: ret(function(node, scope) {
+      for (var i = 0; i < node.expressions.length; ++i)
+        infer(node.expressions[i], scope, ANull)
+      return cx.str
+    }),
+    TaggedTemplateExpression: fill(function(node, scope, out) {
+      var args = [new Arr(cx.str)]
+      for (var i = 0; i < node.quasi.expressions.length; ++i)
+        args.push(infer(node.quasi.expressions[i], scope))
+      infer(node.tag, scope, new IsCallee(cx.topScope, args, node.quasi.expressions, out))
+    }),
+    YieldExpression: ret(function(node, scope) {
+      var output = ANull, fn = functionScope(scope).fnType
+      if (fn) {
+        if (fn.retval == ANull) fn.retval = new AVal
+        if (!fn.yieldval) fn.yieldval = new AVal
+        output = fn.retval
+      }
+      if (node.argument) {
+        if (node.delegate) {
+          infer(node.argument, scope, new HasMethodCall("next", [], null,
+                                                        new GetProp("value", output)))
+        } else {
+          infer(node.argument, scope, output)
+        }
+      }
+      return fn ? fn.yieldval : ANull
     })
   };
+  inferExprVisitor.ArrowFunctionExpression = inferExprVisitor.FunctionExpression
 
-  function infer(node, scope, c, out, name) {
-    return inferExprVisitor[node.type](node, scope, c, out, name);
+  function infer(node, scope, out, name) {
+    var handler = inferExprVisitor[node.type];
+    return handler ? handler(node, scope, out, name) : ANull;
   }
 
-  var inferWrapper = walk.make({
-    Expression: function(node, scope, c) {
-      infer(node, scope, c, ANull);
+  function loopPattern(init) {
+    return init.type == "VariableDeclaration" ? init.declarations[0].id : init
+  }
+
+  var inferWrapper = exports.inferWrapper = walk.make({
+    Expression: function(node, scope) {
+      infer(node, node.scope || scope, 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);
+      var inner = node.scope, fn = inner.fnType;
+      connectParams(node, inner)
+      c(node.body, inner, "Statement");
+      maybeTagAsInstantiated(node, fn) || maybeTagAsGeneric(fn);
+      scope.getProp(node.id.name).addType(fn)
+    },
+
+    Statement: function(node, scope, c) {
+      c(node, node.scope || scope)
     },
 
-    VariableDeclaration: function(node, scope, c) {
+    VariableDeclaration: function(node, scope) {
       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);
+        var decl = node.declarations[i];
+        if (decl.id.type == "Identifier") {
+          var prop = scope.getProp(decl.id.name);
+          if (decl.init)
+            infer(decl.init, scope, prop, decl.id.name);
+        } else if (decl.init) {
+          connectPattern(decl.id, scope, infer(decl.init, scope))
+        }
       }
     },
 
-    ReturnStatement: function(node, scope, c) {
+    ClassDeclaration: function(node, scope) {
+      scope.getProp(node.id.name).addType(inferClass(node, scope, node.id.name))
+    },
+
+    ReturnStatement: function(node, scope) {
       if (!node.argument) return;
-      var output = ANull;
-      if (scope.fnType) {
-        if (scope.fnType.retval == ANull) scope.fnType.retval = new AVal;
-        output = scope.fnType.retval;
+      var output = ANull, fn = functionScope(scope).fnType
+      if (fn) {
+        if (fn.retval == ANull) fn.retval = new AVal;
+        output = fn.retval;
       }
-      infer(node.argument, scope, c, output);
+      infer(node.argument, scope, output);
     },
 
     ForInStatement: function(node, scope, c) {
-      var source = infer(node.right, scope, c);
+      var source = infer(node.right, scope);
       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;
+        var pattern = loopPattern(node.left)
+        if (pattern.type == "Identifier") {
+          if (pattern.name in scope.props)
+            scope.getProp(pattern.name).iteratesOver = source
+          source.getProp("<i>").propagate(ensureVar(pattern, scope))
+        } else {
+          connectPattern(pattern, scope, source.getProp("<i>"))
         }
-        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); }
+    ForOfStatement: function(node, scope, c) {
+      var pattern = loopPattern(node.left), target
+      if (pattern.type == "Identifier")
+        target = ensureVar(pattern, scope)
+      else
+        connectPattern(pattern, scope, target = new AVal)
+      infer(node.right, scope, new HasMethodCall(":Symbol.iterator", [], null,
+                                                 new HasMethodCall("next", [], null,
+                                                                   new GetProp("value", target))))
+      c(node.body, scope, "Statement")
+    }
   });
 
   // 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 parse = exports.parse = function(text, options, thirdArg) {
+    if (!options || Array.isArray(options)) options = thirdArg
     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) {
+  exports.analyze = function(ast, name, scope) {
     if (typeof ast == "string") ast = parse(ast);
 
     if (!name) name = "file#" + cx.origins.length;
     exports.addOrigin(cx.curOrigin = name);
 
     if (!scope) scope = cx.topScope;
+    cx.startAnalysis();
+
     walk.recursive(ast, scope, null, scopeGatherer);
-    runPasses(passes, "preInfer", ast, scope);
+    if (cx.parent) cx.parent.signal("preInfer", ast, scope)
     walk.recursive(ast, scope, null, inferWrapper);
-    runPasses(passes, "postInfer", ast, scope);
+    if (cx.parent) cx.parent.signal("postInfer", ast, scope)
 
     cx.curOrigin = null;
   };
 
   // PURGING
 
-  exports.purgeTypes = function(origins, start, end) {
+  exports.purge = 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);
@@ -1211,87 +1747,107 @@
     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.types.length) this.maxWeight = 0;
+
     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;
-    var props = [];
     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 = {
+  function generatorResult(input, output) {
+    var retObj = new Obj(true)
+    retObj.defProp("done").addType(cx.bool)
+    output.propagate(retObj.defProp("value"))
+    var method = new Fn(null, ANull, input ? [input] : [], input ? ["?"] : [], retObj)
+    var result = new Obj(cx.definitions.ecma6 && cx.definitions.ecma6.generator_prototype || true)
+    result.defProp("next").addType(method)
+    return result
+  }
+
+  function maybeIterator(fn, output) {
+    if (!fn.generator) return output
+    if (!fn.computeRet) { // Reuse iterator objects for non-computed return types
+      if (fn.generator === true) fn.generator = generatorResult(fn.yieldval, output)
+      return fn.generator
+    }
+    return generatorResult(fn.yieldval, output)
+  }
+
+  function computeReturnType(funcNode, argNodes, scope) {
+    var fn = findType(funcNode, scope).getFunctionType()
+    if (!fn) return ANull
+    var result = fn.retval
+    if (fn.computeRet) {
+      for (var i = 0, args = []; i < argNodes.length; ++i)
+        args.push(findType(argNodes[i], scope))
+      var self = ANull
+      if (funcNode.type == "MemberExpression")
+        self = findType(funcNode.object, scope)
+      result = fn.computeRet(self, args, argNodes);
+    }
+    return maybeIterator(fn, result)
+  }
+
+  var typeFinder = exports.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);
+      return arrayLiteralType(node.elements, scope, findType)
     },
     ObjectExpression: function(node) {
       return node.objType;
     },
+    ClassExpression: function(node) {
+      return node.objType;
+    },
     FunctionExpression: function(node) {
-      return node.body.scope.fnType;
+      return node.scope.fnType;
+    },
+    ArrowFunctionExpression: function(node) {
+      return node.scope.fnType;
     },
     SequenceExpression: function(node, scope) {
       return findType(node.expressions[node.expressions.length-1], scope);
     },
     UnaryExpression: function(node) {
       return unopResultType(node.operator);
     },
     UpdateExpression: function() {
@@ -1314,133 +1870,222 @@
       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();
+      var proto = f && f.getProp("prototype").getObjType();
       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;
-      }
+      return computeReturnType(node.callee, node.arguments, scope)
     },
     MemberExpression: function(node, scope) {
-      var propN = propName(node, scope), obj = findType(node.object, scope).getType();
+      var propN = propName(node), 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;
+      return getThis(scope)
     },
     Literal: function(node) {
-      return literalType(node.value);
+      return literalType(node);
+    },
+    Super: ret(function(node) {
+      return node.superType
+    }),
+    TemplateLiteral: function() {
+      return cx.str
+    },
+    TaggedTemplateExpression: function(node, scope) {
+      return computeReturnType(node.tag, node.quasi.expressions, scope)
+    },
+    YieldExpression: function(_node, scope) {
+      var fn = functionScope(scope).fnType
+      return fn ? fn.yieldval : ANull
     }
   };
 
   function findType(node, scope) {
-    var found = typeFinder[node.type](node, scope);
-    return found;
+    var finder = typeFinder[node.type];
+    return finder ? finder(node, scope) : ANull;
   }
 
   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");
+      walk.base.Function(node, node.scope, c)
+    },
+    Property: function(node, st, c) {
+      if (node.computed) c(node.key, st, "Expression");
+      if (node.key != node.value) c(node.value, st, "Expression");
+    },
+    Statement: function(node, st, c) {
+      c(node, node.scope || st)
     },
-    TryStatement: function(node, st, c) {
-      if (node.handler)
-        c(node.handler.param, st);
-      walk.base.TryStatement(node, st, c);
+    ImportSpecifier: function(node, st, c) {
+      c(node.local, st)
     },
-    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");
-      }
+    ImportDefaultSpecifier: function(node, st, c) {
+      c(node.local, st)
+    },
+    ImportNamespaceSpecifier: function(node, st, c) {
+      c(node.local, st)
     }
   });
   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);};
+    var test = filter || function(_t, node) {
+      if (node.type == "Identifier" && node.name == "✖") return false;
+      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;
+      if (node.type == "Identifier" && node.name == "✖") 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);
   };
 
+  // Finding the expected type of something, from context
+
+  exports.parentNode = function(child, ast) {
+    var stack = [];
+    function c(node, st, override) {
+      if (node.start <= child.start && node.end >= child.end) {
+        var top = stack[stack.length - 1];
+        if (node == child) throw {found: top};
+        if (top != node) stack.push(node);
+        walk.base[override || node.type](node, st, c);
+        if (top != node) stack.pop();
+      }
+    }
+    try {
+      c(ast, null);
+    } catch (e) {
+      if (e.found) return e.found;
+      throw e;
+    }
+  };
+
+  var findTypeFromContext = exports.findTypeFromContext = {
+    ArrayExpression: function(parent, _, get) { return get(parent, true).getProp("<i>"); },
+    ObjectExpression: function(parent, node, get) {
+      for (var i = 0; i < parent.properties.length; ++i) {
+        var prop = node.properties[i];
+        if (prop.value == node)
+          return get(parent, true).getProp(prop.key.name);
+      }
+    },
+    UnaryExpression: function(parent) { return unopResultType(parent.operator); },
+    UpdateExpression: function() { return cx.num; },
+    BinaryExpression: function(parent) { return binopIsBoolean(parent.operator) ? cx.bool : cx.num; },
+    AssignmentExpression: function(parent, _, get) { return get(parent.left); },
+    LogicalExpression: function(parent, _, get) { return get(parent, true); },
+    ConditionalExpression: function(parent, node, get) {
+      if (parent.consequent == node || parent.alternate == node) return get(parent, true);
+    },
+    CallExpression: function(parent, node, get) {
+      for (var i = 0; i < parent.arguments.length; i++) {
+        var arg = parent.arguments[i];
+        if (arg == node) {
+          var calleeType = get(parent.callee).getFunctionType();
+          if (calleeType instanceof Fn)
+            return calleeType.args[i];
+          break;
+        }
+      }
+    },
+    ReturnStatement: function(_parent, node, get) {
+      var fnNode = walk.findNodeAround(node.sourceFile.ast, node.start, "Function");
+      if (fnNode) {
+        var fnType = fnNode.node.type != "FunctionDeclaration"
+          ? get(fnNode.node, true).getFunctionType()
+          : fnNode.node.scope.fnType;
+        if (fnType) return fnType.retval.getType();
+      }
+    },
+    VariableDeclarator: function(parent, node, get) {
+      if (parent.init == node) return get(parent.id)
+    }
+  };
+  findTypeFromContext.NewExpression = findTypeFromContext.CallExpression
+
+  exports.typeFromContext = function(ast, found) {
+    var parent = exports.parentNode(found.node, ast);
+    var type = null;
+    if (findTypeFromContext.hasOwnProperty(parent.type)) {
+      var finder = findTypeFromContext[parent.type];
+      type = finder && finder(parent, found.node, function(node, fromContext) {
+        var obj = {node: node, state: found.state};
+        var tp = fromContext ? exports.typeFromContext(ast, obj) : exports.expressionType(obj);
+        return tp || ANull;
+      });
+    }
+    return type || exports.expressionType(found);
+  };
+
   // 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) {
+    refFindWalker.Identifier = refFindWalker.VariablePattern = 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"); }
+    Function: function(node, _scope, c) {
+      c(node.body, node.scope, node.expression ? "Expression" : "Statement")
+    },
+    Statement: function(node, scope, c) {
+      c(node, node.scope || scope)
+    }
   });
 
   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);
       },
@@ -1450,18 +2095,18 @@
           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;
+    var found = walk.findNodeAround(ast, pos, function(_, node) {
+      return 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);
old mode 100644
new mode 100755
--- a/devtools/client/sourceeditor/tern/signal.js
+++ b/devtools/client/sourceeditor/tern/signal.js
@@ -1,26 +1,51 @@
-(function(mod) {
+(function(root, 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) {
+  mod((root.tern || (root.tern = {})).signal = {}); // Plain browser env
+})(this, 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; }
   }
+
+  var noHandlers = []
+  function getHandlers(emitter, type) {
+    var arr = emitter._handlers && emitter._handlers[type];
+    return arr && arr.length ? arr.slice() : noHandlers
+  }
+
   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);
+    var arr = getHandlers(this, type)
+    for (var i = 0; i < arr.length; ++i) arr[i].call(this, a1, a2, a3, a4)
+  }
+
+  function signalReturnFirst(type, a1, a2, a3, a4) {
+    var arr = getHandlers(this, type)
+    for (var i = 0; i < arr.length; ++i) {
+      var result = arr[i].call(this, a1, a2, a3, a4)
+      if (result) return result
+    }
+  }
+
+  function hasHandler(type) {
+    var arr = this._handlers && this._handlers[type]
+    return arr && arr.length > 0 && arr
   }
 
   exports.mixin = function(obj) {
-    obj.on = on; obj.off = off; obj.signal = signal;
+    obj.on = on; obj.off = off;
+    obj.signal = signal;
+    obj.signalReturnFirst = signalReturnFirst;
+    obj.hasHandler = hasHandler;
     return obj;
   };
 });
old mode 100644
new mode 100755
--- a/devtools/client/sourceeditor/tern/tern.js
+++ b/devtools/client/sourceeditor/tern/tern.js
@@ -1,35 +1,40 @@
 // 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) {
+(function(root, mod) {
   if (typeof exports == "object" && typeof module == "object") // CommonJS
     return mod(exports, require("./infer"), require("./signal"),
-               require("acorn/acorn"), require("acorn/util/walk"));
+               require("acorn/acorn"), require("acorn/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) {
+    return define(["exports", "./infer", "./signal", "acorn/acorn", "acorn/walk"], mod);
+  mod(root.tern || (root.tern = {}), tern, tern.signal, acorn, acorn.walk); // Plain browser env
+})(this, function(exports, infer, signal, acorn, walk) {
   "use strict";
 
   var plugins = Object.create(null);
   exports.registerPlugin = function(name, init) { plugins[name] = init; };
 
   var defaultOptions = exports.defaultOptions = {
     debug: false,
     async: false,
     getFile: function(_f, c) { if (this.async) c(null, null); },
+    normalizeFilename: function(name) { return name },
     defs: [],
     plugins: {},
     fetchTimeout: 1000,
-    dependencyBudget: 20000
+    dependencyBudget: 20000,
+    reuseInstances: true,
+    stripCRs: false,
+    ecmaVersion: 6,
+    projectDir: "/"
   };
 
   var queryTypes = {
     completions: {
       takesFile: true,
       run: findCompletions
     },
     properties: {
@@ -66,73 +71,89 @@
 
   function File(name, parent) {
     this.name = name;
     this.parent = parent;
     this.scope = this.text = this.ast = this.lineOffsets = null;
   }
   File.prototype.asLineChar = function(pos) { return asLineChar(this, pos); };
 
+  function parseFile(srv, file) {
+    var options = {
+      directSourceFile: file,
+      allowReturnOutsideFunction: true,
+      allowImportExportEverywhere: true,
+      ecmaVersion: srv.options.ecmaVersion
+    }
+    var text = srv.signalReturnFirst("preParse", file.text, options) || file.text
+    var ast = infer.parse(text, options)
+    srv.signal("postParse", ast, text)
+    return ast
+  }
+
   function updateText(file, text, srv) {
-    file.text = text;
-    file.ast = infer.parse(text, srv.passes, {directSourceFile: file, allowReturnOutsideFunction: true});
+    file.text = srv.options.stripCRs ? text.replace(/\r\n/g, "\n") : text;
+    infer.withContext(srv.cx, function() {
+      file.ast = parseFile(srv, 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.projectDir = options.projectDir.replace(/\\/g, "/")
+    if (!/\/$/.test(this.projectDir)) this.projectDir += "/"
+
     this.handlers = Object.create(null);
     this.files = [];
     this.fileMap = Object.create(null);
+    this.needsPurge = [];
     this.budgets = Object.create(null);
     this.uses = 0;
     this.pending = 0;
     this.asyncError = null;
-    this.passes = Object.create(null);
+    this.mod = {}
 
-    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.defs = options.defs.slice(0)
+    this.plugins = Object.create(null)
+    for (var plugin in options.plugins) if (options.plugins.hasOwnProperty(plugin))
+      this.loadPlugin(plugin, options.plugins[plugin])
 
     this.reset();
   };
   Server.prototype = signal.mixin({
     addFile: function(name, /*optional*/ text, parent) {
       // Don't crash when sloppy plugins pass non-existent parent ids
-      if (parent && !parent in this.fileMap) parent = null;
+      if (parent && !(parent in this.fileMap)) parent = null;
+      if (!(name in this.fileMap))
+        name = this.normalizeFilename(name)
       ensureFile(this, name, parent, text);
     },
     delFile: function(name) {
-      for (var i = 0, f; i < this.files.length; ++i) if ((f = this.files[i]).name == name) {
-        clearFile(this, f, null, true);
-        this.files.splice(i--, 1);
+      var file = this.findFile(name);
+      if (file) {
+        this.needsPurge.push(file.name);
+        this.files.splice(this.files.indexOf(file), 1);
         delete this.fileMap[name];
-        return;
       }
     },
     reset: function() {
       this.signal("reset");
       this.cx = new infer.Context(this.defs, this);
       this.uses = 0;
       this.budgets = Object.create(null);
       for (var i = 0; i < this.files.length; ++i) {
         var file = this.files[i];
         file.scope = null;
       }
+      this.signal("postReset");
     },
 
     request: function(doc, c) {
       var inv = invalidDoc(doc);
       if (inv) return c(inv);
 
       var self = this;
       doRequest(this, doc, function(err, data) {
@@ -156,33 +177,62 @@
       });
     },
 
     startAsyncAction: function() {
       ++this.pending;
     },
     finishAsyncAction: function(err) {
       if (err) this.asyncError = err;
-      if (--this.pending == 0) this.signal("everythingFetched");
+      if (--this.pending === 0) this.signal("everythingFetched");
+    },
+
+    addDefs: function(defs, toFront) {
+      if (toFront) this.defs.unshift(defs)
+      else this.defs.push(defs)
+
+      if (this.cx) this.reset()
+    },
+
+    loadPlugin: function(name, options) {
+      if (arguments.length == 1) options = this.options.plugins[name] || true
+      if (name in this.plugins || !(name in plugins) || !options) return
+      this.plugins[name] = true
+      var init = plugins[name](this, options)
+
+      // This is for backwards-compatibilty. Don't rely on it -- use addDef and on directly
+      if (!init) return
+      if (init.defs) this.addDefs(init.defs, init.loadFirst)
+      if (init.passes) for (var type in init.passes) if (init.passes.hasOwnProperty(type))
+        this.on(type, init.passes[type])
+    },
+
+    normalizeFilename: function(name) {
+      var norm = this.options.normalizeFilename(name).replace(/\\/g, "/")
+      if (norm.indexOf(this.projectDir) == 0) norm = norm.slice(this.projectDir.length)
+      return norm
     }
   });
 
   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, null, file.type == "full" ? file.text : null);
+      if (file.type == "delete")
+        srv.delFile(file.name);
+      else
+        ensureFile(srv, file.name, null, file.type == "full" ? file.text : null);
     }
 
     var timeBudget = typeof doc.timeout == "number" ? [doc.timeout] : null;
     if (!query) {
       analyzeAll(srv, timeBudget, function(){});
       return;
     }
 
@@ -203,37 +253,42 @@
         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);
       }
+      infer.resetGuessing()
       infer.withContext(srv.cx, timeBudget ? function() { infer.withTimeout(timeBudget[0], run); } : run);
     });
   }
 
   function analyzeFile(srv, file) {
     infer.withContext(srv.cx, function() {
       file.scope = srv.cx.topScope;
       srv.signal("beforeLoad", file);
-      infer.markVariablesDefinedBy(file.scope, file.name);
-      infer.analyze(file.ast, file.name, file.scope, srv.passes);
-      infer.purgeMarkedVariables(file.scope);
+      infer.analyze(file.ast, file.name, file.scope);
       srv.signal("afterLoad", file);
     });
     return file;
   }
 
   function ensureFile(srv, name, parent, text) {
     var known = srv.findFile(name);
     if (known) {
-      if (text != null) clearFile(srv, known, text);
-      if (parentDepth(known.parent) > parentDepth(parent)) {
+      if (text != null) {
+        if (known.scope) {
+          srv.needsPurge.push(name);
+          known.scope = null;
+        }
+        updateText(known, text, srv);
+      }
+      if (parentDepth(srv, known.parent) > parentDepth(srv, parent)) {
         known.parent = parent;
         if (known.excluded) known.excluded = null;
       }
       return;
     }
 
     var file = new File(name, parent);
     srv.files.push(file);
@@ -246,50 +301,33 @@
         updateText(file, text || "", srv);
         srv.finishAsyncAction(err);
       });
     } else {
       updateText(file, srv.options.getFile(name) || "", srv);
     }
   }
 
-  function clearFile(srv, file, newText, purgeVars) {
-    if (file.scope) {
-      infer.withContext(srv.cx, function() {
-        // FIXME try to batch purges into a single pass (each call needs
-        // to traverse the whole graph)
-        infer.purgeTypes(file.name);
-        if (purgeVars) {
-          infer.markVariablesDefinedBy(file.scope, file.name);
-          infer.purgeMarkedVariables(file.scope);
-        }
-      });
-      file.scope = null;
-    }
-    if (newText != null) updateText(file, newText, srv);
-  }
-
   function fetchAll(srv, c) {
     var done = true, returned = false;
-    for (var i = 0; i < srv.files.length; ++i) {
-      var file = srv.files[i];
-      if (file.text != null) continue;
+    srv.files.forEach(function(file) {
+      if (file.text != null) return;
       if (srv.options.async) {
         done = false;
         srv.options.getFile(file.name, function(err, text) {
           if (err && !returned) { returned = true; return c(err); }
           updateText(file, text || "", srv);
           fetchAll(srv, c);
         });
       } else {
         try {
           updateText(file, srv.options.getFile(file.name) || "", srv);
         } catch (e) { return c(e); }
       }
-    }
+    });
     if (done) c();
   }
 
   function waitOnFetch(srv, timeBudget, c) {
     var done = function() {
       srv.off("everythingFetched", done);
       clearTimeout(timeout);
       analyzeAll(srv, timeBudget, c);
@@ -299,27 +337,34 @@
   }
 
   function analyzeAll(srv, timeBudget, c) {
     if (srv.pending) return waitOnFetch(srv, timeBudget, c);
 
     var e = srv.fetchError;
     if (e) { srv.fetchError = null; return c(e); }
 
+    if (srv.needsPurge.length > 0) infer.withContext(srv.cx, function() {
+      infer.purge(srv.needsPurge);
+      srv.needsPurge.length = 0;
+    });
+
     var done = true;
     // The second inner loop might add new files. The outer loop keeps
     // repeating both inner loops until all files have been looked at.
     for (var i = 0; i < srv.files.length;) {
       var toAnalyze = [];
       for (; i < srv.files.length; ++i) {
         var file = srv.files[i];
         if (file.text == null) done = false;
         else if (file.scope == null && !file.excluded) toAnalyze.push(file);
       }
-      toAnalyze.sort(function(a, b) { return parentDepth(a.parent) - parentDepth(b.parent); });
+      toAnalyze.sort(function(a, b) {
+        return parentDepth(srv, a.parent) - parentDepth(srv, b.parent);
+      });
       for (var j = 0; j < toAnalyze.length; j++) {
         var file = toAnalyze[j];
         if (file.parent && !chargeOnBudget(srv, file)) {
           file.excluded = true;
         } else if (timeBudget) {
           var startTime = +new Date;
           infer.withTimeout(timeBudget[0], function() { analyzeFile(srv, file); });
           timeBudget[0] -= +new Date - startTime;
@@ -361,52 +406,51 @@
     return err;
   }
 
   function resolveFile(srv, localFiles, name) {
     var isRef = name.match(/^#(\d+)$/);
     if (!isRef) return srv.findFile(name);
 
     var file = localFiles[isRef[1]];
-    if (!file) throw ternError("Reference to unknown file " + name);
+    if (!file || file.type == "delete") throw ternError("Reference to unknown file " + name);
     if (file.type == "full") return srv.findFile(file.name);
 
     // This is a partial file
 
     var realFile = file.backing = srv.findFile(file.name);
     var offset = file.offset;
     if (file.offsetLines) offset = {line: file.offsetLines, ch: 0};
     file.offset = offset = resolvePos(realFile, file.offsetLines == null ? file.offset : {line: file.offsetLines, ch: 0}, true);
     var line = firstLine(file.text);
     var foundPos = findMatchingPosition(line, realFile.text, offset);
     var pos = foundPos == null ? Math.max(0, realFile.text.lastIndexOf("\n", offset)) : foundPos;
+    var inObject, atFunction;
 
     infer.withContext(srv.cx, function() {
-      infer.purgeTypes(file.name, pos, pos + file.text.length);
+      infer.purge(file.name, pos, pos + file.text.length);
 
       var text = file.text, m;
       if (m = text.match(/(?:"([^"]*)"|([\w$]+))\s*:\s*function\b/)) {
         var objNode = walk.findNodeAround(file.backing.ast, pos, "ObjectExpression");
         if (objNode && objNode.node.objType)
-          var inObject = {type: objNode.node.objType, prop: m[2] || m[1]};
+          inObject = {type: objNode.node.objType, prop: m[2] || m[1]};
       }
       if (foundPos && (m = line.match(/^(.*?)\bfunction\b/))) {
         var cut = m[1].length, white = "";
         for (var i = 0; i < cut; ++i) white += " ";
-        text = white + text.slice(cut);
-        var atFunction = true;
+        file.text = white + text.slice(cut);
+        atFunction = true;
       }
 
       var scopeStart = infer.scopeAt(realFile.ast, pos, realFile.scope);
       var scopeEnd = infer.scopeAt(realFile.ast, pos + text.length, realFile.scope);
       var scope = file.scope = scopeDepth(scopeStart) < scopeDepth(scopeEnd) ? scopeEnd : scopeStart;
-      infer.markVariablesDefinedBy(scopeStart, file.name, pos, pos + file.text.length);
-      file.ast = infer.parse(file.text, srv.passes, {directSourceFile: file, allowReturnOutsideFunction: true});
-      infer.analyze(file.ast, file.name, scope, srv.passes);
-      infer.purgeMarkedVariables(scopeStart);
+      file.ast = parseFile(srv, file)
+      infer.analyze(file.ast, file.name, scope);
 
       // This is a kludge to tie together the function types (if any)
       // outside and inside of the fragment, so that arguments and
       // return values have some information known about them.
       tieTogether: if (inObject || atFunction) {
         var newInner = infer.scopeAt(file.ast, line.length, scopeStart);
         if (!newInner.fnType) break tieTogether;
         if (inObject) {
@@ -478,18 +522,19 @@
       if (doc.query.start && !isPosition(doc.query.start)) return ".query.start must be a position";
       if (doc.query.end && !isPosition(doc.query.end)) return ".query.end must be a position";
     }
     if (doc.files) {
       if (!Array.isArray(doc.files)) return "Files property must be an array";
       for (var i = 0; i < doc.files.length; ++i) {
         var file = doc.files[i];
         if (typeof file != "object") return ".files[n] must be objects";
+        else if (typeof file.name != "string") return ".files[n].name must be a string";
+        else if (file.type == "delete") continue;
         else if (typeof file.text != "string") return ".files[n].text must be a string";
-        else if (typeof file.name != "string") return ".files[n].name must be a string";
         else if (file.type == "part") {
           if (!isPosition(file.offset) && typeof file.offsetLines != "number")
             return ".files[n].offset must be a position";
         } else if (file.type != "full") return ".files[n].type must be \"full\" or \"part\"";
       }
     }
   }
 
@@ -499,38 +544,38 @@
     var text = file.text, offsets = file.lineOffsets || (file.lineOffsets = [0]);
     var pos = 0, curLine = 0;
     var storePos = Math.min(Math.floor(line / offsetSkipLines), offsets.length - 1);
     var pos = offsets[storePos], curLine = storePos * offsetSkipLines;
 
     while (curLine < line) {
       ++curLine;
       pos = text.indexOf("\n", pos) + 1;
-      if (pos == 0) return null;
-      if (curLine % offsetSkipLines == 0) offsets.push(pos);
+      if (pos === 0) return null;
+      if (curLine % offsetSkipLines === 0) offsets.push(pos);
     }
     return pos;
   }
 
-  function resolvePos(file, pos, tolerant) {
+  var resolvePos = exports.resolvePos = function(file, pos, tolerant) {
     if (typeof pos != "number") {
       var lineStart = findLineStart(file, pos.line);
       if (lineStart == null) {
         if (tolerant) pos = file.text.length;
         else throw ternError("File doesn't contain a line " + pos.line);
       } else {
         pos = lineStart + pos.ch;
       }
     }
     if (pos > file.text.length) {
       if (tolerant) pos = file.text.length;
       else throw ternError("Position " + pos + " is outside of file.");
     }
     return pos;
-  }
+  };
 
   function asLineChar(file, pos) {
     if (!file) return {line: 0, ch: 0};
     var offsets = file.lineOffsets || (file.lineOffsets = [0]);
     var text = file.text, line, lineStart;
     for (var i = offsets.length - 1; i >= 0; --i) if (offsets[i] <= pos) {
       line = i * offsetSkipLines;
       lineStart = offsets[i];
@@ -539,26 +584,26 @@
       var eol = text.indexOf("\n", lineStart);
       if (eol >= pos || eol < 0) break;
       lineStart = eol + 1;
       ++line;
     }
     return {line: line, ch: pos - lineStart};
   }
 
-  function outputPos(query, file, pos) {
+  var outputPos = exports.outputPos = function(query, file, pos) {
     if (query.lineCharPositions) {
       var out = asLineChar(file, pos);
       if (file.type == "part")
         out.line += file.offsetLines != null ? file.offsetLines : asLineChar(file.backing, file.offset).line;
       return out;
     } else {
       return pos + (file.type == "part" ? file.offset : 0);
     }
-  }
+  };
 
   // Delete empty fields from result objects
   function clean(obj) {
     for (var prop in obj) if (obj[prop] == null) delete obj[prop];
     return obj;
   }
   function maybeSet(obj, prop, val) {
     if (val != null) obj[prop] = val;
@@ -573,165 +618,284 @@
     else return aUp ? 1 : -1;
   }
 
   function isStringAround(node, start, end) {
     return node.type == "Literal" && typeof node.value == "string" &&
       node.start == start - 1 && node.end <= end + 1;
   }
 
+  function pointInProp(objNode, point) {
+    for (var i = 0; i < objNode.properties.length; i++) {
+      var curProp = objNode.properties[i];
+      if (curProp.key.start <= point && curProp.key.end >= point)
+        return curProp;
+    }
+  }
+
   var jsKeywords = ("break do instanceof typeof case else new var " +
     "catch finally return void continue for switch while debugger " +
     "function this with default if throw delete in try").split(" ");
 
+  var addCompletion = exports.addCompletion = function(query, completions, name, aval, depth) {
+    var typeInfo = query.types || query.docs || query.urls || query.origins;
+    var wrapAsObjs = typeInfo || query.depths;
+
+    for (var i = 0; i < completions.length; ++i) {
+      var c = completions[i];
+      if ((wrapAsObjs ? c.name : c) == name) return;
+    }
+    var rec = wrapAsObjs ? {name: name} : name;
+    completions.push(rec);
+
+    if (aval && typeInfo) {
+      infer.resetGuessing();
+      var type = aval.getType();
+      rec.guess = infer.didGuess();
+      if (query.types)
+        rec.type = infer.toString(aval);
+      if (query.docs)
+        maybeSet(rec, "doc", parseDoc(query, aval.doc || type && type.doc));
+      if (query.urls)
+        maybeSet(rec, "url", aval.url || type && type.url);
+      if (query.origins)
+        maybeSet(rec, "origin", aval.origin || type && type.origin);
+    }
+    if (query.depths) rec.depth = depth || 0;
+    return rec;
+  };
+
   function findCompletions(srv, query, file) {
     if (query.end == null) throw ternError("missing .query.end field");
+    var fromPlugin = srv.signalReturnFirst("completion", file, query)
+    if (fromPlugin) return fromPlugin
+
     var wordStart = resolvePos(file, query.end), wordEnd = wordStart, text = file.text;
     while (wordStart && acorn.isIdentifierChar(text.charCodeAt(wordStart - 1))) --wordStart;
     if (query.expandWordForward !== false)
       while (wordEnd < text.length && acorn.isIdentifierChar(text.charCodeAt(wordEnd))) ++wordEnd;
-    var word = text.slice(wordStart, wordEnd), completions = [];
+    var word = text.slice(wordStart, wordEnd), completions = [], ignoreObj;
     if (query.caseInsensitive) word = word.toLowerCase();
-    var wrapAsObjs = query.types || query.depths || query.docs || query.urls || query.origins;
 
-    function gather(prop, obj, depth) {
+    function gather(prop, obj, depth, addInfo) {
       // 'hasOwnProperty' and such are usually just noise, leave them
       // out when no prefix is provided.
-      if (query.omitObjectPrototype !== false && obj == srv.cx.protos.Object && !word) return;
+      if ((objLit || query.omitObjectPrototype !== false) && obj == srv.cx.protos.Object && !word) return;
       if (query.filter !== false && word &&
-          (query.caseInsensitive ? prop.toLowerCase() : prop).indexOf(word) != 0) return;
-      for (var i = 0; i < completions.length; ++i) {
-        var c = completions[i];
-        if ((wrapAsObjs ? c.name : c) == prop) return;
-      }
-      var rec = wrapAsObjs ? {name: prop} : prop;
-      completions.push(rec);
-
-      if (query.types || query.docs || query.urls || query.origins) {
-        var val = obj ? obj.props[prop] : infer.ANull;
-        infer.resetGuessing();
-        var type = val.getType();
-        rec.guess = infer.didGuess();
-        if (query.types)
-          rec.type = infer.toString(type);
-        if (query.docs)
-          maybeSet(rec, "doc", val.doc || type && type.doc);
-        if (query.urls)
-          maybeSet(rec, "url", val.url || type && type.url);
-        if (query.origins)
-          maybeSet(rec, "origin", val.origin || type && type.origin);
-      }
-      if (query.depths) rec.depth = depth;
+          (query.caseInsensitive ? prop.toLowerCase() : prop).indexOf(word) !== 0) return;
+      if (ignoreObj && ignoreObj.props[prop]) return;
+      var result = addCompletion(query, completions, prop, obj && obj.props[prop], depth);
+      if (addInfo && result && typeof result != "string") addInfo(result);
     }
 
-    var memberExpr = infer.findExpressionAround(file.ast, null, wordStart, file.scope, "MemberExpression");
-    var hookname;
-    if (memberExpr &&
-        (memberExpr.node.computed ? isStringAround(memberExpr.node.property, wordStart, wordEnd)
-                                  : memberExpr.node.object.end < wordStart)) {
-      var prop = memberExpr.node.property;
+    var hookname, prop, objType, isKey;
+
+    var exprAt = infer.findExpressionAround(file.ast, null, wordStart, file.scope);
+    var memberExpr, objLit;
+    // Decide whether this is an object property, either in a member
+    // expression or an object literal.
+    if (exprAt) {
+      var exprNode = exprAt.node;
+      if (exprNode.type == "MemberExpression" && exprNode.object.end < wordStart) {
+        memberExpr = exprAt;
+      } else if (isStringAround(exprNode, wordStart, wordEnd)) {
+        var parent = infer.parentNode(exprNode, file.ast);
+        if (parent.type == "MemberExpression" && parent.property == exprNode)
+          memberExpr = {node: parent, state: exprAt.state};
+      } else if (exprNode.type == "ObjectExpression") {
+        var objProp = pointInProp(exprNode, wordEnd);
+        if (objProp) {
+          objLit = exprAt;
+          prop = isKey = objProp.key.name;
+        } else if (!word && !/:\s*$/.test(file.text.slice(0, wordStart))) {
+          objLit = exprAt;
+          prop = isKey = true;
+        }
+      }
+    }
+
+    if (objLit) {
+      // Since we can't use the type of the literal itself to complete
+      // its properties (it doesn't contain the information we need),
+      // we have to try asking the surrounding expression for type info.
+      objType = infer.typeFromContext(file.ast, objLit);
+      ignoreObj = objLit.node.objType;
+    } else if (memberExpr) {
+      prop = memberExpr.node.property;
       prop = prop.type == "Literal" ? prop.value.slice(1) : prop.name;
+      memberExpr.node = memberExpr.node.object;
+      objType = infer.expressionType(memberExpr);
+    } else if (text.charAt(wordStart - 1) == ".") {
+      var pathStart = wordStart - 1;
+      while (pathStart && (text.charAt(pathStart - 1) == "." || acorn.isIdentifierChar(text.charCodeAt(pathStart - 1)))) pathStart--;
+      var path = text.slice(pathStart, wordStart - 1);
+      if (path) {
+        objType = infer.def.parsePath(path, file.scope).getObjType();
+        prop = word;
+      }
+    }
+
+    if (prop != null) {
       srv.cx.completingProperty = prop;
 
-      memberExpr.node = memberExpr.node.object;
-      var tp = infer.expressionType(memberExpr);
-      if (tp) infer.forAllPropertiesOf(tp, gather);
+      if (objType) infer.forAllPropertiesOf(objType, gather);
 
-      if (!completions.length && query.guess !== false && tp && tp.guessProperties) {
-        tp.guessProperties(function(p, o, d) {if (p != prop && p != "✖") gather(p, o, d);});
-      }
+      if (!completions.length && query.guess !== false && objType && objType.guessProperties)
+        objType.guessProperties(function(p, o, d) {if (p != prop && p != "✖") gather(p, o, d);});
       if (!completions.length && word.length >= 2 && query.guess !== false)
         for (var prop in srv.cx.props) gather(prop, srv.cx.props[prop][0], 0);
       hookname = "memberCompletion";
     } else {
       infer.forAllLocalsAt(file.ast, wordStart, file.scope, gather);
-      if (query.includeKeywords) jsKeywords.forEach(function(kw) { gather(kw, null, 0); });
-      hookname = "completion";
+      if (query.includeKeywords) jsKeywords.forEach(function(kw) {
+        gather(kw, null, 0, function(rec) { rec.isKeyword = true; });
+      });
+      hookname = "variableCompletion";
     }
-    if (srv.passes[hookname])
-      srv.passes[hookname].forEach(function(hook) {hook(file, wordStart, wordEnd, gather);});
+    srv.signal(hookname, file, wordStart, wordEnd, gather)
 
     if (query.sort !== false) completions.sort(compareCompletions);
     srv.cx.completingProperty = null;
 
     return {start: outputPos(query, file, wordStart),
             end: outputPos(query, file, wordEnd),
+            isProperty: !!prop,
+            isObjectKey: !!isKey,
             completions: completions};
   }
 
   function findProperties(srv, query) {
     var prefix = query.prefix, found = [];
     for (var prop in srv.cx.props)
-      if (prop != "<i>" && (!prefix || prop.indexOf(prefix) == 0)) found.push(prop);
+      if (prop != "<i>" && (!prefix || prop.indexOf(prefix) === 0)) found.push(prop);
     if (query.sort !== false) found.sort(compareCompletions);
     return {completions: found};
   }
 
   var findExpr = exports.findQueryExpr = function(file, query, wide) {
     if (query.end == null) throw ternError("missing .query.end field");
 
     if (query.variable) {
       var scope = infer.scopeAt(file.ast, resolvePos(file, query.end), file.scope);
       return {node: {type: "Identifier", name: query.variable, start: query.end, end: query.end + 1},
               state: scope};
     } else {
       var start = query.start && resolvePos(file, query.start), end = resolvePos(file, query.end);
       var expr = infer.findExpressionAt(file.ast, start, end, file.scope);
       if (expr) return expr;
       expr = infer.findExpressionAround(file.ast, start, end, file.scope);
-      if (expr && (wide || (start == null ? end : start) - expr.node.start < 20 || expr.node.end - end < 20))
+      if (expr && (expr.node.type == "ObjectExpression" || wide ||
+                   (start == null ? end : start) - expr.node.start < 20 || expr.node.end - end < 20))
         return expr;
-      throw ternError("No expression at the given position.");
+      return null;
     }
   };
 
-  function findTypeAt(_srv, query, file) {
-    var expr = findExpr(file, query);
-    infer.resetGuessing();
-    var type = infer.expressionType(expr);
+  function findExprOrThrow(file, query, wide) {
+    var expr = findExpr(file, query, wide);
+    if (expr) return expr;
+    throw ternError("No expression at the given position.");
+  }
+
+  function ensureObj(tp) {
+    if (!tp || !(tp = tp.getType()) || !(tp instanceof infer.Obj)) return null;
+    return tp;
+  }
+
+  function findExprType(srv, query, file, expr) {
+    var type;
+    if (expr) {
+      infer.resetGuessing();
+      type = infer.expressionType(expr);
+    }
+    var typeHandlers = srv.hasHandler("typeAt")
+    if (typeHandlers) {
+      var pos = resolvePos(file, query.end)
+      for (var i = 0; i < typeHandlers.length; i++)
+        type = typeHandlers[i](file, pos, expr, type)
+    }
+    if (!type) throw ternError("No type found at the given position.");
+
+    var objProp;
+    if (expr.node.type == "ObjectExpression" && query.end != null &&
+        (objProp = pointInProp(expr.node, resolvePos(file, query.end)))) {
+      var name = objProp.key.name;
+      var fromCx = ensureObj(infer.typeFromContext(file.ast, expr));
+      if (fromCx && fromCx.hasProp(name)) {
+        type = fromCx.hasProp(name);
+      } else {
+        var fromLocal = ensureObj(type);
+        if (fromLocal && fromLocal.hasProp(name))
+          type = fromLocal.hasProp(name);
+      }
+    }
+    return type;
+  };
+
+  function findTypeAt(srv, query, file) {
+    var expr = findExpr(file, query), exprName;
+    var type = findExprType(srv, query, file, expr), exprType = type;
     if (query.preferFunction)
       type = type.getFunctionType() || type.getType();
     else
       type = type.getType();
 
-    if (expr.node.type == "Identifier")
-      var exprName = expr.node.name;
-    else if (expr.node.type == "MemberExpression" && !expr.node.computed)
-      var exprName = expr.node.property.name;
+    if (expr) {
+      if (expr.node.type == "Identifier")
+        exprName = expr.node.name;
+      else if (expr.node.type == "MemberExpression" && !expr.node.computed)
+        exprName = expr.node.property.name;
+    }
 
     if (query.depth != null && typeof query.depth != "number")
       throw ternError(".query.depth must be a number");
 
     var result = {guess: infer.didGuess(),
-                  type: infer.toString(type, query.depth),
+                  type: infer.toString(exprType, query.depth),
                   name: type && type.name,
-                  exprName: exprName};
-    if (type) storeTypeDocs(type, result);
+                  exprName: exprName,
+                  doc: exprType.doc,
+                  url: exprType.url};
+    if (type) storeTypeDocs(query, type, result);
 
     return clean(result);
   }
 
-  function findDocs(_srv, query, file) {
+  function parseDoc(query, doc) {
+    if (!doc) return null;
+    if (query.docFormat == "full") return doc;
+    var parabreak = /.\n[\s@\n]/.exec(doc);
+    if (parabreak) doc = doc.slice(0, parabreak.index + 1);
+    doc = doc.replace(/\n\s*/g, " ");
+    if (doc.length < 100) return doc;
+    var sentenceEnd = /[\.!?] [A-Z]/g;
+    sentenceEnd.lastIndex = 80;
+    var found = sentenceEnd.exec(doc);
+    if (found) doc = doc.slice(0, found.index + 1);
+    return doc;
+  }
+
+  function findDocs(srv, query, file) {
     var expr = findExpr(file, query);
-    var type = infer.expressionType(expr);
-    var result = {url: type.url, doc: type.doc};
+    var type = findExprType(srv, query, file, expr);
+    var result = {url: type.url, doc: parseDoc(query, type.doc), type: infer.toString(type)};
     var inner = type.getType();
-    if (inner) storeTypeDocs(inner, result);
+    if (inner) storeTypeDocs(query, inner, result);
     return clean(result);
   }
 
-  function storeTypeDocs(type, out) {
+  function storeTypeDocs(query, type, out) {
     if (!out.url) out.url = type.url;
-    if (!out.doc) out.doc = type.doc;
+    if (!out.doc) out.doc = parseDoc(query, type.doc);
     if (!out.origin) out.origin = type.origin;
     var ctor, boring = infer.cx().protos;
     if (!out.url && !out.doc && type.proto && (ctor = type.proto.hasCtor) &&
         type.proto != boring.Object && type.proto != boring.Function && type.proto != boring.Array) {
       out.url = ctor.url;
-      out.doc = ctor.doc;
+      out.doc = parseDoc(query, ctor.doc);
     }
   }
 
   var getSpan = exports.getSpan = function(obj) {
     if (!obj.origin) return;
     if (obj.originNode) {
       var node = obj.originNode;
       if (/^Function/.test(node.type) && node.id) node = node.id;
@@ -750,26 +914,25 @@
       var file = srv.findFile(span.origin);
       target.start = outputPos(query, file, span.node.start);
       target.end = outputPos(query, file, span.node.end);
     }
   };
 
   function findDef(srv, query, file) {
     var expr = findExpr(file, query);
-    infer.resetGuessing();
-    var type = infer.expressionType(expr);
+    var type = findExprType(srv, query, file, expr);
     if (infer.didGuess()) return {};
 
     var span = getSpan(type);
-    var result = {url: type.url, doc: type.doc, origin: type.origin};
+    var result = {url: type.url, doc: parseDoc(query, type.doc), origin: type.origin};
 
     if (type.types) for (var i = type.types.length - 1; i >= 0; --i) {
       var tp = type.types[i];
-      storeTypeDocs(tp, result);
+      storeTypeDocs(query, tp, result);
       if (!span) span = getSpan(tp);
     }
 
     if (span && span.node) { // refers to a loaded file
       var spanFile = span.node.sourceFile || srv.findFile(span.origin);
       var start = outputPos(query, spanFile, span.node.start), end = outputPos(query, spanFile, span.node.end);
       result.start = start; result.end = end;
       result.file = span.origin;
@@ -782,17 +945,17 @@
     }
     return clean(result);
   }
 
   function findRefsToVariable(srv, query, file, expr, checkShadowing) {
     var name = expr.node.name;
 
     for (var scope = expr.state; scope && !(name in scope.props); scope = scope.prev) {}
-    if (!scope) throw ternError("Could not find a definition for " + name + " " + !!srv.cx.topScope.props.x);
+    if (!scope) throw ternError("Could not find a definition for " + name);
 
     var type, refs = [];
     function storeRef(file) {
       return function(node, scopeHere) {
         if (checkShadowing) for (var s = scopeHere; s != scope; s = s.prev) {
           var exists = s.hasProp(checkShadowing);
           if (exists)
             throw ternError("Renaming `" + name + "` to `" + checkShadowing + "` would make a variable at line " +
@@ -823,17 +986,17 @@
         infer.findRefs(cur.ast, cur.scope, name, scope, storeRef(cur));
       }
     }
 
     return {refs: refs, type: type, name: name};
   }
 
   function findRefsToProperty(srv, query, expr, prop) {
-    var objType = infer.expressionType(expr).getType();
+    var objType = infer.expressionType(expr).getObjType();
     if (!objType) throw ternError("Couldn't determine type of base object.");
 
     var refs = [];
     function storeRef(file) {
       return function(node) {
         refs.push({file: file.name,
                    start: outputPos(query, file, node.start),
                    end: outputPos(query, file, node.end)});
@@ -843,17 +1006,17 @@
       var cur = srv.files[i];
       infer.findPropRefs(cur.ast, cur.scope, objType, prop.name, storeRef(cur));
     }
 
     return {refs: refs, name: prop.name};
   }
 
   function findRefs(srv, query, file) {
-    var expr = findExpr(file, query, true);
+    var expr = findExprOrThrow(file, query, true);
     if (expr && expr.node.type == "Identifier") {
       return findRefsToVariable(srv, query, file, expr);
     } else if (expr && expr.node.type == "MemberExpression" && !expr.node.computed) {
       var p = expr.node.property;
       expr.node = expr.node.object;
       return findRefsToProperty(srv, query, expr, p);
     } else if (expr && expr.node.type == "ObjectExpression") {
       var pos = resolvePos(file, query.end);
@@ -863,17 +1026,17 @@
           return findRefsToProperty(srv, query, expr, k);
       }
     }
     throw ternError("Not at a variable or property name.");
   }
 
   function buildRename(srv, query, file) {
     if (typeof query.newName != "string") throw ternError(".query.newName should be a string");
-    var expr = findExpr(file, query);
+    var expr = findExprOrThrow(file, query);
     if (!expr || expr.node.type != "Identifier") throw ternError("Not at a variable.");
 
     var data = findRefsToVariable(srv, query, file, expr, query.newName), refs = data.refs;
     delete data.refs;
     data.files = srv.files.map(function(f){return f.name;});
 
     var changes = data.changes = [];
     for (var i = 0; i < refs.length; ++i) {
@@ -884,10 +1047,10 @@
 
     return data;
   }
 
   function listFiles(srv) {
     return {files: srv.files.map(function(f){return f.name;})};
   }
 
-  exports.version = "0.6.2";
+  exports.version = "0.16.0";
 });
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -356,16 +356,17 @@ StyleSheetEditor.prototype = {
     } else if (this.sourceEditor) {
       this._getSourceTextAndPrettify().then((newText) => {
         this._justSetText = true;
         let firstLine = this.sourceEditor.getFirstVisibleLine();
         let pos = this.sourceEditor.getCursor();
         this.sourceEditor.setText(newText);
         this.sourceEditor.setFirstVisibleLine(firstLine);
         this.sourceEditor.setCursor(pos);
+        this.emit("style-applied");
       });
     }
   },
 
   /**
    * Handles changes to the list of @media rules in the stylesheet.
    * Emits 'media-rules-changed' if the list has changed.
    *
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -51,17 +51,16 @@ support-files =
   sync.html
 
 [browser_styleeditor_autocomplete.js]
 [browser_styleeditor_autocomplete-disabled.js]
 [browser_styleeditor_bug_740541_iframes.js]
 [browser_styleeditor_bug_851132_middle_click.js]
 [browser_styleeditor_bug_870339.js]
 [browser_styleeditor_cmd_edit.js]
-skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
 [browser_styleeditor_enabled.js]
 [browser_styleeditor_fetch-from-cache.js]
 [browser_styleeditor_filesave.js]
 [browser_styleeditor_highlight-selector.js]
 [browser_styleeditor_import.js]
 [browser_styleeditor_import_rule.js]
 [browser_styleeditor_init.js]
 [browser_styleeditor_inline_friendly_names.js]
@@ -78,16 +77,15 @@ skip-if = e10s # Bug 1055333 - style edi
 [browser_styleeditor_reload.js]
 [browser_styleeditor_scroll.js]
 [browser_styleeditor_sv_keynav.js]
 [browser_styleeditor_sv_resize.js]
 [browser_styleeditor_selectstylesheet.js]
 [browser_styleeditor_sourcemaps.js]
 [browser_styleeditor_sourcemap_large.js]
 [browser_styleeditor_sourcemap_watching.js]
-skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
 [browser_styleeditor_sync.js]
 [browser_styleeditor_syncAddRule.js]
 [browser_styleeditor_syncAlreadyOpen.js]
 [browser_styleeditor_syncEditSelector.js]
 [browser_styleeditor_syncIntoRuleView.js]
 [browser_styleeditor_transition_rule.js]
 [browser_styleeditor_xul.js]
--- a/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_watching.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_watching.js
@@ -1,98 +1,92 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-Components.utils.import("resource://gre/modules/Task.jsm");
-var {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
-var promise = require("promise");
-
 const TESTCASE_URI_HTML = TEST_BASE_HTTP + "sourcemaps-watching.html";
 const TESTCASE_URI_CSS = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css";
 const TESTCASE_URI_REG_CSS = TEST_BASE_HTTP + "simple.css";
 const TESTCASE_URI_SCSS = TEST_BASE_HTTP + "sourcemap-sass/sourcemaps.scss";
 const TESTCASE_URI_MAP = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css.map";
 const TESTCASE_SCSS_NAME = "sourcemaps.scss";
 
 const TRANSITIONS_PREF = "devtools.styleeditor.transitions";
 
 const CSS_TEXT = "* { color: blue }";
 
-var Cc = Components.classes;
-var Ci = Components.interfaces;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const {FileUtils} = Components.utils.import("resource://gre/modules/FileUtils.jsm", {});
+const {NetUtil} = Components.utils.import("resource://gre/modules/NetUtil.jsm", {});
 
-var tempScope = {};
-Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
-Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
-var FileUtils = tempScope.FileUtils;
-var NetUtil = tempScope.NetUtil;
-
-function test() {
-  waitForExplicitFinish();
-
-  Services.prefs.setBoolPref(TRANSITIONS_PREF, false);
+add_task(function*() {
+  yield new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({"set": [
+      [TRANSITIONS_PREF, false]
+    ]}, resolve);
+  });
 
-  Task.spawn(function*() {
-    // copy all our files over so we don't screw them up for other tests
-    let HTMLFile = yield copy(TESTCASE_URI_HTML, ["sourcemaps.html"]);
-    let CSSFile = yield copy(TESTCASE_URI_CSS,
-      ["sourcemap-css", "sourcemaps.css"]);
-    yield copy(TESTCASE_URI_SCSS, ["sourcemap-sass", "sourcemaps.scss"]);
-    yield copy(TESTCASE_URI_MAP, ["sourcemap-css", "sourcemaps.css.map"]);
-    yield copy(TESTCASE_URI_REG_CSS, ["simple.css"]);
+  // copy all our files over so we don't screw them up for other tests
+  let HTMLFile = yield copy(TESTCASE_URI_HTML, ["sourcemaps.html"]);
+  let CSSFile = yield copy(TESTCASE_URI_CSS,
+    ["sourcemap-css", "sourcemaps.css"]);
+  yield copy(TESTCASE_URI_SCSS, ["sourcemap-sass", "sourcemaps.scss"]);
+  yield copy(TESTCASE_URI_MAP, ["sourcemap-css", "sourcemaps.css.map"]);
+  yield copy(TESTCASE_URI_REG_CSS, ["simple.css"]);
 
-    let uri = Services.io.newFileURI(HTMLFile);
-    let testcaseURI = uri.resolve("");
+  let uri = Services.io.newFileURI(HTMLFile);
+  let testcaseURI = uri.resolve("");
+
+  let { ui } = yield openStyleEditorForURL(testcaseURI);
 
-    let { ui } = yield openStyleEditorForURL(testcaseURI);
+  let editor = ui.editors[1];
+  if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) {
+    editor = ui.editors[2];
+  }
 
-    let editor = ui.editors[1];
-    if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) {
-      editor = ui.editors[2];
-    }
+  is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor");
 
-    is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor");
+  let link = getLinkFor(editor);
+  link.click();
+
+  yield editor.getSourceEditor();
 
-    let link = getLinkFor(editor);
-    link.click();
-
-    yield editor.getSourceEditor();
+  let element = content.document.querySelector("div");
+  let style = content.getComputedStyle(element, null);
 
-    let element = content.document.querySelector("div");
-    let style = content.getComputedStyle(element, null);
+  is(style.color, "rgb(255, 0, 102)", "div is red before saving file");
 
-    is(style.color, "rgb(255, 0, 102)", "div is red before saving file");
+  // let styleApplied = promise.defer();
+  let styleApplied = editor.once("style-applied");
 
-    editor.styleSheet.relatedStyleSheet.once("style-applied", function() {
-      is(style.color, "rgb(0, 0, 255)", "div is blue after saving file");
-      finishUp();
-    });
+  yield pauseForTimeChange();
 
-    yield pauseForTimeChange();
+  // Edit and save Sass in the editor. This will start off a file-watching
+  // process waiting for the CSS file to change.
+  yield editSCSS(editor);
 
-    // Edit and save Sass in the editor. This will start off a file-watching
-    // process waiting for the CSS file to change.
-    yield editSCSS(editor);
+  // We can't run Sass or another compiler, so we fake it by just
+  // directly changing the CSS file.
+  yield editCSSFile(CSSFile);
 
-    // We can't run Sass or another compiler, so we fake it by just
-    // directly changing the CSS file.
-    yield editCSSFile(CSSFile);
+  info("wrote to CSS file, waiting for style-applied event");
 
-    info("wrote to CSS file");
-  });
-}
+  yield styleApplied;
+
+  is(style.color, "rgb(0, 0, 255)", "div is blue after saving file");
+});
 
 function editSCSS(editor) {
   let deferred = promise.defer();
 
-  let pos = {line: 0, ch: 0};
-  editor.sourceEditor.replaceText(CSS_TEXT, pos, pos);
+  editor.sourceEditor.setText(CSS_TEXT);
 
   editor.saveToFile(null, function(file) {
     ok(file, "Scss file should be saved");
     deferred.resolve();
   });
 
   return deferred.promise;
 }
@@ -107,21 +101,16 @@ function pauseForTimeChange() {
   // We have to wait for the system time to turn over > 1000 ms so that
   // our file's last change time will show a change. This reflects what
   // would happen in real life with a user manually saving the file.
   setTimeout(deferred.resolve, 2000);
 
   return deferred.promise;
 }
 
-function finishUp() {
-  Services.prefs.clearUserPref(TRANSITIONS_PREF);
-  finish();
-}
-
 /* Helpers */
 
 function getLinkFor(editor) {
   return editor.summary.querySelector(".stylesheet-name");
 }
 
 function getStylesheetNameFor(editor) {
   return editor.summary.querySelector(".stylesheet-name > label")
--- a/devtools/client/themes/images/sort-arrows.svg
+++ b/devtools/client/themes/images/sort-arrows.svg
@@ -1,9 +1,12 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="22" height="11">
-  <g fill="#edf0f1" fill-opacity="0.8">
-    <polygon points="3,5 5.5,8 8,5"/>
-    <polygon points="14,7 16.5,4 19,7"/>
-  </g>
+<svg xmlns="http://www.w3.org/2000/svg" width="7" height="4" fill="#edf0f1" fill-opacity="0.8">
+  <style>
+    polygon:not(:target) {
+      display: none;
+    }
+  </style>
+  <polygon points="0,4 3.5,0 7,4" id="ascending"/>
+  <polygon points="0,0 3.5,4 7,0" id="descending"/>
 </svg>
--- a/devtools/client/themes/layoutview.css
+++ b/devtools/client/themes/layoutview.css
@@ -1,17 +1,17 @@
 /* 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/ */
 
 .theme-sidebar {
   box-sizing: border-box;
 }
 
-body {
+.theme-sidebar body {
   /* The view will grow bigger as the window gets resized, until 400px */
   max-width: 400px;
   margin: 0px auto;
   padding: 0;
   /* "Contain" the absolutely positioned #main element */
   position: relative;
 }
 
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -78,29 +78,24 @@
 #requests-menu-waterfall-image {
   display: -moz-box;
   height: 4px;
   margin-inline-end: 6px;
   -moz-box-ordinal-group: 2;
   width: 7px;
 }
 
-.requests-menu-header-button[sorted] > .button-box > .button-icon,
-.requests-menu-header-button[sorted] #requests-menu-waterfall-image {
-  list-style-image: url('chrome://devtools/skin/images/sort-arrows.svg');
-}
-
 .requests-menu-header-button[sorted=ascending] > .button-box > .button-icon,
 .requests-menu-header-button[sorted=ascending] #requests-menu-waterfall-image {
-  -moz-image-region: rect(4px, 19px, 7px, 14px);
+  list-style-image: url("chrome://devtools/skin/images/sort-arrows.svg#ascending");
 }
 
 .requests-menu-header-button[sorted=descending] > .button-box > .button-icon,
 .requests-menu-header-button[sorted=descending] #requests-menu-waterfall-image {
-  -moz-image-region: rect(5px, 8px, 8px, 3px);
+  list-style-image: url("chrome://devtools/skin/images/sort-arrows.svg#descending");
 }
 
 .requests-menu-header-button > .button-box > .button-text,
 #requests-menu-waterfall-label-wrapper {
   -moz-box-flex: 1;
 }
 
 .requests-menu-header-button[sorted],
@@ -111,24 +106,29 @@
 
 .requests-menu-header-button[sorted],
 .requests-menu-header[active] + .requests-menu-header .requests-menu-header-button {
   border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
 }
 
 /* Network requests table: specific column dimensions */
 
-.requests-menu-status,
-.requests-menu-method-box,
-.requests-menu-method {
+.requests-menu-status {
   max-width: 6em;
   text-align: center;
   width: 10vw;
 }
 
+.requests-menu-method,
+.requests-menu-method-box {
+  max-width: 7em;
+  text-align: center;
+  width: 10vw;
+}
+
 .requests-menu-icon-and-file {
   width: 22vw;
 }
 
 .requests-menu-icon {
   background: #fff;
   width: calc(1em + 4px);
   height: calc(1em + 4px);
@@ -167,18 +167,23 @@
   list-style-image: url(chrome://devtools/skin/images/security-state-broken.svg);
 }
 
 .security-state-local {
   list-style-image: url(chrome://devtools/skin/images/security-state-local.svg);
 }
 
 .requests-menu-type,
-.requests-menu-transferred,
 .requests-menu-size {
+  max-width: 6em;
+  text-align: center;
+  width: 8vw;
+}
+
+.requests-menu-transferred {
   max-width: 8em;
   text-align: center;
   width: 8vw;
 }
 
 /* Network requests table: status codes */
 
 .requests-menu-status-code {
--- a/devtools/client/themes/widgets.css
+++ b/devtools/client/themes/widgets.css
@@ -1204,71 +1204,73 @@
 /* Column Headers */
 
 .table-widget-column-header,
 .table-widget-cell {
   -moz-border-end: 1px solid var(--table-splitter-color) !important;
 }
 
 /* Table widget column header colors are taken from netmonitor.inc.css to match
-   the look of both the tables. This needs to be updated along with netmonitor
-   header colors in bug 951714 */
+   the look of both the tables. */
 
 .table-widget-column-header {
-  background: rgba(0,0,0,0);
   position: sticky;
   top: 0;
   width: 100%;
+  margin: 0;
   padding: 5px 0 0 !important;
   color: inherit;
   text-align: center;
   font-weight: inherit !important;
-  transition: background-color 0.1s ease-in-out;
+  border-bottom-width: 0 !important;
+  border-image: linear-gradient(transparent 15%, var(--theme-splitter-color) 15%, var(--theme-splitter-color) 85%, transparent 85%) 1 1;
+  background-repeat: no-repeat;
 }
 
-.table-widget-column-header:hover {
-  background-image: linear-gradient(rgba(0,0,0,0.10), rgba(0,0,0,0.10));
-}
-
-.table-widget-column-header:hover:active {
-  background-image: linear-gradient(rgba(0,0,0,0.25), rgba(0,0,0,0.25));
+.table-widget-column-header:not([sorted]):hover {
+  background-image: linear-gradient(rgba(0,0,0,0.1),rgba(0,0,0,0.1));
 }
 
-.table-widget-column-header:not(:active)[sorted] {
-  background-image: linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15));
+.table-widget-column-header[sorted] {
+  background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
+  border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
+  box-shadow: -0.5px 0px 0px 0.5px var(--theme-splitter-color);
+  background-position: right 6px center;
 }
 
-.table-widget-column-header:not(:active)[sorted=ascending] {
-  background-image: radial-gradient(farthest-side at center top, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3)),
-                    linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15));
-  background-size: 100% 1px, auto;
-  background-repeat: no-repeat, repeat;
+.table-widget-column-header[sorted]:-moz-locale-dir(rtl) {
+  background-position: 6px center;
 }
 
-.table-widget-column-header:not(:active)[sorted=descending] {
-  background-image: radial-gradient(farthest-side at center bottom, hsla(200,100%,70%,.7), hsla(200,100%,70%,0.3)),
-                    linear-gradient(rgba(0,0,0,0.15), rgba(0,0,0,0.15));
-  background-size: 100% 1px, auto;
-  background-repeat: no-repeat, repeat;
-  background-position: bottom;
+.table-widget-column-header[sorted=ascending] {
+  background-image: url("chrome://devtools/skin/images/sort-arrows.svg#ascending");
+}
+
+.table-widget-column-header[sorted=descending] {
+  background-image: url("chrome://devtools/skin/images/sort-arrows.svg#descending");
 }
 
 /* Cells */
 
 .table-widget-cell {
   width: 100%;
   padding: 3px 4px;
   background-clip: padding-box;
   min-width: 100px;
   -moz-user-focus: normal;
   margin-bottom: -1px !important;
   border-bottom: 1px solid transparent;
   color: var(--theme-body-color);
 }
 
+.table-widget-column-header + .table-widget-cell {
+  border-top: 1px solid var(--theme-splitter-color);
+}
+
 .table-widget-cell:last-child {
   border-bottom: 1px solid var(--table-splitter-color);
 }
 
 :root:not(.filtering) .table-widget-cell:nth-child(odd):not(.theme-selected),
 .table-widget-cell:not(.theme-selected)[odd] {
   background: var(--table-zebra-background);
 }
--- a/devtools/server/actors/canvas.js
+++ b/devtools/server/actors/canvas.js
@@ -4,16 +4,17 @@
 "use strict";
 
 const {Cc, Ci, Cu, Cr} = require("chrome");
 const events = require("sdk/event/core");
 const promise = require("promise");
 const protocol = require("devtools/server/protocol");
 const {CallWatcherActor, CallWatcherFront} = require("devtools/server/actors/call-watcher");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const {WebGLPrimitiveCounter} = require("devtools/server/primitive");
 
 const {on, once, off, emit} = events;
 const {method, custom, Arg, Option, RetVal} = protocol;
 
 const CANVAS_CONTEXTS = [
   "CanvasRenderingContext2D",
   "WebGLRenderingContext"
 ];
@@ -108,31 +109,38 @@ var FrameSnapshotActor = protocol.ActorC
    *        The server connection.
    * @param HTMLCanvasElement canvas
    *        A reference to the content canvas.
    * @param array calls
    *        An array of "function-call" actor instances.
    * @param object screenshot
    *        A single "snapshot-image" type instance.
    */
-  initialize: function(conn, { canvas, calls, screenshot }) {
+  initialize: function(conn, { canvas, calls, screenshot, primitive }) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this._contentCanvas = canvas;
     this._functionCalls = calls;
     this._animationFrameEndScreenshot = screenshot;
+    this._primitive = primitive;
   },
 
   /**
    * Gets as much data about this snapshot without computing anything costly.
    */
   getOverview: method(function() {
     return {
       calls: this._functionCalls,
       thumbnails: this._functionCalls.map(e => e._thumbnail).filter(e => !!e),
-      screenshot: this._animationFrameEndScreenshot
+      screenshot: this._animationFrameEndScreenshot,
+      primitive: {
+        tris: this._primitive.tris,
+        vertices: this._primitive.vertices,
+        points: this._primitive.points,
+        lines: this._primitive.lines
+      }
     };
   }, {
     response: { overview: RetVal("snapshot-overview") }
   }),
 
   /**
    * Gets a screenshot of the canvas's contents after the specified
    * function was called.
@@ -236,20 +244,22 @@ var CanvasActor = exports.CanvasActor = 
   // Reset for each recording, boolean indicating whether or not
   // any draw calls were called for a recording.
   _animationContainsDrawCall: false,
 
   typeName: "canvas",
   initialize: function(conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
+    this._webGLPrimitiveCounter = new WebGLPrimitiveCounter(tabActor);
     this._onContentFunctionCall = this._onContentFunctionCall.bind(this);
   },
   destroy: function(conn) {
     protocol.Actor.prototype.destroy.call(this, conn);
+    this._webGLPrimitiveCounter.destroy();
     this.finalize();
   },
 
   /**
    * Starts listening for function calls.
    */
   setup: method(function({ reload }) {
     if (this._initialized) {
@@ -312,16 +322,17 @@ var CanvasActor = exports.CanvasActor = 
   recordAnimationFrame: method(function() {
     if (this._callWatcher.isRecording()) {
       return this._currentAnimationFrameSnapshot.promise;
     }
 
     this._recordingContainsDrawCall = false;
     this._callWatcher.eraseRecording();
     this._callWatcher.initFrameStartTimestamp();
+    this._webGLPrimitiveCounter.resetCounts();
     this._callWatcher.resumeRecording();
 
     let deferred = this._currentAnimationFrameSnapshot = promise.defer();
     return deferred.promise;
   }, {
     response: { snapshot: RetVal("nullable:frame-snapshot") }
   }),
 
@@ -365,16 +376,17 @@ var CanvasActor = exports.CanvasActor = 
     // those timers is considered extremely poor practice, they're still widely
     // used on the web, especially for old demos; it's nice to support them as well.
     if (CanvasFront.LOOP_GENERATORS.has(name)) {
       this._handleAnimationFrame(functionCall);
       return;
     }
     if (CanvasFront.DRAW_CALLS.has(name) && this._animationStarted) {
       this._handleDrawCall(functionCall);
+      this._webGLPrimitiveCounter.handleDrawPrimitive(functionCall);
       return;
     }
   },
 
   /**
    * Handle animations generated using requestAnimationFrame.
    */
   _handleAnimationFrame: function(functionCall) {
@@ -410,31 +422,38 @@ var CanvasActor = exports.CanvasActor = 
 
     // Since the animation frame finished, get a hold of the (already retrieved)
     // canvas pixels to conveniently create a screenshot of the final rendering.
     let index = this._lastDrawCallIndex;
     let width = this._lastContentCanvasWidth;
     let height = this._lastContentCanvasHeight;
     let flipped = !!this._lastThumbnailFlipped; // undefined -> false
     let pixels = ContextUtils.getPixelStorage()["8bit"];
+    let primitiveResult = this._webGLPrimitiveCounter.getCounts();
     let animationFrameEndScreenshot = {
       index: index,
       width: width,
       height: height,
       scaling: 1,
       flipped: flipped,
       pixels: pixels.subarray(0, width * height * 4)
     };
 
     // Wrap the function calls and screenshot in a FrameSnapshotActor instance,
     // which will resolve the promise returned by `recordAnimationFrame`.
     let frameSnapshot = new FrameSnapshotActor(this.conn, {
       canvas: this._lastDrawCallCanvas,
       calls: functionCalls,
-      screenshot: animationFrameEndScreenshot
+      screenshot: animationFrameEndScreenshot,
+      primitive: {
+        tris: primitiveResult.tris,
+        vertices: primitiveResult.vertices,
+        points: primitiveResult.points,
+        lines: primitiveResult.lines
+      }
     });
 
     this._currentAnimationFrameSnapshot.resolve(frameSnapshot);
     this._currentAnimationFrameSnapshot = null;
     this._animationStarted = false;
   },
 
   /**
--- a/devtools/server/moz.build
+++ b/devtools/server/moz.build
@@ -28,14 +28,15 @@ SOURCES += [
 
 FINAL_LIBRARY = 'xul'
 
 DevToolsModules(
     'child.js',
     'content-globals.js',
     'content-server.jsm',
     'main.js',
+    'primitive.js',
     'protocol.js',
     'worker.js'
 )
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
new file mode 100644
--- /dev/null
+++ b/devtools/server/primitive.js
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { on, once, off, emit } = require("sdk/event/core");
+const { Class } = require("sdk/core/heritage");
+
+const WebGLPrimitivesType = {
+  "POINTS": 0,
+  "LINES": 1,
+  "LINE_LOOP": 2,
+  "LINE_STRIP": 3,
+  "TRIANGLES": 4,
+  "TRIANGLE_STRIP": 5,
+  "TRIANGLE_FAN": 6
+};
+
+/**
+ * A utility for monitoring WebGL primitive draws. Takes a `tabActor`
+ * and monitors primitive draws over time.
+ */
+const WebGLDrawArrays = "drawArrays";
+const WebGLDrawElements = "drawElements";
+
+var WebGLPrimitiveCounter = exports.WebGLPrimitiveCounter = Class({
+  initialize: function(tabActor) {
+    this.tabActor = tabActor;
+  },
+
+  destroy: function() {
+     this.stopRecording();
+  },
+
+  /**
+   * Starts monitoring primitive draws, storing the primitives count per tick.
+   */
+  resetCounts: function() {
+    this._tris = 0;
+    this._vertices = 0;
+    this._points = 0;
+    this._lines = 0;
+    this._startTime = this.tabActor.docShell.now();
+  },
+
+  /**
+   * Stops monitoring primitive draws, returning the recorded values.
+   */
+  getCounts: function() {
+    var result = {
+      tris: this._tris,
+      vertices: this._vertices,
+      points: this._points,
+      lines: this._lines
+    };
+
+    this._tris = 0;
+    this._vertices = 0;
+    this._points = 0;
+    this._lines = 0;
+    return result;
+  },
+
+  /**
+   * Handles WebGL draw primitive functions to catch primitive info.
+   */
+  handleDrawPrimitive: function(functionCall) {
+    let { name, args } = functionCall.details;
+
+    if (name === WebGLDrawArrays) {
+      this._processDrawArrays(args);
+    } else if (name === WebGLDrawElements) {
+      this._processDrawElements(args);
+    }
+  },
+
+  /**
+   * Processes WebGL drawArrays method to count primitve numbers
+   */
+  _processDrawArrays: function(args) {
+    let mode = args[0];
+    let count = args[2];
+
+    switch ( mode ) {
+      case WebGLPrimitivesType.POINTS:
+        this._vertices += count;
+        this._points += count;
+        break;
+      case WebGLPrimitivesType.LINES:
+        this._vertices += count;
+        this._lines += (count / 2);
+        break;
+      case WebGLPrimitivesType.LINE_LOOP:
+        this._vertices += count;
+        this._lines += count;
+        break;
+      case WebGLPrimitivesType.LINE_STRIP:
+        this._vertices += count;
+        this._lines += (count - 1);
+        break;
+      case WebGLPrimitivesType.TRIANGLES:
+        this._tris += (count / 3);
+        this._vertices += count;
+        break;
+      case WebGLPrimitivesType.TRIANGLE_STRIP:
+        this._tris += (count - 2);
+        this._vertices += count;
+        break;
+      case WebGLPrimitivesType.TRIANGLE_FAN:
+        this._tris += (count - 2);
+        this._vertices += count;
+        break;
+      default:
+        console.error("_processDrawArrays doesn't define this type.");
+        break;
+    }
+  },
+
+  /**
+   * Processes WebGL drawElements method to count primitve numbers
+   */
+  _processDrawElements: function(args) {
+    let mode = args[0];
+    let count = args[1];
+
+    switch ( mode ) {
+      case WebGLPrimitivesType.POINTS:
+        this._vertices += count;
+        this._points += count;
+        break;
+      case WebGLPrimitivesType.LINES:
+        this._vertices += count;
+        this._lines += (count / 2);
+        break;
+      case WebGLPrimitivesType.LINE_LOOP:
+        this._vertices += count;
+        this._lines += count;
+        break;
+      case WebGLPrimitivesType.LINE_STRIP:
+        this._vertices += count;
+        this._lines += (count - 1);
+        break;
+      case WebGLPrimitivesType.TRIANGLES:
+        let tris = count / 3;
+        let vertex = tris * 3;
+
+        if (tris > 1) {
+          vertex = tris * 2;
+        }
+        this._tris += tris;
+        this._vertices += vertex;
+        break;
+      case WebGLPrimitivesType.TRIANGLE_STRIP:
+        this._tris += (count - 2);
+        this._vertices += count;
+        break;
+      case WebGLPrimitivesType.TRIANGLE_FAN:
+        this._tris += (count - 2);
+        this._vertices += count;
+      default:
+        console.error("_processDrawElements doesn't define this type.");
+        break;
+    }
+  }
+});
\ No newline at end of file
--- a/devtools/shared/acorn/UPGRADING.md
+++ b/devtools/shared/acorn/UPGRADING.md
@@ -21,8 +21,11 @@ 3. Copy acorn.js to our tree:
 
 4. Copy acorn_loose.js to our tree:
 
        $ cp acorn_loose.js /path/to/mozilla-central/devtools/shared/acorn/acorn_loose.js
 
 5. Copy util/walk.js to our tree:
 
        $ cp util/walk.js /path/to/mozilla-central/devtools/shared/acorn/walk.js
+
+6. Check and see if javascript pretty-printing and scratchpad work without any errors.  As of version 2.6.4 we need to comment out lines in acorn_loose.js that attempt to extend the acorn object, like `acorn.parse_dammit = parse_dammit`.
+
--- a/devtools/shared/acorn/acorn.js
+++ b/devtools/shared/acorn/acorn.js
@@ -1,2696 +1,3330 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.acorn = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+// A recursive descent parser operates by defining functions for all
+// syntactic elements, and recursively calling those, each function
+// advancing the input stream and returning an AST node. Precedence
+// of constructs (for example, the fact that `!x[1]` means `!(x[1])`
+// instead of `(!x)[1]` is handled by the fact that the parser
+// function that parses unary prefix operators is called first, and
+// in turn calls the function that parses `[]` subscripts — that
+// way, it'll receive the node for `x[1]` already parsed, and wraps
+// *that* in the unary operator node.
+//
+// Acorn uses an [operator precedence parser][opp] to handle binary
+// operator precedence, because it is much more compact than using
+// the technique outlined above, which uses different, nesting
+// functions to specify precedence, for all of the ten binary
+// precedence levels that JavaScript defines.
+//
+// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
+
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var pp = _state.Parser.prototype;
+
+// Check if property name clashes with already added.
+// Object/class getters and setters are not allowed to clash —
+// either with each other or with an init property — and in
+// strict mode, init properties are also not allowed to be repeated.
+
+pp.checkPropClash = function (prop, propHash) {
+  if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand)) return;
+  var key = prop.key;var name = undefined;
+  switch (key.type) {
+    case "Identifier":
+      name = key.name;break;
+    case "Literal":
+      name = String(key.value);break;
+    default:
+      return;
+  }
+  var kind = prop.kind;
+
+  if (this.options.ecmaVersion >= 6) {
+    if (name === "__proto__" && kind === "init") {
+      if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
+      propHash.proto = true;
+    }
+    return;
+  }
+  name = "$" + name;
+  var other = propHash[name];
+  if (other) {
+    var isGetSet = kind !== "init";
+    if ((this.strict || isGetSet) && other[kind] || !(isGetSet ^ other.init)) this.raise(key.start, "Redefinition of property");
+  } else {
+    other = propHash[name] = {
+      init: false,
+      get: false,
+      set: false
+    };
+  }
+  other[kind] = true;
+};
+
+// ### Expression parsing
+
+// These nest, from the most general expression type at the top to
+// 'atomic', nondivisible expression types at the bottom. Most of
+// the functions will simply let the function(s) below them parse,
+// and, *if* the syntactic construct they handle is present, wrap
+// the AST node that the inner parser gave them in another node.
+
+// Parse a full expression. The optional arguments are used to
+// forbid the `in` operator (in for loops initalization expressions)
+// and provide reference for storing '=' operator inside shorthand
+// property assignment in contexts where both object expression
+// and object pattern might appear (so it's possible to raise
+// delayed syntax error at correct position).
+
+pp.parseExpression = function (noIn, refDestructuringErrors) {
+  var startPos = this.start,
+      startLoc = this.startLoc;
+  var expr = this.parseMaybeAssign(noIn, refDestructuringErrors);
+  if (this.type === _tokentype.types.comma) {
+    var node = this.startNodeAt(startPos, startLoc);
+    node.expressions = [expr];
+    while (this.eat(_tokentype.types.comma)) node.expressions.push(this.parseMaybeAssign(noIn, refDestructuringErrors));
+    return this.finishNode(node, "SequenceExpression");
+  }
+  return expr;
+};
+
+// Parse an assignment expression. This includes applications of
+// operators like `+=`.
+
+pp.parseMaybeAssign = function (noIn, refDestructuringErrors, afterLeftParse) {
+  if (this.type == _tokentype.types._yield && this.inGenerator) return this.parseYield();
+
+  var validateDestructuring = false;
+  if (!refDestructuringErrors) {
+    refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 };
+    validateDestructuring = true;
+  }
+  var startPos = this.start,
+      startLoc = this.startLoc;
+  if (this.type == _tokentype.types.parenL || this.type == _tokentype.types.name) this.potentialArrowAt = this.start;
+  var left = this.parseMaybeConditional(noIn, refDestructuringErrors);
+  if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc);
+  if (this.type.isAssign) {
+    if (validateDestructuring) this.checkPatternErrors(refDestructuringErrors, true);
+    var node = this.startNodeAt(startPos, startLoc);
+    node.operator = this.value;
+    node.left = this.type === _tokentype.types.eq ? this.toAssignable(left) : left;
+    refDestructuringErrors.shorthandAssign = 0; // reset because shorthand default was used correctly
+    this.checkLVal(left);
+    this.next();
+    node.right = this.parseMaybeAssign(noIn);
+    return this.finishNode(node, "AssignmentExpression");
+  } else {
+    if (validateDestructuring) this.checkExpressionErrors(refDestructuringErrors, true);
+  }
+  return left;
+};
+
+// Parse a ternary conditional (`?:`) operator.
+
+pp.parseMaybeConditional = function (noIn, refDestructuringErrors) {
+  var startPos = this.start,
+      startLoc = this.startLoc;
+  var expr = this.parseExprOps(noIn, refDestructuringErrors);
+  if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
+  if (this.eat(_tokentype.types.question)) {
+    var node = this.startNodeAt(startPos, startLoc);
+    node.test = expr;
+    node.consequent = this.parseMaybeAssign();
+    this.expect(_tokentype.types.colon);
+    node.alternate = this.parseMaybeAssign(noIn);
+    return this.finishNode(node, "ConditionalExpression");
+  }
+  return expr;
+};
+
+// Start the precedence parser.
+
+pp.parseExprOps = function (noIn, refDestructuringErrors) {
+  var startPos = this.start,
+      startLoc = this.startLoc;
+  var expr = this.parseMaybeUnary(refDestructuringErrors);
+  if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
+  return this.parseExprOp(expr, startPos, startLoc, -1, noIn);
+};
+
+// Parse binary operators with the operator precedence parsing
+// algorithm. `left` is the left-hand side of the operator.
+// `minPrec` provides context that allows the function to stop and
+// defer further parser to one of its callers when it encounters an
+// operator that has a lower precedence than the set it is parsing.
+
+pp.parseExprOp = function (left, leftStartPos, leftStartLoc, minPrec, noIn) {
+  var prec = this.type.binop;
+  if (prec != null && (!noIn || this.type !== _tokentype.types._in)) {
+    if (prec > minPrec) {
+      var node = this.startNodeAt(leftStartPos, leftStartLoc);
+      node.left = left;
+      node.operator = this.value;
+      var op = this.type;
+      this.next();
+      var startPos = this.start,
+          startLoc = this.startLoc;
+      node.right = this.parseExprOp(this.parseMaybeUnary(), startPos, startLoc, prec, noIn);
+      this.finishNode(node, op === _tokentype.types.logicalOR || op === _tokentype.types.logicalAND ? "LogicalExpression" : "BinaryExpression");
+      return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn);
+    }
+  }
+  return left;
+};
+
+// Parse unary operators, both prefix and postfix.
+
+pp.parseMaybeUnary = function (refDestructuringErrors) {
+  if (this.type.prefix) {
+    var node = this.startNode(),
+        update = this.type === _tokentype.types.incDec;
+    node.operator = this.value;
+    node.prefix = true;
+    this.next();
+    node.argument = this.parseMaybeUnary();
+    this.checkExpressionErrors(refDestructuringErrors, true);
+    if (update) this.checkLVal(node.argument);else if (this.strict && node.operator === "delete" && node.argument.type === "Identifier") this.raise(node.start, "Deleting local variable in strict mode");
+    return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
+  }
+  var startPos = this.start,
+      startLoc = this.startLoc;
+  var expr = this.parseExprSubscripts(refDestructuringErrors);
+  if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
+  while (this.type.postfix && !this.canInsertSemicolon()) {
+    var node = this.startNodeAt(startPos, startLoc);
+    node.operator = this.value;
+    node.prefix = false;
+    node.argument = expr;
+    this.checkLVal(expr);
+    this.next();
+    expr = this.finishNode(node, "UpdateExpression");
+  }
+  return expr;
+};
+
+// Parse call, dot, and `[]`-subscript expressions.
+
+pp.parseExprSubscripts = function (refDestructuringErrors) {
+  var startPos = this.start,
+      startLoc = this.startLoc;
+  var expr = this.parseExprAtom(refDestructuringErrors);
+  var skipArrowSubscripts = expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")";
+  if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) return expr;
+  return this.parseSubscripts(expr, startPos, startLoc);
+};
+
+pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
+  for (;;) {
+    if (this.eat(_tokentype.types.dot)) {
+      var node = this.startNodeAt(startPos, startLoc);
+      node.object = base;
+      node.property = this.parseIdent(true);
+      node.computed = false;
+      base = this.finishNode(node, "MemberExpression");
+    } else if (this.eat(_tokentype.types.bracketL)) {
+      var node = this.startNodeAt(startPos, startLoc);
+      node.object = base;
+      node.property = this.parseExpression();
+      node.computed = true;
+      this.expect(_tokentype.types.bracketR);
+      base = this.finishNode(node, "MemberExpression");
+    } else if (!noCalls && this.eat(_tokentype.types.parenL)) {
+      var node = this.startNodeAt(startPos, startLoc);
+      node.callee = base;
+      node.arguments = this.parseExprList(_tokentype.types.parenR, false);
+      base = this.finishNode(node, "CallExpression");
+    } else if (this.type === _tokentype.types.backQuote) {
+      var node = this.startNodeAt(startPos, startLoc);
+      node.tag = base;
+      node.quasi = this.parseTemplate();
+      base = this.finishNode(node, "TaggedTemplateExpression");
+    } else {
+      return base;
+    }
+  }
+};
+
+// Parse an atomic expression — either a single token that is an
+// expression, an expression started by a keyword like `function` or
+// `new`, or an expression wrapped in punctuation like `()`, `[]`,
+// or `{}`.
+
+pp.parseExprAtom = function (refDestructuringErrors) {
+  var node = undefined,
+      canBeArrow = this.potentialArrowAt == this.start;
+  switch (this.type) {
+    case _tokentype.types._super:
+      if (!this.inFunction) this.raise(this.start, "'super' outside of function or class");
+    case _tokentype.types._this:
+      var type = this.type === _tokentype.types._this ? "ThisExpression" : "Super";
+      node = this.startNode();
+      this.next();
+      return this.finishNode(node, type);
+
+    case _tokentype.types._yield:
+      if (this.inGenerator) this.unexpected();
+
+    case _tokentype.types.name:
+      var startPos = this.start,
+          startLoc = this.startLoc;
+      var id = this.parseIdent(this.type !== _tokentype.types.name);
+      if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id]);
+      return id;
+
+    case _tokentype.types.regexp:
+      var value = this.value;
+      node = this.parseLiteral(value.value);
+      node.regex = { pattern: value.pattern, flags: value.flags };
+      return node;
+
+    case _tokentype.types.num:case _tokentype.types.string:
+      return this.parseLiteral(this.value);
+
+    case _tokentype.types._null:case _tokentype.types._true:case _tokentype.types._false:
+      node = this.startNode();
+      node.value = this.type === _tokentype.types._null ? null : this.type === _tokentype.types._true;
+      node.raw = this.type.keyword;
+      this.next();
+      return this.finishNode(node, "Literal");
+
+    case _tokentype.types.parenL:
+      return this.parseParenAndDistinguishExpression(canBeArrow);
+
+    case _tokentype.types.bracketL:
+      node = this.startNode();
+      this.next();
+      // check whether this is array comprehension or regular array
+      if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) {
+        return this.parseComprehension(node, false);
+      }
+      node.elements = this.parseExprList(_tokentype.types.bracketR, true, true, refDestructuringErrors);
+      return this.finishNode(node, "ArrayExpression");
+
+    case _tokentype.types.braceL:
+      return this.parseObj(false, refDestructuringErrors);
+
+    case _tokentype.types._function:
+      node = this.startNode();
+      this.next();
+      return this.parseFunction(node, false);
+
+    case _tokentype.types._class:
+      return this.parseClass(this.startNode(), false);
+
+    case _tokentype.types._new:
+      return this.parseNew();
+
+    case _tokentype.types.backQuote:
+      return this.parseTemplate();
+
+    default:
+      this.unexpected();
+  }
+};
+
+pp.parseLiteral = function (value) {
+  var node = this.startNode();
+  node.value = value;
+  node.raw = this.input.slice(this.start, this.end);
+  this.next();
+  return this.finishNode(node, "Literal");
+};
+
+pp.parseParenExpression = function () {
+  this.expect(_tokentype.types.parenL);
+  var val = this.parseExpression();
+  this.expect(_tokentype.types.parenR);
+  return val;
+};
+
+pp.parseParenAndDistinguishExpression = function (canBeArrow) {
+  var startPos = this.start,
+      startLoc = this.startLoc,
+      val = undefined;
+  if (this.options.ecmaVersion >= 6) {
+    this.next();
+
+    if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) {
+      return this.parseComprehension(this.startNodeAt(startPos, startLoc), true);
+    }
+
+    var innerStartPos = this.start,
+        innerStartLoc = this.startLoc;
+    var exprList = [],
+        first = true;
+    var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 },
+        spreadStart = undefined,
+        innerParenStart = undefined;
+    while (this.type !== _tokentype.types.parenR) {
+      first ? first = false : this.expect(_tokentype.types.comma);
+      if (this.type === _tokentype.types.ellipsis) {
+        spreadStart = this.start;
+        exprList.push(this.parseParenItem(this.parseRest()));
+        break;
+      } else {
+        if (this.type === _tokentype.types.parenL && !innerParenStart) {
+          innerParenStart = this.start;
+        }
+        exprList.push(this.parseMaybeAssign(false, refDestructuringErrors, this.parseParenItem));
+      }
+    }
+    var innerEndPos = this.start,
+        innerEndLoc = this.startLoc;
+    this.expect(_tokentype.types.parenR);
+
+    if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) {
+      this.checkPatternErrors(refDestructuringErrors, true);
+      if (innerParenStart) this.unexpected(innerParenStart);
+      return this.parseParenArrowList(startPos, startLoc, exprList);
+    }
+
+    if (!exprList.length) this.unexpected(this.lastTokStart);
+    if (spreadStart) this.unexpected(spreadStart);
+    this.checkExpressionErrors(refDestructuringErrors, true);
+
+    if (exprList.length > 1) {
+      val = this.startNodeAt(innerStartPos, innerStartLoc);
+      val.expressions = exprList;
+      this.finishNodeAt(val, "SequenceExpression", innerEndPos, innerEndLoc);
+    } else {
+      val = exprList[0];
+    }
+  } else {
+    val = this.parseParenExpression();
+  }
+
+  if (this.options.preserveParens) {
+    var par = this.startNodeAt(startPos, startLoc);
+    par.expression = val;
+    return this.finishNode(par, "ParenthesizedExpression");
+  } else {
+    return val;
+  }
+};
+
+pp.parseParenItem = function (item) {
+  return item;
+};
+
+pp.parseParenArrowList = function (startPos, startLoc, exprList) {
+  return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList);
+};
+
+// New's precedence is slightly tricky. It must allow its argument
+// to be a `[]` or dot subscript expression, but not a call — at
+// least, not without wrapping it in parentheses. Thus, it uses the
+
+var empty = [];
+
+pp.parseNew = function () {
+  var node = this.startNode();
+  var meta = this.parseIdent(true);
+  if (this.options.ecmaVersion >= 6 && this.eat(_tokentype.types.dot)) {
+    node.meta = meta;
+    node.property = this.parseIdent(true);
+    if (node.property.name !== "target") this.raise(node.property.start, "The only valid meta property for new is new.target");
+    if (!this.inFunction) this.raise(node.start, "new.target can only be used in functions");
+    return this.finishNode(node, "MetaProperty");
+  }
+  var startPos = this.start,
+      startLoc = this.startLoc;
+  node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true);
+  if (this.eat(_tokentype.types.parenL)) node.arguments = this.parseExprList(_tokentype.types.parenR, false);else node.arguments = empty;
+  return this.finishNode(node, "NewExpression");
+};
+
+// Parse template expression.
+
+pp.parseTemplateElement = function () {
+  var elem = this.startNode();
+  elem.value = {
+    raw: this.input.slice(this.start, this.end).replace(/\r\n?/g, '\n'),
+    cooked: this.value
+  };
+  this.next();
+  elem.tail = this.type === _tokentype.types.backQuote;
+  return this.finishNode(elem, "TemplateElement");
+};
+
+pp.parseTemplate = function () {
+  var node = this.startNode();
+  this.next();
+  node.expressions = [];
+  var curElt = this.parseTemplateElement();
+  node.quasis = [curElt];
+  while (!curElt.tail) {
+    this.expect(_tokentype.types.dollarBraceL);
+    node.expressions.push(this.parseExpression());
+    this.expect(_tokentype.types.braceR);
+    node.quasis.push(curElt = this.parseTemplateElement());
+  }
+  this.next();
+  return this.finishNode(node, "TemplateLiteral");
+};
+
+// Parse an object literal or binding pattern.
+
+pp.parseObj = function (isPattern, refDestructuringErrors) {
+  var node = this.startNode(),
+      first = true,
+      propHash = {};
+  node.properties = [];
+  this.next();
+  while (!this.eat(_tokentype.types.braceR)) {
+    if (!first) {
+      this.expect(_tokentype.types.comma);
+      if (this.afterTrailingComma(_tokentype.types.braceR)) break;
+    } else first = false;
+
+    var prop = this.startNode(),
+        isGenerator = undefined,
+        startPos = undefined,
+        startLoc = undefined;
+    if (this.options.ecmaVersion >= 6) {
+      prop.method = false;
+      prop.shorthand = false;
+      if (isPattern || refDestructuringErrors) {
+        startPos = this.start;
+        startLoc = this.startLoc;
+      }
+      if (!isPattern) isGenerator = this.eat(_tokentype.types.star);
+    }
+    this.parsePropertyName(prop);
+    this.parsePropertyValue(prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors);
+    this.checkPropClash(prop, propHash);
+    node.properties.push(this.finishNode(prop, "Property"));
+  }
+  return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression");
+};
+
+pp.parsePropertyValue = function (prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) {
+  if (this.eat(_tokentype.types.colon)) {
+    prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refDestructuringErrors);
+    prop.kind = "init";
+  } else if (this.options.ecmaVersion >= 6 && this.type === _tokentype.types.parenL) {
+    if (isPattern) this.unexpected();
+    prop.kind = "init";
+    prop.method = true;
+    prop.value = this.parseMethod(isGenerator);
+  } else if (this.options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set") && (this.type != _tokentype.types.comma && this.type != _tokentype.types.braceR)) {
+    if (isGenerator || isPattern) this.unexpected();
+    prop.kind = prop.key.name;
+    this.parsePropertyName(prop);
+    prop.value = this.parseMethod(false);
+    var paramCount = prop.kind === "get" ? 0 : 1;
+    if (prop.value.params.length !== paramCount) {
+      var start = prop.value.start;
+      if (prop.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param");
+    }
+  } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") {
+    prop.kind = "init";
+    if (isPattern) {
+      if (this.keywords.test(prop.key.name) || (this.strict ? this.reservedWordsStrictBind : this.reservedWords).test(prop.key.name)) this.raise(prop.key.start, "Binding " + prop.key.name);
+      prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);
+    } else if (this.type === _tokentype.types.eq && refDestructuringErrors) {
+      if (!refDestructuringErrors.shorthandAssign) refDestructuringErrors.shorthandAssign = this.start;
+      prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);
+    } else {
+      prop.value = prop.key;
+    }
+    prop.shorthand = true;
+  } else this.unexpected();
+};
+
+pp.parsePropertyName = function (prop) {
+  if (this.options.ecmaVersion >= 6) {
+    if (this.eat(_tokentype.types.bracketL)) {
+      prop.computed = true;
+      prop.key = this.parseMaybeAssign();
+      this.expect(_tokentype.types.bracketR);
+      return prop.key;
+    } else {
+      prop.computed = false;
+    }
+  }
+  return prop.key = this.type === _tokentype.types.num || this.type === _tokentype.types.string ? this.parseExprAtom() : this.parseIdent(true);
+};
+
+// Initialize empty function node.
+
+pp.initFunction = function (node) {
+  node.id = null;
+  if (this.options.ecmaVersion >= 6) {
+    node.generator = false;
+    node.expression = false;
+  }
+};
+
+// Parse object or class method.
+
+pp.parseMethod = function (isGenerator) {
+  var node = this.startNode();
+  this.initFunction(node);
+  this.expect(_tokentype.types.parenL);
+  node.params = this.parseBindingList(_tokentype.types.parenR, false, false);
+  if (this.options.ecmaVersion >= 6) node.generator = isGenerator;
+  this.parseFunctionBody(node, false);
+  return this.finishNode(node, "FunctionExpression");
+};
+
+// Parse arrow function expression with given parameters.
+
+pp.parseArrowExpression = function (node, params) {
+  this.initFunction(node);
+  node.params = this.toAssignableList(params, true);
+  this.parseFunctionBody(node, true);
+  return this.finishNode(node, "ArrowFunctionExpression");
+};
+
+// Parse function body and check parameters.
+
+pp.parseFunctionBody = function (node, isArrowFunction) {
+  var isExpression = isArrowFunction && this.type !== _tokentype.types.braceL;
+
+  if (isExpression) {
+    node.body = this.parseMaybeAssign();
+    node.expression = true;
+  } else {
+    // Start a new scope with regard to labels and the `inFunction`
+    // flag (restore them to their old value afterwards).
+    var oldInFunc = this.inFunction,
+        oldInGen = this.inGenerator,
+        oldLabels = this.labels;
+    this.inFunction = true;this.inGenerator = node.generator;this.labels = [];
+    node.body = this.parseBlock(true);
+    node.expression = false;
+    this.inFunction = oldInFunc;this.inGenerator = oldInGen;this.labels = oldLabels;
+  }
+
+  // If this is a strict mode function, verify that argument names
+  // are not repeated, and it does not try to bind the words `eval`
+  // or `arguments`.
+  if (this.strict || !isExpression && node.body.body.length && this.isUseStrict(node.body.body[0])) {
+    var oldStrict = this.strict;
+    this.strict = true;
+    if (node.id) this.checkLVal(node.id, true);
+    this.checkParams(node);
+    this.strict = oldStrict;
+  } else if (isArrowFunction) {
+    this.checkParams(node);
+  }
+};
+
+// Checks function params for various disallowed patterns such as using "eval"
+// or "arguments" and duplicate parameters.
+
+pp.checkParams = function (node) {
+  var nameHash = {};
+  for (var i = 0; i < node.params.length; i++) {
+    this.checkLVal(node.params[i], true, nameHash);
+  }
+};
+
+// Parses a comma-separated list of expressions, and returns them as
+// an array. `close` is the token type that ends the list, and
+// `allowEmpty` can be turned on to allow subsequent commas with
+// nothing in between them to be parsed as `null` (which is needed
+// for array literals).
+
+pp.parseExprList = function (close, allowTrailingComma, allowEmpty, refDestructuringErrors) {
+  var elts = [],
+      first = true;
+  while (!this.eat(close)) {
+    if (!first) {
+      this.expect(_tokentype.types.comma);
+      if (this.type === close && refDestructuringErrors && !refDestructuringErrors.trailingComma) {
+        refDestructuringErrors.trailingComma = this.lastTokStart;
+      }
+      if (allowTrailingComma && this.afterTrailingComma(close)) break;
+    } else first = false;
+
+    var elt = undefined;
+    if (allowEmpty && this.type === _tokentype.types.comma) elt = null;else if (this.type === _tokentype.types.ellipsis) elt = this.parseSpread(refDestructuringErrors);else elt = this.parseMaybeAssign(false, refDestructuringErrors);
+    elts.push(elt);
+  }
+  return elts;
+};
+
+// Parse the next token as an identifier. If `liberal` is true (used
+// when parsing properties), it will also convert keywords into
+// identifiers.
+
+pp.parseIdent = function (liberal) {
+  var node = this.startNode();
+  if (liberal && this.options.allowReserved == "never") liberal = false;
+  if (this.type === _tokentype.types.name) {
+    if (!liberal && (this.strict ? this.reservedWordsStrict : this.reservedWords).test(this.value) && (this.options.ecmaVersion >= 6 || this.input.slice(this.start, this.end).indexOf("\\") == -1)) this.raise(this.start, "The keyword '" + this.value + "' is reserved");
+    node.name = this.value;
+  } else if (liberal && this.type.keyword) {
+    node.name = this.type.keyword;
+  } else {
+    this.unexpected();
+  }
+  this.next();
+  return this.finishNode(node, "Identifier");
+};
+
+// Parses yield expression inside generator.
+
+pp.parseYield = function () {
+  var node = this.startNode();
+  this.next();
+  if (this.type == _tokentype.types.semi || this.canInsertSemicolon() || this.type != _tokentype.types.star && !this.type.startsExpr) {
+    node.delegate = false;
+    node.argument = null;
+  } else {
+    node.delegate = this.eat(_tokentype.types.star);
+    node.argument = this.parseMaybeAssign();
+  }
+  return this.finishNode(node, "YieldExpression");
+};
+
+// Parses array and generator comprehensions.
+
+pp.parseComprehension = function (node, isGenerator) {
+  node.blocks = [];
+  while (this.type === _tokentype.types._for) {
+    var block = this.startNode();
+    this.next();
+    this.expect(_tokentype.types.parenL);
+    block.left = this.parseBindingAtom();
+    this.checkLVal(block.left, true);
+    this.expectContextual("of");
+    block.right = this.parseExpression();
+    this.expect(_tokentype.types.parenR);
+    node.blocks.push(this.finishNode(block, "ComprehensionBlock"));
+  }
+  node.filter = this.eat(_tokentype.types._if) ? this.parseParenExpression() : null;
+  node.body = this.parseExpression();
+  this.expect(isGenerator ? _tokentype.types.parenR : _tokentype.types.bracketR);
+  node.generator = isGenerator;
+  return this.finishNode(node, "ComprehensionExpression");
+};
+
+},{"./state":10,"./tokentype":14}],2:[function(_dereq_,module,exports){
+// This is a trick taken from Esprima. It turns out that, on
+// non-Chrome browsers, to check whether a string is in a set, a
+// predicate containing a big ugly `switch` statement is faster than
+// a regular expression, and on Chrome the two are about on par.
+// This function uses `eval` (non-lexical) to produce such a
+// predicate from a space-separated string of words.
+//
+// It starts by sorting the words by length.
+
+// Reserved word lists for various dialects of the language
+
+"use strict";
+
+exports.__esModule = true;
+exports.isIdentifierStart = isIdentifierStart;
+exports.isIdentifierChar = isIdentifierChar;
+var reservedWords = {
+  3: "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile",
+  5: "class enum extends super const export import",
+  6: "enum",
+  strict: "implements interface let package private protected public static yield",
+  strictBind: "eval arguments"
+};
+
+exports.reservedWords = reservedWords;
+// And the keywords
+
+var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this";
+
+var keywords = {
+  5: ecma5AndLessKeywords,
+  6: ecma5AndLessKeywords + " let const class extends export import yield super"
+};
+
+exports.keywords = keywords;
+// ## Character categories
+
+// Big ugly regular expressions that match characters in the
+// whitespace, identifier, and identifier-start categories. These
+// are only applied when a character is found to actually have a
+// code point above 128.
+// Generated by `bin/generate-identifier-regex.js`.
+
+var nonASCIIidentifierStartChars = "ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢲऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞭꞰꞱꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭟꭤꭥꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ";
+var nonASCIIidentifierChars = "‌‍·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣤ-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఃా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ංඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ູົຼ່-ໍ໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠐-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏ᦰ-ᧀᧈᧉ᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷼-᷿‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꘠-꘩꙯ꙴ-꙽ꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-꣄꣐-꣙꣠-꣱꤀-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︭︳︴﹍-﹏0-9_";
+
+var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
+var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
+
+nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
+
+// These are a run-length and offset encoded representation of the
+// >0xffff code points that are a valid part of identifiers. The
+// offset starts at 0x10000, and each pair of numbers represents an
+// offset to the next range, and then a size of the range. They were
+// generated by tools/generate-identifier-regex.js
+var astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 17, 26, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 99, 39, 9, 51, 157, 310, 10, 21, 11, 7, 153, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 98, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 26, 45, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 955, 52, 76, 44, 33, 24, 27, 35, 42, 34, 4, 0, 13, 47, 15, 3, 22, 0, 38, 17, 2, 24, 133, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 32, 4, 287, 47, 21, 1, 2, 0, 185, 46, 82, 47, 21, 0, 60, 42, 502, 63, 32, 0, 449, 56, 1288, 920, 104, 110, 2962, 1070, 13266, 568, 8, 30, 114, 29, 19, 47, 17, 3, 32, 20, 6, 18, 881, 68, 12, 0, 67, 12, 16481, 1, 3071, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 4149, 196, 1340, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42710, 42, 4148, 12, 221, 16355, 541];
+var astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 1306, 2, 54, 14, 32, 9, 16, 3, 46, 10, 54, 9, 7, 2, 37, 13, 2, 9, 52, 0, 13, 2, 49, 13, 16, 9, 83, 11, 168, 11, 6, 9, 8, 2, 57, 0, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 316, 19, 13, 9, 214, 6, 3, 8, 112, 16, 16, 9, 82, 12, 9, 9, 535, 9, 20855, 9, 135, 4, 60, 6, 26, 9, 1016, 45, 17, 3, 19723, 1, 5319, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 4305, 6, 792618, 239];
+
+// This has a complexity linear to the value of the code. The
+// assumption is that looking up astral identifier characters is
+// rare.
+function isInAstralSet(code, set) {
+  var pos = 0x10000;
+  for (var i = 0; i < set.length; i += 2) {
+    pos += set[i];
+    if (pos > code) return false;
+    pos += set[i + 1];
+    if (pos >= code) return true;
+  }
+}
+
+// Test whether a given character code starts an identifier.
+
+function isIdentifierStart(code, astral) {
+  if (code < 65) return code === 36;
+  if (code < 91) return true;
+  if (code < 97) return code === 95;
+  if (code < 123) return true;
+  if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
+  if (astral === false) return false;
+  return isInAstralSet(code, astralIdentifierStartCodes);
+}
+
+// Test whether a given character is part of an identifier.
+
+function isIdentifierChar(code, astral) {
+  if (code < 48) return code === 36;
+  if (code < 58) return true;
+  if (code < 65) return false;
+  if (code < 91) return true;
+  if (code < 97) return code === 95;
+  if (code < 123) return true;
+  if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
+  if (astral === false) return false;
+  return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
+}
+
+},{}],3:[function(_dereq_,module,exports){
 // Acorn is a tiny, fast JavaScript parser written in JavaScript.
 //
-// Acorn was written by Marijn Haverbeke and various contributors and
-// released under an MIT license. The Unicode regexps (for identifiers
-// and whitespace) were taken from [Esprima](http://esprima.org) by
-// Ariya Hidayat.
+// Acorn was written by Marijn Haverbeke, Ingvar Stepanyan, and
+// various contributors and released under an MIT license.
 //
 // Git repositories for Acorn are available at
 //
 //     http://marijnhaverbeke.nl/git/acorn
-//     https://github.com/marijnh/acorn.git
+//     https://github.com/ternjs/acorn.git
 //
 // Please use the [github bug tracker][ghbt] to report issues.
 //
-// [ghbt]: https://github.com/marijnh/acorn/issues
+// [ghbt]: https://github.com/ternjs/acorn/issues
 //
 // This file defines the main parser interface. The library also comes
 // with a [error-tolerant parser][dammit] and an
 // [abstract syntax tree walker][walk], defined in other files.
 //
 // [dammit]: acorn_loose.js
 // [walk]: util/walk.js
 
-(function(root, mod) {
-  if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
-  if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
-  mod(root.acorn || (root.acorn = {})); // Plain browser env
-})(this, function(exports) {
-  "use strict";
-
-  exports.version = "0.11.1";
-
-  // The main exported interface (under `self.acorn` when in the
-  // browser) is a `parse` function that takes a code string and
-  // returns an abstract syntax tree as specified by [Mozilla parser
-  // API][api], with the caveat that inline XML is not recognized.
-  //
-  // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
-
-  var options, input, inputLen, sourceFile;
-
-  exports.parse = function(inpt, opts) {
-    input = String(inpt); inputLen = input.length;
-    setOptions(opts);
-    initTokenState();
-    var startPos = options.locations ? [tokPos, curPosition()] : tokPos;
-    initParserState();
-    return parseTopLevel(options.program || startNodeAt(startPos));
+"use strict";
+
+exports.__esModule = true;
+exports.parse = parse;
+exports.parseExpressionAt = parseExpressionAt;
+exports.tokenizer = tokenizer;
+
+var _state = _dereq_("./state");
+
+_dereq_("./parseutil");
+
+_dereq_("./statement");
+
+_dereq_("./lval");
+
+_dereq_("./expression");
+
+_dereq_("./location");
+
+exports.Parser = _state.Parser;
+exports.plugins = _state.plugins;
+
+var _options = _dereq_("./options");
+
+exports.defaultOptions = _options.defaultOptions;
+
+var _locutil = _dereq_("./locutil");
+
+exports.Position = _locutil.Position;
+exports.SourceLocation = _locutil.SourceLocation;
+exports.getLineInfo = _locutil.getLineInfo;
+
+var _node = _dereq_("./node");
+
+exports.Node = _node.Node;
+
+var _tokentype = _dereq_("./tokentype");
+
+exports.TokenType = _tokentype.TokenType;
+exports.tokTypes = _tokentype.types;
+
+var _tokencontext = _dereq_("./tokencontext");
+
+exports.TokContext = _tokencontext.TokContext;
+exports.tokContexts = _tokencontext.types;
+
+var _identifier = _dereq_("./identifier");
+
+exports.isIdentifierChar = _identifier.isIdentifierChar;
+exports.isIdentifierStart = _identifier.isIdentifierStart;
+
+var _tokenize = _dereq_("./tokenize");
+
+exports.Token = _tokenize.Token;
+
+var _whitespace = _dereq_("./whitespace");
+
+exports.isNewLine = _whitespace.isNewLine;
+exports.lineBreak = _whitespace.lineBreak;
+exports.lineBreakG = _whitespace.lineBreakG;
+var version = "2.6.4";
+
+exports.version = version;
+// The main exported interface (under `self.acorn` when in the
+// browser) is a `parse` function that takes a code string and
+// returns an abstract syntax tree as specified by [Mozilla parser
+// API][api].
+//
+// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
+
+function parse(input, options) {
+  return new _state.Parser(options, input).parse();
+}
+
+// This function tries to parse a single expression at a given
+// offset in a string. Useful for parsing mixed-language formats
+// that embed JavaScript expressions.
+
+function parseExpressionAt(input, pos, options) {
+  var p = new _state.Parser(options, input, pos);
+  p.nextToken();
+  return p.parseExpression();
+}
+
+// Acorn is organized as a tokenizer and a recursive-descent parser.
+// The `tokenizer` export provides an interface to the tokenizer.
+
+function tokenizer(input, options) {
+  return new _state.Parser(options, input);
+}
+
+},{"./expression":1,"./identifier":2,"./location":4,"./locutil":5,"./lval":6,"./node":7,"./options":8,"./parseutil":9,"./state":10,"./statement":11,"./tokencontext":12,"./tokenize":13,"./tokentype":14,"./whitespace":16}],4:[function(_dereq_,module,exports){
+"use strict";
+
+var _state = _dereq_("./state");
+
+var _locutil = _dereq_("./locutil");
+
+var pp = _state.Parser.prototype;
+
+// This function is used to raise exceptions on parse errors. It
+// takes an offset integer (into the current `input`) to indicate
+// the location of the error, attaches the position to the end
+// of the error message, and then raises a `SyntaxError` with that
+// message.
+
+pp.raise = function (pos, message) {
+  var loc = _locutil.getLineInfo(this.input, pos);
+  message += " (" + loc.line + ":" + loc.column + ")";
+  var err = new SyntaxError(message);
+  err.pos = pos;err.loc = loc;err.raisedAt = this.pos;
+  throw err;
+};
+
+pp.curPosition = function () {
+  if (this.options.locations) {
+    return new _locutil.Position(this.curLine, this.pos - this.lineStart);
+  }
+};
+
+},{"./locutil":5,"./state":10}],5:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+exports.getLineInfo = getLineInfo;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _whitespace = _dereq_("./whitespace");
+
+// These are used when `options.locations` is on, for the
+// `startLoc` and `endLoc` properties.
+
+var Position = (function () {
+  function Position(line, col) {
+    _classCallCheck(this, Position);
+
+    this.line = line;
+    this.column = col;
+  }
+
+  Position.prototype.offset = function offset(n) {
+    return new Position(this.line, this.column + n);
   };
 
-  // A second optional argument can be given to further configure
-  // the parser process. These options are recognized:
-
-  var defaultOptions = exports.defaultOptions = {
-    // `ecmaVersion` indicates the ECMAScript version to parse. Must
-    // be either 3, or 5, or 6. This influences support for strict
-    // mode, the set of reserved words, support for getters and
-    // setters and other features.
-    ecmaVersion: 5,
-    // Turn on `strictSemicolons` to prevent the parser from doing
-    // automatic semicolon insertion.
-    strictSemicolons: false,
-    // When `allowTrailingCommas` is false, the parser will not allow
-    // trailing commas in array and object literals.
-    allowTrailingCommas: true,
-    // By default, reserved words are not enforced. Enable
-    // `forbidReserved` to enforce them. When this option has the
-    // value "everywhere", reserved words and keywords can also not be
-    // used as property names.
-    forbidReserved: false,
-    // When enabled, a return at the top level is not considered an
-    // error.
-    allowReturnOutsideFunction: false,
-    // When enabled, import/export statements are not constrained to
-    // appearing at the top of the program.
-    allowImportExportEverywhere: false,
-    // When enabled, hashbang directive in the beginning of file
-    // is allowed and treated as a line comment.
-    allowHashBang: false,
-    // When `locations` is on, `loc` properties holding objects with
-    // `start` and `end` properties in `{line, column}` form (with
-    // line being 1-based and column 0-based) will be attached to the
-    // nodes.
-    locations: false,
-    // A function can be passed as `onToken` option, which will
-    // cause Acorn to call that function with object in the same
-    // format as tokenize() returns. Note that you are not
-    // allowed to call the parser from the callback—that will
-    // corrupt its internal state.
-    onToken: null,
-    // A function can be passed as `onComment` option, which will
-    // cause Acorn to call that function with `(block, text, start,
-    // end)` parameters whenever a comment is skipped. `block` is a
-    // boolean indicating whether this is a block (`/* */`) comment,
-    // `text` is the content of the comment, and `start` and `end` are
-    // character offsets that denote the start and end of the comment.
-    // When the `locations` option is on, two more parameters are
-    // passed, the full `{line, column}` locations of the start and
-    // end of the comments. Note that you are not allowed to call the
-    // parser from the callback—that will corrupt its internal state.
-    onComment: null,
-    // Nodes have their start and end characters offsets recorded in
-    // `start` and `end` properties (directly on the node, rather than
-    // the `loc` object, which holds line/column data. To also add a
-    // [semi-standardized][range] `range` property holding a `[start,
-    // end]` array with the same numbers, set the `ranges` option to
-    // `true`.
-    //
-    // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
-    ranges: false,
-    // It is possible to parse multiple files into a single AST by
-    // passing the tree produced by parsing the first file as
-    // `program` option in subsequent parses. This will add the
-    // toplevel forms of the parsed file to the `Program` (top) node
-    // of an existing parse tree.
-    program: null,
-    // When `locations` is on, you can pass this to record the source
-    // file in every node's `loc` object.
-    sourceFile: null,
-    // This value, if given, is stored in every node, whether
-    // `locations` is on or off.
-    directSourceFile: null,
-    // When enabled, parenthesized expressions are represented by
-    // (non-standard) ParenthesizedExpression nodes
-    preserveParens: false
-  };
-
-  // This function tries to parse a single expression at a given
-  // offset in a string. Useful for parsing mixed-language formats
-  // that embed JavaScript expressions.
-
-  exports.parseExpressionAt = function(inpt, pos, opts) {
-    input = String(inpt); inputLen = input.length;
-    setOptions(opts);
-    initTokenState(pos);
-    initParserState();
-    return parseExpression();
-  };
-
-  var isArray = function (obj) {
-    return Object.prototype.toString.call(obj) === "[object Array]";
-  };
-
-  function setOptions(opts) {
-    options = {};
-    for (var opt in defaultOptions)
-      options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt];
-    sourceFile = options.sourceFile || null;
-    if (isArray(options.onToken)) {
+  return Position;
+})();
+
+exports.Position = Position;
+
+var SourceLocation = function SourceLocation(p, start, end) {
+  _classCallCheck(this, SourceLocation);
+
+  this.start = start;
+  this.end = end;
+  if (p.sourceFile !== null) this.source = p.sourceFile;
+}
+
+// The `getLineInfo` function is mostly useful when the
+// `locations` option is off (for performance reasons) and you
+// want to find the line/column position for a given character
+// offset. `input` should be the code string that the offset refers
+// into.
+
+;
+
+exports.SourceLocation = SourceLocation;
+
+function getLineInfo(input, offset) {
+  for (var line = 1, cur = 0;;) {
+    _whitespace.lineBreakG.lastIndex = cur;
+    var match = _whitespace.lineBreakG.exec(input);
+    if (match && match.index < offset) {
+      ++line;
+      cur = match.index + match[0].length;
+    } else {
+      return new Position(line, offset - cur);
+    }
+  }
+}
+
+},{"./whitespace":16}],6:[function(_dereq_,module,exports){
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _util = _dereq_("./util");
+
+var pp = _state.Parser.prototype;
+
+// Convert existing expression atom to assignable pattern
+// if possible.
+
+pp.toAssignable = function (node, isBinding) {
+  if (this.options.ecmaVersion >= 6 && node) {
+    switch (node.type) {
+      case "Identifier":
+      case "ObjectPattern":
+      case "ArrayPattern":
+        break;
+
+      case "ObjectExpression":
+        node.type = "ObjectPattern";
+        for (var i = 0; i < node.properties.length; i++) {
+          var prop = node.properties[i];
+          if (prop.kind !== "init") this.raise(prop.key.start, "Object pattern can't contain getter or setter");
+          this.toAssignable(prop.value, isBinding);
+        }
+        break;
+
+      case "ArrayExpression":
+        node.type = "ArrayPattern";
+        this.toAssignableList(node.elements, isBinding);
+        break;
+
+      case "AssignmentExpression":
+        if (node.operator === "=") {
+          node.type = "AssignmentPattern";
+          delete node.operator;
+          // falls through to AssignmentPattern
+        } else {
+            this.raise(node.left.end, "Only '=' operator can be used for specifying default value.");
+            break;
+          }
+
+      case "AssignmentPattern":
+        if (node.right.type === "YieldExpression") this.raise(node.right.start, "Yield expression cannot be a default value");
+        break;
+
+      case "ParenthesizedExpression":
+        node.expression = this.toAssignable(node.expression, isBinding);
+        break;
+
+      case "MemberExpression":
+        if (!isBinding) break;
+
+      default:
+        this.raise(node.start, "Assigning to rvalue");
+    }
+  }
+  return node;
+};
+
+// Convert list of expression atoms to binding list.
+
+pp.toAssignableList = function (exprList, isBinding) {
+  var end = exprList.length;
+  if (end) {
+    var last = exprList[end - 1];
+    if (last && last.type == "RestElement") {
+      --end;
+    } else if (last && last.type == "SpreadElement") {
+      last.type = "RestElement";
+      var arg = last.argument;
+      this.toAssignable(arg, isBinding);
+      if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") this.unexpected(arg.start);
+      --end;
+    }
+
+    if (isBinding && last.type === "RestElement" && last.argument.type !== "Identifier") this.unexpected(last.argument.start);
+  }
+  for (var i = 0; i < end; i++) {
+    var elt = exprList[i];
+    if (elt) this.toAssignable(elt, isBinding);
+  }
+  return exprList;
+};
+
+// Parses spread element.
+
+pp.parseSpread = function (refDestructuringErrors) {
+  var node = this.startNode();
+  this.next();
+  node.argument = this.parseMaybeAssign(refDestructuringErrors);
+  return this.finishNode(node, "SpreadElement");
+};
+
+pp.parseRest = function (allowNonIdent) {
+  var node = this.startNode();
+  this.next();
+
+  // RestElement inside of a function parameter must be an identifier
+  if (allowNonIdent) node.argument = this.type === _tokentype.types.name ? this.parseIdent() : this.unexpected();else node.argument = this.type === _tokentype.types.name || this.type === _tokentype.types.bracketL ? this.parseBindingAtom() : this.unexpected();
+
+  return this.finishNode(node, "RestElement");
+};
+
+// Parses lvalue (assignable) atom.
+
+pp.parseBindingAtom = function () {
+  if (this.options.ecmaVersion < 6) return this.parseIdent();
+  switch (this.type) {
+    case _tokentype.types.name:
+      return this.parseIdent();
+
+    case _tokentype.types.bracketL:
+      var node = this.startNode();
+      this.next();
+      node.elements = this.parseBindingList(_tokentype.types.bracketR, true, true);
+      return this.finishNode(node, "ArrayPattern");
+
+    case _tokentype.types.braceL:
+      return this.parseObj(true);
+
+    default:
+      this.unexpected();
+  }
+};
+
+pp.parseBindingList = function (close, allowEmpty, allowTrailingComma, allowNonIdent) {
+  var elts = [],
+      first = true;
+  while (!this.eat(close)) {
+    if (first) first = false;else this.expect(_tokentype.types.comma);
+    if (allowEmpty && this.type === _tokentype.types.comma) {
+      elts.push(null);
+    } else if (allowTrailingComma && this.afterTrailingComma(close)) {
+      break;
+    } else if (this.type === _tokentype.types.ellipsis) {
+      var rest = this.parseRest(allowNonIdent);
+      this.parseBindingListItem(rest);
+      elts.push(rest);
+      this.expect(close);
+      break;
+    } else {
+      var elem = this.parseMaybeDefault(this.start, this.startLoc);
+      this.parseBindingListItem(elem);
+      elts.push(elem);
+    }
+  }
+  return elts;
+};
+
+pp.parseBindingListItem = function (param) {
+  return param;
+};
+
+// Parses assignment pattern around given atom if possible.
+
+pp.parseMaybeDefault = function (startPos, startLoc, left) {
+  left = left || this.parseBindingAtom();
+  if (this.options.ecmaVersion < 6 || !this.eat(_tokentype.types.eq)) return left;
+  var node = this.startNodeAt(startPos, startLoc);
+  node.left = left;
+  node.right = this.parseMaybeAssign();
+  return this.finishNode(node, "AssignmentPattern");
+};
+
+// Verify that a node is an lval — something that can be assigned
+// to.
+
+pp.checkLVal = function (expr, isBinding, checkClashes) {
+  switch (expr.type) {
+    case "Identifier":
+      if (this.strict && this.reservedWordsStrictBind.test(expr.name)) this.raise(expr.start, (isBinding ? "Binding " : "Assigning to ") + expr.name + " in strict mode");
+      if (checkClashes) {
+        if (_util.has(checkClashes, expr.name)) this.raise(expr.start, "Argument name clash");
+        checkClashes[expr.name] = true;
+      }
+      break;
+
+    case "MemberExpression":
+      if (isBinding) this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression");
+      break;
+
+    case "ObjectPattern":
+      for (var i = 0; i < expr.properties.length; i++) {
+        this.checkLVal(expr.properties[i].value, isBinding, checkClashes);
+      }break;
+
+    case "ArrayPattern":
+      for (var i = 0; i < expr.elements.length; i++) {
+        var elem = expr.elements[i];
+        if (elem) this.checkLVal(elem, isBinding, checkClashes);
+      }
+      break;
+
+    case "AssignmentPattern":
+      this.checkLVal(expr.left, isBinding, checkClashes);
+      break;
+
+    case "RestElement":
+      this.checkLVal(expr.argument, isBinding, checkClashes);
+      break;
+
+    case "ParenthesizedExpression":
+      this.checkLVal(expr.expression, isBinding, checkClashes);
+      break;
+
+    default:
+      this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " rvalue");
+  }
+};
+
+},{"./state":10,"./tokentype":14,"./util":15}],7:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _state = _dereq_("./state");
+
+var _locutil = _dereq_("./locutil");
+
+var Node = function Node(parser, pos, loc) {
+  _classCallCheck(this, Node);
+
+  this.type = "";
+  this.start = pos;
+  this.end = 0;
+  if (parser.options.locations) this.loc = new _locutil.SourceLocation(parser, loc);
+  if (parser.options.directSourceFile) this.sourceFile = parser.options.directSourceFile;
+  if (parser.options.ranges) this.range = [pos, 0];
+}
+
+// Start an AST node, attaching a start offset.
+
+;
+
+exports.Node = Node;
+var pp = _state.Parser.prototype;
+
+pp.startNode = function () {
+  return new Node(this, this.start, this.startLoc);
+};
+
+pp.startNodeAt = function (pos, loc) {
+  return new Node(this, pos, loc);
+};
+
+// Finish an AST node, adding `type` and `end` properties.
+
+function finishNodeAt(node, type, pos, loc) {
+  node.type = type;
+  node.end = pos;
+  if (this.options.locations) node.loc.end = loc;
+  if (this.options.ranges) node.range[1] = pos;
+  return node;
+}
+
+pp.finishNode = function (node, type) {
+  return finishNodeAt.call(this, node, type, this.lastTokEnd, this.lastTokEndLoc);
+};
+
+// Finish node at given position
+
+pp.finishNodeAt = function (node, type, pos, loc) {
+  return finishNodeAt.call(this, node, type, pos, loc);
+};
+
+},{"./locutil":5,"./state":10}],8:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+exports.getOptions = getOptions;
+
+var _util = _dereq_("./util");
+
+var _locutil = _dereq_("./locutil");
+
+// A second optional argument can be given to further configure
+// the parser process. These options are recognized:
+
+var defaultOptions = {
+  // `ecmaVersion` indicates the ECMAScript version to parse. Must
+  // be either 3, or 5, or 6. This influences support for strict
+  // mode, the set of reserved words, support for getters and
+  // setters and other features.
+  ecmaVersion: 5,
+  // Source type ("script" or "module") for different semantics
+  sourceType: "script",
+  // `onInsertedSemicolon` can be a callback that will be called
+  // when a semicolon is automatically inserted. It will be passed
+  // th position of the comma as an offset, and if `locations` is
+  // enabled, it is given the location as a `{line, column}` object
+  // as second argument.
+  onInsertedSemicolon: null,
+  // `onTrailingComma` is similar to `onInsertedSemicolon`, but for
+  // trailing commas.
+  onTrailingComma: null,
+  // By default, reserved words are only enforced if ecmaVersion >= 5.
+  // Set `allowReserved` to a boolean value to explicitly turn this on
+  // an off. When this option has the value "never", reserved words
+  // and keywords can also not be used as property names.
+  allowReserved: null,
+  // When enabled, a return at the top level is not considered an
+  // error.
+  allowReturnOutsideFunction: false,
+  // When enabled, import/export statements are not constrained to
+  // appearing at the top of the program.
+  allowImportExportEverywhere: false,
+  // When enabled, hashbang directive in the beginning of file
+  // is allowed and treated as a line comment.
+  allowHashBang: false,
+  // When `locations` is on, `loc` properties holding objects with
+  // `start` and `end` properties in `{line, column}` form (with
+  // line being 1-based and column 0-based) will be attached to the
+  // nodes.
+  locations: false,
+  // A function can be passed as `onToken` option, which will
+  // cause Acorn to call that function with object in the same
+  // format as tokens returned from `tokenizer().getToken()`. Note
+  // that you are not allowed to call the parser from the
+  // callback—that will corrupt its internal state.
+  onToken: null,
+  // A function can be passed as `onComment` option, which will
+  // cause Acorn to call that function with `(block, text, start,
+  // end)` parameters whenever a comment is skipped. `block` is a
+  // boolean indicating whether this is a block (`/* */`) comment,
+  // `text` is the content of the comment, and `start` and `end` are
+  // character offsets that denote the start and end of the comment.
+  // When the `locations` option is on, two more parameters are
+  // passed, the full `{line, column}` locations of the start and
+  // end of the comments. Note that you are not allowed to call the
+  // parser from the callback—that will corrupt its internal state.
+  onComment: null,
+  // Nodes have their start and end characters offsets recorded in
+  // `start` and `end` properties (directly on the node, rather than
+  // the `loc` object, which holds line/column data. To also add a
+  // [semi-standardized][range] `range` property holding a `[start,
+  // end]` array with the same numbers, set the `ranges` option to
+  // `true`.
+  //
+  // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
+  ranges: false,
+  // It is possible to parse multiple files into a single AST by
+  // passing the tree produced by parsing the first file as
+  // `program` option in subsequent parses. This will add the
+  // toplevel forms of the parsed file to the `Program` (top) node
+  // of an existing parse tree.
+  program: null,
+  // When `locations` is on, you can pass this to record the source
+  // file in every node's `loc` object.
+  sourceFile: null,
+  // This value, if given, is stored in every node, whether
+  // `locations` is on or off.
+  directSourceFile: null,
+  // When enabled, parenthesized expressions are represented by
+  // (non-standard) ParenthesizedExpression nodes
+  preserveParens: false,
+  plugins: {}
+};
+
+exports.defaultOptions = defaultOptions;
+// Interpret and default an options object
+
+function getOptions(opts) {
+  var options = {};
+  for (var opt in defaultOptions) {
+    options[opt] = opts && _util.has(opts, opt) ? opts[opt] : defaultOptions[opt];
+  }if (options.allowReserved == null) options.allowReserved = options.ecmaVersion < 5;
+
+  if (_util.isArray(options.onToken)) {
+    (function () {
       var tokens = options.onToken;
       options.onToken = function (token) {
-        tokens.push(token);
-      };
-    }
-    if (isArray(options.onComment)) {
-      var comments = options.onComment;
-      options.onComment = function (block, text, start, end, startLoc, endLoc) {
-        var comment = {
-          type: block ? 'Block' : 'Line',
-          value: text,
-          start: start,
-          end: end
-        };
-        if (options.locations) {
-          comment.loc = new SourceLocation();
-          comment.loc.start = startLoc;
-          comment.loc.end = endLoc;
-        }
-        if (options.ranges)
-          comment.range = [start, end];
-        comments.push(comment);
+        return tokens.push(token);
       };
-    }
-    isKeyword = options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword;
+    })();
+  }
+  if (_util.isArray(options.onComment)) options.onComment = pushComment(options, options.onComment);
+
+  return options;
+}
+
+function pushComment(options, array) {
+  return function (block, text, start, end, startLoc, endLoc) {
+    var comment = {
+      type: block ? 'Block' : 'Line',
+      value: text,
+      start: start,
+      end: end
+    };
+    if (options.locations) comment.loc = new _locutil.SourceLocation(this, startLoc, endLoc);
+    if (options.ranges) comment.range = [start, end];
+    array.push(comment);
+  };
+}
+
+},{"./locutil":5,"./util":15}],9:[function(_dereq_,module,exports){
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _whitespace = _dereq_("./whitespace");
+
+var pp = _state.Parser.prototype;
+
+// ## Parser utilities
+
+// Test whether a statement node is the string literal `"use strict"`.
+
+pp.isUseStrict = function (stmt) {
+  return this.options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && stmt.expression.raw.slice(1, -1) === "use strict";
+};
+
+// Predicate that tests whether the next token is of the given
+// type, and if yes, consumes it as a side effect.
+
+pp.eat = function (type) {
+  if (this.type === type) {
+    this.next();
+    return true;
+  } else {
+    return false;
+  }
+};
+
+// Tests whether parsed token is a contextual keyword.
+
+pp.isContextual = function (name) {
+  return this.type === _tokentype.types.name && this.value === name;
+};
+
+// Consumes contextual keyword if possible.
+
+pp.eatContextual = function (name) {
+  return this.value === name && this.eat(_tokentype.types.name);
+};
+
+// Asserts that following token is given contextual keyword.
+
+pp.expectContextual = function (name) {
+  if (!this.eatContextual(name)) this.unexpected();
+};
+
+// Test whether a semicolon can be inserted at the current position.
+
+pp.canInsertSemicolon = function () {
+  return this.type === _tokentype.types.eof || this.type === _tokentype.types.braceR || _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start));
+};
+
+pp.insertSemicolon = function () {
+  if (this.canInsertSemicolon()) {
+    if (this.options.onInsertedSemicolon) this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc);
+    return true;
+  }
+};
+
+// Consume a semicolon, or, failing that, see if we are allowed to
+// pretend that there is a semicolon at this position.
+
+pp.semicolon = function () {
+  if (!this.eat(_tokentype.types.semi) && !this.insertSemicolon()) this.unexpected();
+};
+
+pp.afterTrailingComma = function (tokType) {
+  if (this.type == tokType) {
+    if (this.options.onTrailingComma) this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc);
+    this.next();
+    return true;
   }
-
-  // The `getLineInfo` function is mostly useful when the
-  // `locations` option is off (for performance reasons) and you
-  // want to find the line/column position for a given character
-  // offset. `input` should be the code string that the offset refers
-  // into.
-
-  var getLineInfo = exports.getLineInfo = function(input, offset) {
-    for (var line = 1, cur = 0;;) {
-      lineBreak.lastIndex = cur;
-      var match = lineBreak.exec(input);
-      if (match && match.index < offset) {
-        ++line;
-        cur = match.index + match[0].length;
-      } else break;
+};
+
+// Expect a token of a given type. If found, consume it, otherwise,
+// raise an unexpected token error.
+
+pp.expect = function (type) {
+  this.eat(type) || this.unexpected();
+};
+
+// Raise an unexpected token error.
+
+pp.unexpected = function (pos) {
+  this.raise(pos != null ? pos : this.start, "Unexpected token");
+};
+
+pp.checkPatternErrors = function (refDestructuringErrors, andThrow) {
+  var pos = refDestructuringErrors && refDestructuringErrors.trailingComma;
+  if (!andThrow) return !!pos;
+  if (pos) this.raise(pos, "Trailing comma is not permitted in destructuring patterns");
+};
+
+pp.checkExpressionErrors = function (refDestructuringErrors, andThrow) {
+  var pos = refDestructuringErrors && refDestructuringErrors.shorthandAssign;
+  if (!andThrow) return !!pos;
+  if (pos) this.raise(pos, "Shorthand property assignments are valid only in destructuring patterns");
+};
+
+},{"./state":10,"./tokentype":14,"./whitespace":16}],10:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _identifier = _dereq_("./identifier");
+
+var _tokentype = _dereq_("./tokentype");
+
+var _whitespace = _dereq_("./whitespace");
+
+var _options = _dereq_("./options");
+
+// Registered plugins
+var plugins = {};
+
+exports.plugins = plugins;
+function keywordRegexp(words) {
+  return new RegExp("^(" + words.replace(/ /g, "|") + ")$");
+}
+
+var Parser = (function () {
+  function Parser(options, input, startPos) {
+    _classCallCheck(this, Parser);
+
+    this.options = options = _options.getOptions(options);
+    this.sourceFile = options.sourceFile;
+    this.keywords = keywordRegexp(_identifier.keywords[options.ecmaVersion >= 6 ? 6 : 5]);
+    var reserved = options.allowReserved ? "" : _identifier.reservedWords[options.ecmaVersion] + (options.sourceType == "module" ? " await" : "");
+    this.reservedWords = keywordRegexp(reserved);
+    var reservedStrict = (reserved ? reserved + " " : "") + _identifier.reservedWords.strict;
+    this.reservedWordsStrict = keywordRegexp(reservedStrict);
+    this.reservedWordsStrictBind = keywordRegexp(reservedStrict + " " + _identifier.reservedWords.strictBind);
+    this.input = String(input);
+
+    // Used to signal to callers of `readWord1` whether the word
+    // contained any escape sequences. This is needed because words with
+    // escape sequences must not be interpreted as keywords.
+    this.containsEsc = false;
+
+    // Load plugins
+    this.loadPlugins(options.plugins);
+
+    // Set up token state
+
+    // The current position of the tokenizer in the input.
+    if (startPos) {
+      this.pos = startPos;
+      this.lineStart = Math.max(0, this.input.lastIndexOf("\n", startPos));
+      this.curLine = this.input.slice(0, this.lineStart).split(_whitespace.lineBreak).length;
+    } else {
+      this.pos = this.lineStart = 0;
+      this.curLine = 1;
     }
-    return {line: line, column: offset - cur};
+
+    // Properties of the current token:
+    // Its type
+    this.type = _tokentype.types.eof;
+    // For tokens that include more information than their type, the value
+    this.value = null;
+    // Its start and end offset
+    this.start = this.end = this.pos;
+    // And, if locations are used, the {line, column} object
+    // corresponding to those offsets
+    this.startLoc = this.endLoc = this.curPosition();
+
+    // Position information for the previous token
+    this.lastTokEndLoc = this.lastTokStartLoc = null;
+    this.lastTokStart = this.lastTokEnd = this.pos;
+
+    // The context stack is used to superficially track syntactic
+    // context to predict whether a regular expression is allowed in a
+    // given position.
+    this.context = this.initialContext();
+    this.exprAllowed = true;
+
+    // Figure out if it's a module code.
+    this.strict = this.inModule = options.sourceType === "module";
+
+    // Used to signify the start of a potential arrow function
+    this.potentialArrowAt = -1;
+
+    // Flags to track whether we are in a function, a generator.
+    this.inFunction = this.inGenerator = false;
+    // Labels in scope.
+    this.labels = [];
+
+    // If enabled, skip leading hashbang line.
+    if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === '#!') this.skipLineComment(2);
+  }
+
+  // DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them
+
+  Parser.prototype.isKeyword = function isKeyword(word) {
+    return this.keywords.test(word);
+  };
+
+  Parser.prototype.isReservedWord = function isReservedWord(word) {
+    return this.reservedWords.test(word);
+  };
+
+  Parser.prototype.extend = function extend(name, f) {
+    this[name] = f(this[name]);
+  };
+
+  Parser.prototype.loadPlugins = function loadPlugins(pluginConfigs) {
+    for (var _name in pluginConfigs) {
+      var plugin = plugins[_name];
+      if (!plugin) throw new Error("Plugin '" + _name + "' not found");
+      plugin(this, pluginConfigs[_name]);
+    }
+  };
+
+  Parser.prototype.parse = function parse() {
+    var node = this.options.program || this.startNode();
+    this.nextToken();
+    return this.parseTopLevel(node);
   };
 
-  function Token() {
-    this.type = tokType;
-    this.value = tokVal;
-    this.start = tokStart;
-    this.end = tokEnd;
-    if (options.locations) {
-      this.loc = new SourceLocation();
-      this.loc.end = tokEndLoc;
-      // TODO: remove in next major release
-      this.startLoc = tokStartLoc;
-      this.endLoc = tokEndLoc;
+  return Parser;
+})();
+
+exports.Parser = Parser;
+
+},{"./identifier":2,"./options":8,"./tokentype":14,"./whitespace":16}],11:[function(_dereq_,module,exports){
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _whitespace = _dereq_("./whitespace");
+
+var pp = _state.Parser.prototype;
+
+// ### Statement parsing
+
+// Parse a program. Initializes the parser, reads any number of
+// statements, and wraps them in a Program node.  Optionally takes a
+// `program` argument.  If present, the statements will be appended
+// to its body instead of creating a new node.
+
+pp.parseTopLevel = function (node) {
+  var first = true;
+  if (!node.body) node.body = [];
+  while (this.type !== _tokentype.types.eof) {
+    var stmt = this.parseStatement(true, true);
+    node.body.push(stmt);
+    if (first) {
+      if (this.isUseStrict(stmt)) this.setStrict(true);
+      first = false;
     }
-    if (options.ranges)
-      this.range = [tokStart, tokEnd];
+  }
+  this.next();
+  if (this.options.ecmaVersion >= 6) {
+    node.sourceType = this.options.sourceType;
+  }
+  return this.finishNode(node, "Program");
+};
+
+var loopLabel = { kind: "loop" },
+    switchLabel = { kind: "switch" };
+
+// Parse a single statement.
+//
+// If expecting a statement and finding a slash operator, parse a
+// regular expression literal. This is to handle cases like
+// `if (foo) /blah/.exec(foo)`, where looking at the previous token
+// does not help.
+
+pp.parseStatement = function (declaration, topLevel) {
+  var starttype = this.type,
+      node = this.startNode();
+
+  // Most types of statements are recognized by the keyword they
+  // start with. Many are trivial to parse, some require a bit of
+  // complexity.
+
+  switch (starttype) {
+    case _tokentype.types._break:case _tokentype.types._continue:
+      return this.parseBreakContinueStatement(node, starttype.keyword);
+    case _tokentype.types._debugger:
+      return this.parseDebuggerStatement(node);
+    case _tokentype.types._do:
+      return this.parseDoStatement(node);
+    case _tokentype.types._for:
+      return this.parseForStatement(node);
+    case _tokentype.types._function:
+      if (!declaration && this.options.ecmaVersion >= 6) this.unexpected();
+      return this.parseFunctionStatement(node);
+    case _tokentype.types._class:
+      if (!declaration) this.unexpected();
+      return this.parseClass(node, true);
+    case _tokentype.types._if:
+      return this.parseIfStatement(node);
+    case _tokentype.types._return:
+      return this.parseReturnStatement(node);
+    case _tokentype.types._switch:
+      return this.parseSwitchStatement(node);
+    case _tokentype.types._throw:
+      return this.parseThrowStatement(node);
+    case _tokentype.types._try:
+      return this.parseTryStatement(node);
+    case _tokentype.types._let:case _tokentype.types._const:
+      if (!declaration) this.unexpected(); // NOTE: falls through to _var
+    case _tokentype.types._var:
+      return this.parseVarStatement(node, starttype);
+    case _tokentype.types._while:
+      return this.parseWhileStatement(node);
+    case _tokentype.types._with:
+      return this.parseWithStatement(node);
+    case _tokentype.types.braceL:
+      return this.parseBlock();
+    case _tokentype.types.semi:
+      return this.parseEmptyStatement(node);
+    case _tokentype.types._export:
+    case _tokentype.types._import:
+      if (!this.options.allowImportExportEverywhere) {
+        if (!topLevel) this.raise(this.start, "'import' and 'export' may only appear at the top level");
+        if (!this.inModule) this.raise(this.start, "'import' and 'export' may appear only with 'sourceType: module'");
+      }
+      return starttype === _tokentype.types._import ? this.parseImport(node) : this.parseExport(node);
+
+    // If the statement does not start with a statement keyword or a
+    // brace, it's an ExpressionStatement or LabeledStatement. We
+    // simply start parsing an expression, and afterwards, if the
+    // next token is a colon and the expression was a simple
+    // Identifier node, we switch to interpreting it as a label.
+    default:
+      var maybeName = this.value,
+          expr = this.parseExpression();
+      if (starttype === _tokentype.types.name && expr.type === "Identifier" && this.eat(_tokentype.types.colon)) return this.parseLabeledStatement(node, maybeName, expr);else return this.parseExpressionStatement(node, expr);
+  }
+};
+
+pp.parseBreakContinueStatement = function (node, keyword) {
+  var isBreak = keyword == "break";
+  this.next();
+  if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.label = null;else if (this.type !== _tokentype.types.name) this.unexpected();else {
+    node.label = this.parseIdent();
+    this.semicolon();
+  }
+
+  // Verify that there is an actual destination to break or
+  // continue to.
+  for (var i = 0; i < this.labels.length; ++i) {
+    var lab = this.labels[i];
+    if (node.label == null || lab.name === node.label.name) {
+      if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
+      if (node.label && isBreak) break;
+    }
+  }
+  if (i === this.labels.length) this.raise(node.start, "Unsyntactic " + keyword);
+  return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+};
+
+pp.parseDebuggerStatement = function (node) {
+  this.next();
+  this.semicolon();
+  return this.finishNode(node, "DebuggerStatement");
+};
+
+pp.parseDoStatement = function (node) {
+  this.next();
+  this.labels.push(loopLabel);
+  node.body = this.parseStatement(false);
+  this.labels.pop();
+  this.expect(_tokentype.types._while);
+  node.test = this.parseParenExpression();
+  if (this.options.ecmaVersion >= 6) this.eat(_tokentype.types.semi);else this.semicolon();
+  return this.finishNode(node, "DoWhileStatement");
+};
+
+// Disambiguating between a `for` and a `for`/`in` or `for`/`of`
+// loop is non-trivial. Basically, we have to parse the init `var`
+// statement or expression, disallowing the `in` operator (see
+// the second parameter to `parseExpression`), and then check
+// whether the next token is `in` or `of`. When there is no init
+// part (semicolon immediately after the opening parenthesis), it
+// is a regular `for` loop.
+
+pp.parseForStatement = function (node) {
+  this.next();
+  this.labels.push(loopLabel);
+  this.expect(_tokentype.types.parenL);
+  if (this.type === _tokentype.types.semi) return this.parseFor(node, null);
+  if (this.type === _tokentype.types._var || this.type === _tokentype.types._let || this.type === _tokentype.types._const) {
+    var _init = this.startNode(),
+        varKind = this.type;
+    this.next();
+    this.parseVar(_init, true, varKind);
+    this.finishNode(_init, "VariableDeclaration");
+    if ((this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) && _init.declarations.length === 1 && !(varKind !== _tokentype.types._var && _init.declarations[0].init)) return this.parseForIn(node, _init);
+    return this.parseFor(node, _init);
+  }
+  var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 };
+  var init = this.parseExpression(true, refDestructuringErrors);
+  if (this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) {
+    this.checkPatternErrors(refDestructuringErrors, true);
+    this.toAssignable(init);
+    this.checkLVal(init);
+    return this.parseForIn(node, init);
+  } else {
+    this.checkExpressionErrors(refDestructuringErrors, true);
+  }
+  return this.parseFor(node, init);
+};
+
+pp.parseFunctionStatement = function (node) {
+  this.next();
+  return this.parseFunction(node, true);
+};
+
+pp.parseIfStatement = function (node) {
+  this.next();
+  node.test = this.parseParenExpression();
+  node.consequent = this.parseStatement(false);
+  node.alternate = this.eat(_tokentype.types._else) ? this.parseStatement(false) : null;
+  return this.finishNode(node, "IfStatement");
+};
+
+pp.parseReturnStatement = function (node) {
+  if (!this.inFunction && !this.options.allowReturnOutsideFunction) this.raise(this.start, "'return' outside of function");
+  this.next();
+
+  // In `return` (and `break`/`continue`), the keywords with
+  // optional arguments, we eagerly look for a semicolon or the
+  // possibility to insert one.
+
+  if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.argument = null;else {
+    node.argument = this.parseExpression();this.semicolon();
+  }
+  return this.finishNode(node, "ReturnStatement");
+};
+
+pp.parseSwitchStatement = function (node) {
+  this.next();
+  node.discriminant = this.parseParenExpression();
+  node.cases = [];
+  this.expect(_tokentype.types.braceL);
+  this.labels.push(switchLabel);
+
+  // Statements under must be grouped (by label) in SwitchCase
+  // nodes. `cur` is used to keep the node that we are currently
+  // adding statements to.
+
+  for (var cur, sawDefault = false; this.type != _tokentype.types.braceR;) {
+    if (this.type === _tokentype.types._case || this.type === _tokentype.types._default) {
+      var isCase = this.type === _tokentype.types._case;
+      if (cur) this.finishNode(cur, "SwitchCase");
+      node.cases.push(cur = this.startNode());
+      cur.consequent = [];
+      this.next();
+      if (isCase) {
+        cur.test = this.parseExpression();
+      } else {
+        if (sawDefault) this.raise(this.lastTokStart, "Multiple default clauses");
+        sawDefault = true;
+        cur.test = null;
+      }
+      this.expect(_tokentype.types.colon);
+    } else {
+      if (!cur) this.unexpected();
+      cur.consequent.push(this.parseStatement(true));
+    }
   }
-
-  exports.Token = Token;
-
-  // Acorn is organized as a tokenizer and a recursive-descent parser.
-  // The `tokenize` export provides an interface to the tokenizer.
-  // Because the tokenizer is optimized for being efficiently used by
-  // the Acorn parser itself, this interface is somewhat crude and not
-  // very modular. Performing another parse or call to `tokenize` will
-  // reset the internal state, and invalidate existing tokenizers.
-
-  exports.tokenize = function(inpt, opts) {
-    input = String(inpt); inputLen = input.length;
-    setOptions(opts);
-    initTokenState();
-    skipSpace();
-
-    function getToken() {
-      lastEnd = tokEnd;
-      readToken();
-      return new Token();
+  if (cur) this.finishNode(cur, "SwitchCase");
+  this.next(); // Closing brace
+  this.labels.pop();
+  return this.finishNode(node, "SwitchStatement");
+};
+
+pp.parseThrowStatement = function (node) {
+  this.next();
+  if (_whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) this.raise(this.lastTokEnd, "Illegal newline after throw");
+  node.argument = this.parseExpression();
+  this.semicolon();
+  return this.finishNode(node, "ThrowStatement");
+};
+
+// Reused empty array added for node fields that are always empty.
+
+var empty = [];
+
+pp.parseTryStatement = function (node) {
+  this.next();
+  node.block = this.parseBlock();
+  node.handler = null;
+  if (this.type === _tokentype.types._catch) {
+    var clause = this.startNode();
+    this.next();
+    this.expect(_tokentype.types.parenL);
+    clause.param = this.parseBindingAtom();
+    this.checkLVal(clause.param, true);
+    this.expect(_tokentype.types.parenR);
+    clause.body = this.parseBlock();
+    node.handler = this.finishNode(clause, "CatchClause");
+  }
+  node.finalizer = this.eat(_tokentype.types._finally) ? this.parseBlock() : null;
+  if (!node.handler && !node.finalizer) this.raise(node.start, "Missing catch or finally clause");
+  return this.finishNode(node, "TryStatement");
+};
+
+pp.parseVarStatement = function (node, kind) {
+  this.next();
+  this.parseVar(node, false, kind);
+  this.semicolon();
+  return this.finishNode(node, "VariableDeclaration");
+};
+
+pp.parseWhileStatement = function (node) {
+  this.next();
+  node.test = this.parseParenExpression();
+  this.labels.push(loopLabel);
+  node.body = this.parseStatement(false);
+  this.labels.pop();
+  return this.finishNode(node, "WhileStatement");
+};
+
+pp.parseWithStatement = function (node) {
+  if (this.strict) this.raise(this.start, "'with' in strict mode");
+  this.next();
+  node.object = this.parseParenExpression();
+  node.body = this.parseStatement(false);
+  return this.finishNode(node, "WithStatement");
+};
+
+pp.parseEmptyStatement = function (node) {
+  this.next();
+  return this.finishNode(node, "EmptyStatement");
+};
+
+pp.parseLabeledStatement = function (node, maybeName, expr) {
+  for (var i = 0; i < this.labels.length; ++i) {
+    if (this.labels[i].name === maybeName) this.raise(expr.start, "Label '" + maybeName + "' is already declared");
+  }var kind = this.type.isLoop ? "loop" : this.type === _tokentype.types._switch ? "switch" : null;
+  for (var i = this.labels.length - 1; i >= 0; i--) {
+    var label = this.labels[i];
+    if (label.statementStart == node.start) {
+      label.statementStart = this.start;
+      label.kind = kind;
+    } else break;
+  }
+  this.labels.push({ name: maybeName, kind: kind, statementStart: this.start });
+  node.body = this.parseStatement(true);
+  this.labels.pop();
+  node.label = expr;
+  return this.finishNode(node, "LabeledStatement");
+};
+
+pp.parseExpressionStatement = function (node, expr) {
+  node.expression = expr;
+  this.semicolon();
+  return this.finishNode(node, "ExpressionStatement");
+};
+
+// Parse a semicolon-enclosed block of statements, handling `"use
+// strict"` declarations when `allowStrict` is true (used for
+// function bodies).
+
+pp.parseBlock = function (allowStrict) {
+  var node = this.startNode(),
+      first = true,
+      oldStrict = undefined;
+  node.body = [];
+  this.expect(_tokentype.types.braceL);
+  while (!this.eat(_tokentype.types.braceR)) {
+    var stmt = this.parseStatement(true);
+    node.body.push(stmt);
+    if (first && allowStrict && this.isUseStrict(stmt)) {
+      oldStrict = this.strict;
+      this.setStrict(this.strict = true);
     }
-    getToken.jumpTo = function(pos, exprAllowed) {
-      tokPos = pos;
-      if (options.locations) {
-        tokCurLine = 1;
-        tokLineStart = lineBreak.lastIndex = 0;
-        var match;
-        while ((match = lineBreak.exec(input)) && match.index < pos) {
-          ++tokCurLine;
-          tokLineStart = match.index + match[0].length;
+    first = false;
+  }
+  if (oldStrict === false) this.setStrict(false);
+  return this.finishNode(node, "BlockStatement");
+};
+
+// Parse a regular `for` loop. The disambiguation code in
+// `parseStatement` will already have parsed the init statement or
+// expression.
+
+pp.parseFor = function (node, init) {
+  node.init = init;
+  this.expect(_tokentype.types.semi);
+  node.test = this.type === _tokentype.types.semi ? null : this.parseExpression();
+  this.expect(_tokentype.types.semi);
+  node.update = this.type === _tokentype.types.parenR ? null : this.parseExpression();
+  this.expect(_tokentype.types.parenR);
+  node.body = this.parseStatement(false);
+  this.labels.pop();
+  return this.finishNode(node, "ForStatement");
+};
+
+// Parse a `for`/`in` and `for`/`of` loop, which are almost
+// same from parser's perspective.
+
+pp.parseForIn = function (node, init) {
+  var type = this.type === _tokentype.types._in ? "ForInStatement" : "ForOfStatement";
+  this.next();
+  node.left = init;
+  node.right = this.parseExpression();
+  this.expect(_tokentype.types.parenR);
+  node.body = this.parseStatement(false);
+  this.labels.pop();
+  return this.finishNode(node, type);
+};
+
+// Parse a list of variable declarations.
+
+pp.parseVar = function (node, isFor, kind) {
+  node.declarations = [];
+  node.kind = kind.keyword;
+  for (;;) {
+    var decl = this.startNode();
+    this.parseVarId(decl);
+    if (this.eat(_tokentype.types.eq)) {
+      decl.init = this.parseMaybeAssign(isFor);
+    } else if (kind === _tokentype.types._const && !(this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of"))) {
+      this.unexpected();
+    } else if (decl.id.type != "Identifier" && !(isFor && (this.type === _tokentype.types._in || this.isContextual("of")))) {
+      this.raise(this.lastTokEnd, "Complex binding patterns require an initialization value");
+    } else {
+      decl.init = null;
+    }
+    node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
+    if (!this.eat(_tokentype.types.comma)) break;
+  }
+  return node;
+};
+
+pp.parseVarId = function (decl) {
+  decl.id = this.parseBindingAtom();
+  this.checkLVal(decl.id, true);
+};
+
+// Parse a function declaration or literal (depending on the
+// `isStatement` parameter).
+
+pp.parseFunction = function (node, isStatement, allowExpressionBody) {
+  this.initFunction(node);
+  if (this.options.ecmaVersion >= 6) node.generator = this.eat(_tokentype.types.star);
+  if (isStatement || this.type === _tokentype.types.name) node.id = this.parseIdent();
+  this.parseFunctionParams(node);
+  this.parseFunctionBody(node, allowExpressionBody);
+  return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
+};
+
+pp.parseFunctionParams = function (node) {
+  this.expect(_tokentype.types.parenL);
+  node.params = this.parseBindingList(_tokentype.types.parenR, false, false, true);
+};
+
+// Parse a class declaration or literal (depending on the
+// `isStatement` parameter).
+
+pp.parseClass = function (node, isStatement) {
+  this.next();
+  this.parseClassId(node, isStatement);
+  this.parseClassSuper(node);
+  var classBody = this.startNode();
+  var hadConstructor = false;
+  classBody.body = [];
+  this.expect(_tokentype.types.braceL);
+  while (!this.eat(_tokentype.types.braceR)) {
+    if (this.eat(_tokentype.types.semi)) continue;
+    var method = this.startNode();
+    var isGenerator = this.eat(_tokentype.types.star);
+    var isMaybeStatic = this.type === _tokentype.types.name && this.value === "static";
+    this.parsePropertyName(method);
+    method["static"] = isMaybeStatic && this.type !== _tokentype.types.parenL;
+    if (method["static"]) {
+      if (isGenerator) this.unexpected();
+      isGenerator = this.eat(_tokentype.types.star);
+      this.parsePropertyName(method);
+    }
+    method.kind = "method";
+    var isGetSet = false;
+    if (!method.computed) {
+      var key = method.key;
+
+      if (!isGenerator && key.type === "Identifier" && this.type !== _tokentype.types.parenL && (key.name === "get" || key.name === "set")) {
+        isGetSet = true;
+        method.kind = key.name;
+        key = this.parsePropertyName(method);
+      }
+      if (!method["static"] && (key.type === "Identifier" && key.name === "constructor" || key.type === "Literal" && key.value === "constructor")) {
+        if (hadConstructor) this.raise(key.start, "Duplicate constructor in the same class");
+        if (isGetSet) this.raise(key.start, "Constructor can't have get/set modifier");
+        if (isGenerator) this.raise(key.start, "Constructor can't be a generator");
+        method.kind = "constructor";
+        hadConstructor = true;
+      }
+    }
+    this.parseClassMethod(classBody, method, isGenerator);
+    if (isGetSet) {
+      var paramCount = method.kind === "get" ? 0 : 1;
+      if (method.value.params.length !== paramCount) {
+        var start = method.value.start;
+        if (method.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param");
+      }
+    }
+  }
+  node.body = this.finishNode(classBody, "ClassBody");
+  return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
+};
+
+pp.parseClassMethod = function (classBody, method, isGenerator) {
+  method.value = this.parseMethod(isGenerator);
+  classBody.body.push(this.finishNode(method, "MethodDefinition"));
+};
+
+pp.parseClassId = function (node, isStatement) {
+  node.id = this.type === _tokentype.types.name ? this.parseIdent() : isStatement ? this.unexpected() : null;
+};
+
+pp.parseClassSuper = function (node) {
+  node.superClass = this.eat(_tokentype.types._extends) ? this.parseExprSubscripts() : null;
+};
+
+// Parses module export declaration.
+
+pp.parseExport = function (node) {
+  this.next();
+  // export * from '...'
+  if (this.eat(_tokentype.types.star)) {
+    this.expectContextual("from");
+    node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected();
+    this.semicolon();
+    return this.finishNode(node, "ExportAllDeclaration");
+  }
+  if (this.eat(_tokentype.types._default)) {
+    // export default ...
+    var expr = this.parseMaybeAssign();
+    var needsSemi = true;
+    if (expr.type == "FunctionExpression" || expr.type == "ClassExpression") {
+      needsSemi = false;
+      if (expr.id) {
+        expr.type = expr.type == "FunctionExpression" ? "FunctionDeclaration" : "ClassDeclaration";
+      }
+    }
+    node.declaration = expr;
+    if (needsSemi) this.semicolon();
+    return this.finishNode(node, "ExportDefaultDeclaration");
+  }
+  // export var|const|let|function|class ...
+  if (this.shouldParseExportStatement()) {
+    node.declaration = this.parseStatement(true);
+    node.specifiers = [];
+    node.source = null;
+  } else {
+    // export { x, y as z } [from '...']
+    node.declaration = null;
+    node.specifiers = this.parseExportSpecifiers();
+    if (this.eatContextual("from")) {
+      node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected();
+    } else {
+      // check for keywords used as local names
+      for (var i = 0; i < node.specifiers.length; i++) {
+        if (this.keywords.test(node.specifiers[i].local.name) || this.reservedWords.test(node.specifiers[i].local.name)) {
+          this.unexpected(node.specifiers[i].local.start);
         }
       }
-      tokExprAllowed = !!exprAllowed;
-      skipSpace();
-    };
-    getToken.options = options;
-    return getToken;
-  };
-
-  // State is kept in (closure-)global variables. We already saw the
-  // `options`, `input`, and `inputLen` variables above.
-
-  // The current position of the tokenizer in the input.
-
-  var tokPos;
-
-  // The start and end offsets of the current token.
-
-  var tokStart, tokEnd;
-
-  // When `options.locations` is true, these hold objects
-  // containing the tokens start and end line/column pairs.
-
-  var tokStartLoc, tokEndLoc;
-
-  // The type and value of the current token. Token types are objects,
-  // named by variables against which they can be compared, and
-  // holding properties that describe them (indicating, for example,
-  // the precedence of an infix operator, and the original name of a
-  // keyword token). The kind of value that's held in `tokVal` depends
-  // on the type of the token. For literals, it is the literal value,
-  // for operators, the operator name, and so on.
-
-  var tokType, tokVal;
-
-  // Internal state for the tokenizer. To distinguish between division
-  // operators and regular expressions, it remembers whether the last
-  // token was one that is allowed to be followed by an expression. In
-  // some cases, notably after ')' or '}' tokens, the situation
-  // depends on the context before the matching opening bracket, so
-  // tokContext keeps a stack of information about current bracketed
-  // forms.
-
-  var tokContext, tokExprAllowed;
-
-  // When `options.locations` is true, these are used to keep
-  // track of the current line, and know when a new line has been
-  // entered.
-
-  var tokCurLine, tokLineStart;
-
-  // These store the position of the previous token, which is useful
-  // when finishing a node and assigning its `end` position.
-
-  var lastStart, lastEnd, lastEndLoc;
-
-  // This is the parser's state. `inFunction` is used to reject
-  // `return` statements outside of functions, `inGenerator` to
-  // reject `yield`s outside of generators, `labels` to verify
-  // that `break` and `continue` have somewhere to jump to, and
-  // `strict` indicates whether strict mode is on.
-
-  var inFunction, inGenerator, labels, strict;
-
-  // This counter is used for checking that arrow expressions did
-  // not contain nested parentheses in argument list.
-
-  var metParenL;
-
-  // This is used by the tokenizer to track the template strings it is
-  // inside, and count the amount of open braces seen inside them, to
-  // be able to switch back to a template token when the } to match ${
-  // is encountered. It will hold an array of integers.
-
-  var templates;
-
-  function initParserState() {
-    lastStart = lastEnd = tokPos;
-    if (options.locations) lastEndLoc = curPosition();
-    inFunction = inGenerator = strict = false;
-    labels = [];
-    skipSpace();
-    readToken();
+
+      node.source = null;
+    }
+    this.semicolon();
+  }
+  return this.finishNode(node, "ExportNamedDeclaration");
+};
+
+pp.shouldParseExportStatement = function () {
+  return this.type.keyword;
+};
+
+// Parses a comma-separated list of module exports.
+
+pp.parseExportSpecifiers = function () {
+  var nodes = [],
+      first = true;
+  // export { x, y as z } [from '...']
+  this.expect(_tokentype.types.braceL);
+  while (!this.eat(_tokentype.types.braceR)) {
+    if (!first) {
+      this.expect(_tokentype.types.comma);
+      if (this.afterTrailingComma(_tokentype.types.braceR)) break;
+    } else first = false;
+
+    var node = this.startNode();
+    node.local = this.parseIdent(this.type === _tokentype.types._default);
+    node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local;
+    nodes.push(this.finishNode(node, "ExportSpecifier"));
+  }
+  return nodes;
+};
+
+// Parses import declaration.
+
+pp.parseImport = function (node) {
+  this.next();
+  // import '...'
+  if (this.type === _tokentype.types.string) {
+    node.specifiers = empty;
+    node.source = this.parseExprAtom();
+  } else {
+    node.specifiers = this.parseImportSpecifiers();
+    this.expectContextual("from");
+    node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected();
+  }
+  this.semicolon();
+  return this.finishNode(node, "ImportDeclaration");
+};
+
+// Parses a comma-separated list of module imports.
+
+pp.parseImportSpecifiers = function () {
+  var nodes = [],
+      first = true;
+  if (this.type === _tokentype.types.name) {
+    // import defaultObj, { x, y as z } from '...'
+    var node = this.startNode();
+    node.local = this.parseIdent();
+    this.checkLVal(node.local, true);
+    nodes.push(this.finishNode(node, "ImportDefaultSpecifier"));
+    if (!this.eat(_tokentype.types.comma)) return nodes;
+  }
+  if (this.type === _tokentype.types.star) {
+    var node = this.startNode();
+    this.next();
+    this.expectContextual("as");
+    node.local = this.parseIdent();
+    this.checkLVal(node.local, true);
+    nodes.push(this.finishNode(node, "ImportNamespaceSpecifier"));
+    return nodes;
+  }
+  this.expect(_tokentype.types.braceL);
+  while (!this.eat(_tokentype.types.braceR)) {
+    if (!first) {
+      this.expect(_tokentype.types.comma);
+      if (this.afterTrailingComma(_tokentype.types.braceR)) break;
+    } else first = false;
+
+    var node = this.startNode();
+    node.imported = this.parseIdent(true);
+    node.local = this.eatContextual("as") ? this.parseIdent() : node.imported;
+    this.checkLVal(node.local, true);
+    nodes.push(this.finishNode(node, "ImportSpecifier"));
+  }
+  return nodes;
+};
+
+},{"./state":10,"./tokentype":14,"./whitespace":16}],12:[function(_dereq_,module,exports){
+// The algorithm used to determine whether a regexp can appear at a
+// given point in the program is loosely based on sweet.js' approach.
+// See https://github.com/mozilla/sweet.js/wiki/design
+
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _state = _dereq_("./state");
+
+var _tokentype = _dereq_("./tokentype");
+
+var _whitespace = _dereq_("./whitespace");
+
+var TokContext = function TokContext(token, isExpr, preserveSpace, override) {
+  _classCallCheck(this, TokContext);
+
+  this.token = token;
+  this.isExpr = !!isExpr;
+  this.preserveSpace = !!preserveSpace;
+  this.override = override;
+};
+
+exports.TokContext = TokContext;
+var types = {
+  b_stat: new TokContext("{", false),
+  b_expr: new TokContext("{", true),
+  b_tmpl: new TokContext("${", true),
+  p_stat: new TokContext("(", false),
+  p_expr: new TokContext("(", true),
+  q_tmpl: new TokContext("`", true, true, function (p) {
+    return p.readTmplToken();
+  }),
+  f_expr: new TokContext("function", true)
+};
+
+exports.types = types;
+var pp = _state.Parser.prototype;
+
+pp.initialContext = function () {
+  return [types.b_stat];
+};
+
+pp.braceIsBlock = function (prevType) {
+  if (prevType === _tokentype.types.colon) {
+    var _parent = this.curContext();
+    if (_parent === types.b_stat || _parent === types.b_expr) return !_parent.isExpr;
+  }
+  if (prevType === _tokentype.types._return) return _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start));
+  if (prevType === _tokentype.types._else || prevType === _tokentype.types.semi || prevType === _tokentype.types.eof || prevType === _tokentype.types.parenR) return true;
+  if (prevType == _tokentype.types.braceL) return this.curContext() === types.b_stat;
+  return !this.exprAllowed;
+};
+
+pp.updateContext = function (prevType) {
+  var update = undefined,
+      type = this.type;
+  if (type.keyword && prevType == _tokentype.types.dot) this.exprAllowed = false;else if (update = type.updateContext) update.call(this, prevType);else this.exprAllowed = type.beforeExpr;
+};
+
+// Token-specific context update code
+
+_tokentype.types.parenR.updateContext = _tokentype.types.braceR.updateContext = function () {
+  if (this.context.length == 1) {
+    this.exprAllowed = true;
+    return;
+  }
+  var out = this.context.pop();
+  if (out === types.b_stat && this.curContext() === types.f_expr) {
+    this.context.pop();
+    this.exprAllowed = false;
+  } else if (out === types.b_tmpl) {
+    this.exprAllowed = true;
+  } else {
+    this.exprAllowed = !out.isExpr;
+  }
+};
+
+_tokentype.types.braceL.updateContext = function (prevType) {
+  this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr);
+  this.exprAllowed = true;
+};
+
+_tokentype.types.dollarBraceL.updateContext = function () {
+  this.context.push(types.b_tmpl);
+  this.exprAllowed = true;
+};
+
+_tokentype.types.parenL.updateContext = function (prevType) {
+  var statementParens = prevType === _tokentype.types._if || prevType === _tokentype.types._for || prevType === _tokentype.types._with || prevType === _tokentype.types._while;
+  this.context.push(statementParens ? types.p_stat : types.p_expr);
+  this.exprAllowed = true;
+};
+
+_tokentype.types.incDec.updateContext = function () {
+  // tokExprAllowed stays unchanged
+};
+
+_tokentype.types._function.updateContext = function () {
+  if (this.curContext() !== types.b_stat) this.context.push(types.f_expr);
+  this.exprAllowed = false;
+};
+
+_tokentype.types.backQuote.updateContext = function () {
+  if (this.curContext() === types.q_tmpl) this.context.pop();else this.context.push(types.q_tmpl);
+  this.exprAllowed = false;
+};
+
+},{"./state":10,"./tokentype":14,"./whitespace":16}],13:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _identifier = _dereq_("./identifier");
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _locutil = _dereq_("./locutil");
+
+var _whitespace = _dereq_("./whitespace");
+
+// Object type used to represent tokens. Note that normally, tokens
+// simply exist as properties on the parser object. This is only
+// used for the onToken callback and the external tokenizer.
+
+var Token = function Token(p) {
+  _classCallCheck(this, Token);
+
+  this.type = p.type;
+  this.value = p.value;
+  this.start = p.start;
+  this.end = p.end;
+  if (p.options.locations) this.loc = new _locutil.SourceLocation(p, p.startLoc, p.endLoc);
+  if (p.options.ranges) this.range = [p.start, p.end];
+}
+
+// ## Tokenizer
+
+;
+
+exports.Token = Token;
+var pp = _state.Parser.prototype;
+
+// Are we running under Rhino?
+var isRhino = typeof Packages == "object" && Object.prototype.toString.call(Packages) == "[object JavaPackage]";
+
+// Move to the next token
+
+pp.next = function () {
+  if (this.options.onToken) this.options.onToken(new Token(this));
+
+  this.lastTokEnd = this.end;
+  this.lastTokStart = this.start;
+  this.lastTokEndLoc = this.endLoc;
+  this.lastTokStartLoc = this.startLoc;
+  this.nextToken();
+};
+
+pp.getToken = function () {
+  this.next();
+  return new Token(this);
+};
+
+// If we're in an ES6 environment, make parsers iterable
+if (typeof Symbol !== "undefined") pp[Symbol.iterator] = function () {
+  var self = this;
+  return { next: function next() {
+      var token = self.getToken();
+      return {
+        done: token.type === _tokentype.types.eof,
+        value: token
+      };
+    } };
+};
+
+// Toggle strict mode. Re-reads the next number or string to please
+// pedantic tests (`"use strict"; 010;` should fail).
+
+pp.setStrict = function (strict) {
+  this.strict = strict;
+  if (this.type !== _tokentype.types.num && this.type !== _tokentype.types.string) return;
+  this.pos = this.start;
+  if (this.options.locations) {
+    while (this.pos < this.lineStart) {
+      this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
+      --this.curLine;
+    }
+  }
+  this.nextToken();
+};
+
+pp.curContext = function () {
+  return this.context[this.context.length - 1];
+};
+
+// Read a single token, updating the parser object's token-related
+// properties.
+
+pp.nextToken = function () {
+  var curContext = this.curContext();
+  if (!curContext || !curContext.preserveSpace) this.skipSpace();
+
+  this.start = this.pos;
+  if (this.options.locations) this.startLoc = this.curPosition();
+  if (this.pos >= this.input.length) return this.finishToken(_tokentype.types.eof);
+
+  if (curContext.override) return curContext.override(this);else this.readToken(this.fullCharCodeAtPos());
+};
+
+pp.readToken = function (code) {
+  // Identifier or keyword. '\uXXXX' sequences are allowed in
+  // identifiers, so '\' also dispatches to that.
+  if (_identifier.isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */) return this.readWord();
+
+  return this.getTokenFromCode(code);
+};
+
+pp.fullCharCodeAtPos = function () {
+  var code = this.input.charCodeAt(this.pos);
+  if (code <= 0xd7ff || code >= 0xe000) return code;
+  var next = this.input.charCodeAt(this.pos + 1);
+  return (code << 10) + next - 0x35fdc00;
+};
+
+pp.skipBlockComment = function () {
+  var startLoc = this.options.onComment && this.curPosition();
+  var start = this.pos,
+      end = this.input.indexOf("*/", this.pos += 2);
+  if (end === -1) this.raise(this.pos - 2, "Unterminated comment");
+  this.pos = end + 2;
+  if (this.options.locations) {
+    _whitespace.lineBreakG.lastIndex = start;
+    var match = undefined;
+    while ((match = _whitespace.lineBreakG.exec(this.input)) && match.index < this.pos) {
+      ++this.curLine;
+      this.lineStart = match.index + match[0].length;
+    }
+  }
+  if (this.options.onComment) this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos, startLoc, this.curPosition());
+};
+
+pp.skipLineComment = function (startSkip) {
+  var start = this.pos;
+  var startLoc = this.options.onComment && this.curPosition();
+  var ch = this.input.charCodeAt(this.pos += startSkip);
+  while (this.pos < this.input.length && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
+    ++this.pos;
+    ch = this.input.charCodeAt(this.pos);
+  }
+  if (this.options.onComment) this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos, startLoc, this.curPosition());
+};
+
+// Called at the start of the parse and after every token. Skips
+// whitespace and comments, and.
+
+pp.skipSpace = function () {
+  loop: while (this.pos < this.input.length) {
+    var ch = this.input.charCodeAt(this.pos);
+    switch (ch) {
+      case 32:case 160:
+        // ' '
+        ++this.pos;
+        break;
+      case 13:
+        if (this.input.charCodeAt(this.pos + 1) === 10) {
+          ++this.pos;
+        }
+      case 10:case 8232:case 8233:
+        ++this.pos;
+        if (this.options.locations) {
+          ++this.curLine;
+          this.lineStart = this.pos;
+        }
+        break;
+      case 47:
+        // '/'
+        switch (this.input.charCodeAt(this.pos + 1)) {
+          case 42:
+            // '*'
+            this.skipBlockComment();
+            break;
+          case 47:
+            this.skipLineComment(2);
+            break;
+          default:
+            break loop;
+        }
+        break;
+      default:
+        if (ch > 8 && ch < 14 || ch >= 5760 && _whitespace.nonASCIIwhitespace.test(String.fromCharCode(ch))) {
+          ++this.pos;
+        } else {
+          break loop;
+        }
+    }
+  }
+};
+
+// Called at the end of every token. Sets `end`, `val`, and
+// maintains `context` and `exprAllowed`, and skips the space after
+// the token, so that the next one's `start` will point at the
+// right position.
+
+pp.finishToken = function (type, val) {
+  this.end = this.pos;
+  if (this.options.locations) this.endLoc = this.curPosition();
+  var prevType = this.type;
+  this.type = type;
+  this.value = val;
+
+  this.updateContext(prevType);
+};
+
+// ### Token reading
+
+// This is the function that is called to fetch the next token. It
+// is somewhat obscure, because it works in character codes rather
+// than characters, and because operator parsing has been inlined
+// into it.
+//
+// All in the name of speed.
+//
+pp.readToken_dot = function () {
+  var next = this.input.charCodeAt(this.pos + 1);
+  if (next >= 48 && next <= 57) return this.readNumber(true);
+  var next2 = this.input.charCodeAt(this.pos + 2);
+  if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) {
+    // 46 = dot '.'
+    this.pos += 3;
+    return this.finishToken(_tokentype.types.ellipsis);
+  } else {
+    ++this.pos;
+    return this.finishToken(_tokentype.types.dot);
+  }
+};
+
+pp.readToken_slash = function () {
+  // '/'
+  var next = this.input.charCodeAt(this.pos + 1);
+  if (this.exprAllowed) {
+    ++this.pos;return this.readRegexp();
+  }
+  if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+  return this.finishOp(_tokentype.types.slash, 1);
+};
+
+pp.readToken_mult_modulo = function (code) {
+  // '%*'
+  var next = this.input.charCodeAt(this.pos + 1);
+  if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+  return this.finishOp(code === 42 ? _tokentype.types.star : _tokentype.types.modulo, 1);
+};
+
+pp.readToken_pipe_amp = function (code) {
+  // '|&'
+  var next = this.input.charCodeAt(this.pos + 1);
+  if (next === code) return this.finishOp(code === 124 ? _tokentype.types.logicalOR : _tokentype.types.logicalAND, 2);
+  if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+  return this.finishOp(code === 124 ? _tokentype.types.bitwiseOR : _tokentype.types.bitwiseAND, 1);
+};
+
+pp.readToken_caret = function () {
+  // '^'
+  var next = this.input.charCodeAt(this.pos + 1);
+  if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+  return this.finishOp(_tokentype.types.bitwiseXOR, 1);
+};
+
+pp.readToken_plus_min = function (code) {
+  // '+-'
+  var next = this.input.charCodeAt(this.pos + 1);
+  if (next === code) {
+    if (next == 45 && this.input.charCodeAt(this.pos + 2) == 62 && _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.pos))) {
+      // A `-->` line comment
+      this.skipLineComment(3);
+      this.skipSpace();
+      return this.nextToken();
+    }
+    return this.finishOp(_tokentype.types.incDec, 2);
+  }
+  if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+  return this.finishOp(_tokentype.types.plusMin, 1);
+};
+
+pp.readToken_lt_gt = function (code) {
+  // '<>'
+  var next = this.input.charCodeAt(this.pos + 1);
+  var size = 1;
+  if (next === code) {
+    size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2;
+    if (this.input.charCodeAt(this.pos + size) === 61) return this.finishOp(_tokentype.types.assign, size + 1);
+    return this.finishOp(_tokentype.types.bitShift, size);
+  }
+  if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 && this.input.charCodeAt(this.pos + 3) == 45) {
+    if (this.inModule) this.unexpected();
+    // `<!--`, an XML-style comment that should be interpreted as a line comment
+    this.skipLineComment(4);
+    this.skipSpace();
+    return this.nextToken();
+  }
+  if (next === 61) size = this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2;
+  return this.finishOp(_tokentype.types.relational, size);
+};
+
+pp.readToken_eq_excl = function (code) {
+  // '=!'
+  var next = this.input.charCodeAt(this.pos + 1);
+  if (next === 61) return this.finishOp(_tokentype.types.equality, this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2);
+  if (code === 61 && next === 62 && this.options.ecmaVersion >= 6) {
+    // '=>'
+    this.pos += 2;
+    return this.finishToken(_tokentype.types.arrow);
   }
-
-  // This function is used to raise exceptions on parse errors. It
-  // takes an offset integer (into the current `input`) to indicate
-  // the location of the error, attaches the position to the end
-  // of the error message, and then raises a `SyntaxError` with that
-  // message.
-
-  function raise(pos, message) {
-    var loc = getLineInfo(input, pos);
-    message += " (" + loc.line + ":" + loc.column + ")";
-    var err = new SyntaxError(message);
-    err.pos = pos; err.loc = loc; err.raisedAt = tokPos;
-    throw err;
+  return this.finishOp(code === 61 ? _tokentype.types.eq : _tokentype.types.prefix, 1);
+};
+
+pp.getTokenFromCode = function (code) {
+  switch (code) {
+    // The interpretation of a dot depends on whether it is followed
+    // by a digit or another two dots.
+    case 46:
+      // '.'
+      return this.readToken_dot();
+
+    // Punctuation tokens.
+    case 40:
+      ++this.pos;return this.finishToken(_tokentype.types.parenL);
+    case 41:
+      ++this.pos;return this.finishToken(_tokentype.types.parenR);
+    case 59:
+      ++this.pos;return this.finishToken(_tokentype.types.semi);
+    case 44:
+      ++this.pos;return this.finishToken(_tokentype.types.comma);
+    case 91:
+      ++this.pos;return this.finishToken(_tokentype.types.bracketL);
+    case 93:
+      ++this.pos;return this.finishToken(_tokentype.types.bracketR);
+    case 123:
+      ++this.pos;return this.finishToken(_tokentype.types.braceL);
+    case 125:
+      ++this.pos;return this.finishToken(_tokentype.types.braceR);
+    case 58:
+      ++this.pos;return this.finishToken(_tokentype.types.colon);
+    case 63:
+      ++this.pos;return this.finishToken(_tokentype.types.question);
+
+    case 96:
+      // '`'
+      if (this.options.ecmaVersion < 6) break;
+      ++this.pos;
+      return this.finishToken(_tokentype.types.backQuote);
+
+    case 48:
+      // '0'
+      var next = this.input.charCodeAt(this.pos + 1);
+      if (next === 120 || next === 88) return this.readRadixNumber(16); // '0x', '0X' - hex number
+      if (this.options.ecmaVersion >= 6) {
+        if (next === 111 || next === 79) return this.readRadixNumber(8); // '0o', '0O' - octal number
+        if (next === 98 || next === 66) return this.readRadixNumber(2); // '0b', '0B' - binary number
+      }
+    // Anything else beginning with a digit is an integer, octal
+    // number, or float.
+    case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:
+      // 1-9
+      return this.readNumber(false);
+
+    // Quotes produce strings.
+    case 34:case 39:
+      // '"', "'"
+      return this.readString(code);
+
+    // Operators are parsed inline in tiny state machines. '=' (61) is
+    // often referred to. `finishOp` simply skips the amount of
+    // characters it is given as second argument, and returns a token
+    // of the type given by its first argument.
+
+    case 47:
+      // '/'
+      return this.readToken_slash();
+
+    case 37:case 42:
+      // '%*'
+      return this.readToken_mult_modulo(code);
+
+    case 124:case 38:
+      // '|&'
+      return this.readToken_pipe_amp(code);
+
+    case 94:
+      // '^'
+      return this.readToken_caret();
+
+    case 43:case 45:
+      // '+-'
+      return this.readToken_plus_min(code);
+
+    case 60:case 62:
+      // '<>'
+      return this.readToken_lt_gt(code);
+
+    case 61:case 33:
+      // '=!'
+      return this.readToken_eq_excl(code);
+
+    case 126:
+      // '~'
+      return this.finishOp(_tokentype.types.prefix, 1);
+  }
+
+  this.raise(this.pos, "Unexpected character '" + codePointToString(code) + "'");
+};
+
+pp.finishOp = function (type, size) {
+  var str = this.input.slice(this.pos, this.pos + size);
+  this.pos += size;
+  return this.finishToken(type, str);
+};
+
+// Parse a regular expression. Some context-awareness is necessary,
+// since a '/' inside a '[]' set does not end the expression.
+
+function tryCreateRegexp(src, flags, throwErrorAt, parser) {
+  try {
+    return new RegExp(src, flags);
+  } catch (e) {
+    if (throwErrorAt !== undefined) {
+      if (e instanceof SyntaxError) parser.raise(throwErrorAt, "Error parsing regular expression: " + e.message);
+      throw e;
+    }
+  }
+}
+
+var regexpUnicodeSupport = !!tryCreateRegexp("￿", "u");
+
+pp.readRegexp = function () {
+  var _this = this;
+
+  var escaped = undefined,
+      inClass = undefined,
+      start = this.pos;
+  for (;;) {
+    if (this.pos >= this.input.length) this.raise(start, "Unterminated regular expression");
+    var ch = this.input.charAt(this.pos);
+    if (_whitespace.lineBreak.test(ch)) this.raise(start, "Unterminated regular expression");
+    if (!escaped) {
+      if (ch === "[") inClass = true;else if (ch === "]" && inClass) inClass = false;else if (ch === "/" && !inClass) break;
+      escaped = ch === "\\";
+    } else escaped = false;
+    ++this.pos;
+  }
+  var content = this.input.slice(start, this.pos);
+  ++this.pos;
+  // Need to use `readWord1` because '\uXXXX' sequences are allowed
+  // here (don't ask).
+  var mods = this.readWord1();
+  var tmp = content;
+  if (mods) {
+    var validFlags = /^[gmsiy]*$/;
+    if (this.options.ecmaVersion >= 6) validFlags = /^[gmsiyu]*$/;
+    if (!validFlags.test(mods)) this.raise(start, "Invalid regular expression flag");
+    if (mods.indexOf('u') >= 0 && !regexpUnicodeSupport) {
+      // Replace each astral symbol and every Unicode escape sequence that
+      // possibly represents an astral symbol or a paired surrogate with a
+      // single ASCII symbol to avoid throwing on regular expressions that
+      // are only valid in combination with the `/u` flag.
+      // Note: replacing with the ASCII symbol `x` might cause false
+      // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a
+      // perfectly valid pattern that is equivalent to `[a-b]`, but it would
+      // be replaced by `[x-b]` which throws an error.
+      tmp = tmp.replace(/\\u\{([0-9a-fA-F]+)\}/g, function (_match, code, offset) {
+        code = Number("0x" + code);
+        if (code > 0x10FFFF) _this.raise(start + offset + 3, "Code point out of bounds");
+        return "x";
+      });
+      tmp = tmp.replace(/\\u([a-fA-F0-9]{4})|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x");
+    }
+  }
+  // Detect invalid regular expressions.
+  var value = null;
+  // Rhino's regular expression parser is flaky and throws uncatchable exceptions,
+  // so don't do detection if we are running under Rhino
+  if (!isRhino) {
+    tryCreateRegexp(tmp, undefined, start, this);
+    // Get a regular expression object for this pattern-flag pair, or `null` in
+    // case the current environment doesn't support the flags it uses.
+    value = tryCreateRegexp(content, mods);
+  }
+  return this.finishToken(_tokentype.types.regexp, { pattern: content, flags: mods, value: value });
+};
+
+// Read an integer in the given radix. Return null if zero digits
+// were read, the integer value otherwise. When `len` is given, this
+// will return `null` unless the integer has exactly `len` digits.
+
+pp.readInt = function (radix, len) {
+  var start = this.pos,
+      total = 0;
+  for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
+    var code = this.input.charCodeAt(this.pos),
+        val = undefined;
+    if (code >= 97) val = code - 97 + 10; // a
+    else if (code >= 65) val = code - 65 + 10; // A
+      else if (code >= 48 && code <= 57) val = code - 48; // 0-9
+        else val = Infinity;
+    if (val >= radix) break;
+    ++this.pos;
+    total = total * radix + val;
+  }
+  if (this.pos === start || len != null && this.pos - start !== len) return null;
+
+  return total;
+};
+
+pp.readRadixNumber = function (radix) {
+  this.pos += 2; // 0x
+  var val = this.readInt(radix);
+  if (val == null) this.raise(this.start + 2, "Expected number in radix " + radix);
+  if (_identifier.isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number");
+  return this.finishToken(_tokentype.types.num, val);
+};
+
+// Read an integer, octal integer, or floating-point number.
+
+pp.readNumber = function (startsWithDot) {
+  var start = this.pos,
+      isFloat = false,
+      octal = this.input.charCodeAt(this.pos) === 48;
+  if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number");
+  var next = this.input.charCodeAt(this.pos);
+  if (next === 46) {
+    // '.'
+    ++this.pos;
+    this.readInt(10);
+    isFloat = true;
+    next = this.input.charCodeAt(this.pos);
+  }
+  if (next === 69 || next === 101) {
+    // 'eE'
+    next = this.input.charCodeAt(++this.pos);
+    if (next === 43 || next === 45) ++this.pos; // '+-'
+    if (this.readInt(10) === null) this.raise(start, "Invalid number");
+    isFloat = true;
+  }
+  if (_identifier.isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number");
+
+  var str = this.input.slice(start, this.pos),
+      val = undefined;
+  if (isFloat) val = parseFloat(str);else if (!octal || str.length === 1) val = parseInt(str, 10);else if (/[89]/.test(str) || this.strict) this.raise(start, "Invalid number");else val = parseInt(str, 8);
+  return this.finishToken(_tokentype.types.num, val);
+};
+
+// Read a string value, interpreting backslash-escapes.
+
+pp.readCodePoint = function () {
+  var ch = this.input.charCodeAt(this.pos),
+      code = undefined;
+
+  if (ch === 123) {
+    if (this.options.ecmaVersion < 6) this.unexpected();
+    var codePos = ++this.pos;
+    code = this.readHexChar(this.input.indexOf('}', this.pos) - this.pos);
+    ++this.pos;
+    if (code > 0x10FFFF) this.raise(codePos, "Code point out of bounds");
+  } else {
+    code = this.readHexChar(4);
   }
-
-  // Reused empty array added for node fields that are always empty.
-
-  var empty = [];
-
-  // ## Token types
-
-  // The assignment of fine-grained, information-carrying type objects
-  // allows the tokenizer to store the information it has about a
-  // token in a way that is very cheap for the parser to look up.
-
-  // All token type variables start with an underscore, to make them
-  // easy to recognize.
-
-  // These are the general types. The `type` property is only used to
-  // make them recognizeable when debugging.
-
-  var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
-  var _name = {type: "name"}, _eof = {type: "eof"};
-
-  // Keyword tokens. The `keyword` property (also used in keyword-like
-  // operators) indicates that the token originated from an
-  // identifier-like word, which is used when parsing property names.
-  //
-  // The `beforeExpr` property is used to disambiguate between regular
-  // expressions and divisions. It is set on all token types that can
-  // be followed by an expression (thus, a slash after them would be a
-  // regular expression).
-  //
-  // `isLoop` marks a keyword as starting a loop, which is important
-  // to know when parsing a label, in order to allow or disallow
-  // continue jumps to that label.
-
-  var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
-  var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
-  var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
-  var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
-  var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
-  var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
-  var _let = {keyword: "let"}, _const = {keyword: "const"};
-  var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
-  var _this = {keyword: "this"};
-  var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true};
-  var _export = {keyword: "export"}, _import = {keyword: "import"};
-  var _yield = {keyword: "yield", beforeExpr: true};
-
-  // The keywords that denote values.
-
-  var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
-  var _false = {keyword: "false", atomValue: false};
-
-  // Some keywords are treated as regular operators. `in` sometimes
-  // (when parsing `for`) needs to be tested against specifically, so
-  // we assign a variable name to it for quick comparing.
-
-  var _in = {keyword: "in", binop: 7, beforeExpr: true};
-
-  // Map keyword names to token types.
-
-  var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
-                      "continue": _continue, "debugger": _debugger, "default": _default,
-                      "do": _do, "else": _else, "finally": _finally, "for": _for,
-                      "function": _function, "if": _if, "return": _return, "switch": _switch,
-                      "throw": _throw, "try": _try, "var": _var, "let": _let, "const": _const,
-                      "while": _while, "with": _with,
-                      "null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
-                      "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
-                      "typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
-                      "void": {keyword: "void", prefix: true, beforeExpr: true},
-                      "delete": {keyword: "delete", prefix: true, beforeExpr: true},
-                      "class": _class, "extends": _extends,
-                      "export": _export, "import": _import, "yield": _yield};
-
-  // Punctuation token types. Again, the `type` property is purely for debugging.
-
-  var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
-  var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
-  var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
-  var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
-  var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}, _templateContinued = {type: "templateContinued"};
-  var _ellipsis = {type: "...", prefix: true, beforeExpr: true};
+  return code;
+};
+
+function codePointToString(code) {
+  // UTF-16 Decoding
+  if (code <= 0xFFFF) return String.fromCharCode(code);
+  code -= 0x10000;
+  return String.fromCharCode((code >> 10) + 0xD800, (code & 1023) + 0xDC00);
+}
+
+pp.readString = function (quote) {
+  var out = "",
+      chunkStart = ++this.pos;
+  for (;;) {
+    if (this.pos >= this.input.length) this.raise(this.start, "Unterminated string constant");
+    var ch = this.input.charCodeAt(this.pos);
+    if (ch === quote) break;
+    if (ch === 92) {
+      // '\'
+      out += this.input.slice(chunkStart, this.pos);
+      out += this.readEscapedChar(false);
+      chunkStart = this.pos;
+    } else {
+      if (_whitespace.isNewLine(ch)) this.raise(this.start, "Unterminated string constant");
+      ++this.pos;
+    }
+  }
+  out += this.input.slice(chunkStart, this.pos++);
+  return this.finishToken(_tokentype.types.string, out);
+};
+
+// Reads template string tokens.
+
+pp.readTmplToken = function () {
+  var out = "",
+      chunkStart = this.pos;
+  for (;;) {
+    if (this.pos >= this.input.length) this.raise(this.start, "Unterminated template");
+    var ch = this.input.charCodeAt(this.pos);
+    if (ch === 96 || ch === 36 && this.input.charCodeAt(this.pos + 1) === 123) {
+      // '`', '${'
+      if (this.pos === this.start && this.type === _tokentype.types.template) {
+        if (ch === 36) {
+          this.pos += 2;
+          return this.finishToken(_tokentype.types.dollarBraceL);
+        } else {
+          ++this.pos;
+          return this.finishToken(_tokentype.types.backQuote);
+        }
+      }
+      out += this.input.slice(chunkStart, this.pos);
+      return this.finishToken(_tokentype.types.template, out);
+    }
+    if (ch === 92) {
+      // '\'
+      out += this.input.slice(chunkStart, this.pos);
+      out += this.readEscapedChar(true);
+      chunkStart = this.pos;
+    } else if (_whitespace.isNewLine(ch)) {
+      out += this.input.slice(chunkStart, this.pos);
+      ++this.pos;
+      switch (ch) {
+        case 13:
+          if (this.input.charCodeAt(this.pos) === 10) ++this.pos;
+        case 10:
+          out += "\n";
+          break;
+        default:
+          out += String.fromCharCode(ch);
+          break;
+      }
+      if (this.options.locations) {
+        ++this.curLine;
+        this.lineStart = this.pos;
+      }
+      chunkStart = this.pos;
+    } else {
+      ++this.pos;
+    }
+  }
+};
+
+// Used to read escaped characters
+
+pp.readEscapedChar = function (inTemplate) {
+  var ch = this.input.charCodeAt(++this.pos);
+  ++this.pos;
+  switch (ch) {
+    case 110:
+      return "\n"; // 'n' -> '\n'
+    case 114:
+      return "\r"; // 'r' -> '\r'
+    case 120:
+      return String.fromCharCode(this.readHexChar(2)); // 'x'
+    case 117:
+      return codePointToString(this.readCodePoint()); // 'u'
+    case 116:
+      return "\t"; // 't' -> '\t'
+    case 98:
+      return "\b"; // 'b' -> '\b'
+    case 118:
+      return "\u000b"; // 'v' -> '\u000b'
+    case 102:
+      return "\f"; // 'f' -> '\f'