merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Tue, 05 Sep 2017 23:55:39 +0200
changeset 379017 f64e2b4dcf5e
parent 378964 978d2539a8d1 (current diff)
parent 379016 5ca9c4d3658c (diff)
child 379018 b235fb79d6e0
child 379066 62e6b3fa08ae
child 379124 4ae83cf0308a
push id32446
push userarchaeopteryx@coole-files.de
push date2017-09-05 21:56 +0000
treeherdermozilla-central@f64e2b4dcf5e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.0a1
first release with
nightly linux32
f64e2b4dcf5e / 57.0a1 / 20170905220108 / files
nightly linux64
f64e2b4dcf5e / 57.0a1 / 20170905220108 / files
nightly mac
f64e2b4dcf5e / 57.0a1 / 20170905220108 / files
nightly win32
f64e2b4dcf5e / 57.0a1 / 20170905220108 / files
nightly win64
f64e2b4dcf5e / 57.0a1 / 20170905220108 / 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 mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: 17ViEoPyjPa
browser/base/content/browser.xul
layout/tables/crashtests/crashtests.list
third_party/rust/bindgen/.github/ISSUE_TEMPLATE.md
third_party/rust/bindgen/.travis.yml
third_party/rust/bindgen/CONTRIBUTING.md
third_party/rust/bindgen/LICENSE
third_party/rust/bindgen/README.md
third_party/rust/bindgen/appveyor.yml
third_party/rust/bindgen/book/book.toml
third_party/rust/bindgen/book/src/SUMMARY.md
third_party/rust/bindgen/book/src/blacklisting.md
third_party/rust/bindgen/book/src/chapter_1.md
third_party/rust/bindgen/book/src/command-line-usage.md
third_party/rust/bindgen/book/src/cpp.md
third_party/rust/bindgen/book/src/customizing-generated-bindings.md
third_party/rust/bindgen/book/src/introduction.md
third_party/rust/bindgen/book/src/library-usage.md
third_party/rust/bindgen/book/src/nocopy.md
third_party/rust/bindgen/book/src/opaque.md
third_party/rust/bindgen/book/src/replacing-types.md
third_party/rust/bindgen/book/src/requirements.md
third_party/rust/bindgen/book/src/tutorial-0.md
third_party/rust/bindgen/book/src/tutorial-1.md
third_party/rust/bindgen/book/src/tutorial-2.md
third_party/rust/bindgen/book/src/tutorial-3.md
third_party/rust/bindgen/book/src/tutorial-4.md
third_party/rust/bindgen/book/src/tutorial-5.md
third_party/rust/bindgen/book/src/tutorial-6.md
third_party/rust/bindgen/book/src/using-unions.md
third_party/rust/bindgen/book/src/whitelisting.md
third_party/rust/bindgen/ci/assert-docs.sh
third_party/rust/bindgen/ci/assert-no-diff.bat
third_party/rust/bindgen/ci/assert-no-diff.sh
third_party/rust/bindgen/ci/assert-rustfmt.sh
third_party/rust/bindgen/ci/before_install.sh
third_party/rust/bindgen/ci/deploy-book.sh
third_party/rust/bindgen/ci/no-includes.sh
third_party/rust/bindgen/ci/test-book.sh
third_party/rust/bindgen/ci/test.bat
third_party/rust/bindgen/ci/test.sh
third_party/rust/bindgen/example-graphviz-ir.png
third_party/rust/bindgen/rustfmt.toml
third_party/rust/bindgen/src/uses.rs
third_party/rust/clap/appveyor.yml
third_party/rust/thread-id/license
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -432,19 +432,19 @@
            role="group"
            type="arrow"
            hidden="true"
            flip="slide"
            photon="true"
            position="bottomcenter topright"
            tabspecific="true"
            noautofocus="true"
-           copyURL-title="&copyURLCmd.label;"
+           copyURL-title="&copyLinkCmd.label;"
            emailLink-title="&emailPageCmd.label;"
-           sendToDevice-title="&sendToDevice.label3;"
+           sendToDevice-title="&sendTabToDevice.label;"
            sendToDevice-notReadyTitle="&sendToDevice.syncNotReady.label;">
       <photonpanelmultiview id="pageActionPanelMultiView"
                             mainViewId="pageActionPanelMainView"
                             viewCacheId="appMenu-viewCache">
         <panelview id="pageActionPanelMainView"
                    context="pageActionPanelContextMenu"
                    oncontextmenu="BrowserPageActions.onContextMenu(event);"
                    class="PanelUI-subView">
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -2266,17 +2266,18 @@
         "goHome", "homePage", "gotoIndex", "currentURI", "documentURI",
         "preferences", "imageDocument", "isRemoteBrowser", "messageManager",
         "getTabBrowser", "finder", "fastFind", "sessionHistory", "contentTitle",
         "characterSet", "fullZoom", "textZoom", "webProgress",
         "addProgressListener", "removeProgressListener", "audioPlaybackStarted",
         "audioPlaybackStopped", "pauseMedia", "stopMedia",
         "resumeMedia", "mute", "unmute", "blockedPopups", "lastURI",
         "purgeSessionHistory", "stopScroll", "startScroll",
-        "userTypedValue", "userTypedClear", "mediaBlocked"
+        "userTypedValue", "userTypedClear", "mediaBlocked",
+        "didStartLoadSinceLastUserTyping"
       ]</field>
 
       <method name="_createLazyBrowser">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             let browser = aTab.linkedBrowser;
 
@@ -2294,16 +2295,19 @@
                   getter = () => SessionStore.getLazyTabValue(aTab, "title");
                   break;
                 case "currentURI":
                   getter = () => {
                     let url = SessionStore.getLazyTabValue(aTab, "url");
                     return Services.io.newURI(url);
                   };
                   break;
+                case "didStartLoadSinceLastUserTyping":
+                  getter = () => () => false;
+                  break;
                 case "fullZoom":
                 case "textZoom":
                   getter = () => 1;
                   break;
                 case "getTabBrowser":
                   getter = () => () => this;
                   break;
                 case "isRemoteBrowser":
@@ -2436,16 +2440,66 @@
 
             var evt = new CustomEvent("TabBrowserInserted",
               { bubbles: true, detail: { insertedOnTabCreation: aInsertedOnTabCreation } });
             aTab.dispatchEvent(evt);
           ]]>
         </body>
       </method>
 
+      <method name="discardBrowser">
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            "use strict";
+
+            let tab = this.getTabForBrowser(aBrowser);
+
+            if (!tab ||
+                tab.selected ||
+                tab.closing ||
+                this._windowIsClosing ||
+                !aBrowser.isConnected ||
+                !aBrowser.isRemoteBrowser ||
+                aBrowser.frameLoader.tabParent.hasBeforeUnload) {
+              return;
+            }
+
+            // Set browser parameters for when browser is restored.  Also remove
+            // listeners and set up lazy restore data in SessionStore. This must
+            // be done before aBrowser is destroyed and removed from the document.
+            tab._browserParams = { uriIsAboutBlank: aBrowser.currentURI.spec == "about:blank",
+                                   remoteType: aBrowser.remoteType,
+                                   usingPreloadedContent: false };
+
+            SessionStore.resetBrowserToLazyState(tab);
+
+            this._outerWindowIDBrowserMap.delete(aBrowser.outerWindowID);
+
+            // Remove the tab's filter and progress listener.
+            let filter = this._tabFilters.get(tab);
+            let listener = this._tabListeners.get(tab);
+            aBrowser.webProgress.removeProgressListener(filter);
+            filter.removeProgressListener(listener);
+            listener.destroy();
+
+            this._tabListeners.delete(tab);
+            this._tabFilters.delete(tab);
+
+            aBrowser.destroy();
+
+            let notificationbox = this.getNotificationBox(aBrowser);
+            this.mPanelContainer.removeChild(notificationbox);
+            tab.removeAttribute("linkedpanel");
+
+            this._createLazyBrowser(tab);
+          ]]>
+        </body>
+      </method>
+
       <method name="addTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aOwner"/>
         <parameter name="aAllowThirdPartyFixup"/>
         <body>
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -365,16 +365,20 @@ this.SessionStore = {
   getSessionHistory(tab, updatedCallback) {
     return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
   },
 
   undoCloseById(aClosedId) {
     return SessionStoreInternal.undoCloseById(aClosedId);
   },
 
+  resetBrowserToLazyState(tab) {
+    return SessionStoreInternal.resetBrowserToLazyState(tab);
+  },
+
   /**
    * Determines whether the passed version number is compatible with
    * the current version number of the SessionStore.
    *
    * @param version The format and version of the file, as an array, e.g.
    * ["sessionrestore", 1]
    */
   isFormatVersionCompatible(version) {
@@ -1893,29 +1897,17 @@ var SessionStoreInternal = {
    * @param aWindow
    *        Window reference
    * @param aTab
    *        Tab reference
    * @param aNoNotification
    *        bool Do not save state if we're updating an existing tab
    */
   onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
-    let browser = aTab.linkedBrowser;
-    browser.removeEventListener("SwapDocShells", this);
-    browser.removeEventListener("oop-browser-crashed", this);
-
-    // If this tab was in the middle of restoring or still needs to be restored,
-    // we need to reset that state. If the tab was restoring, we will attempt to
-    // restore the next tab.
-    let previousState = browser.__SS_restoreState;
-    if (previousState) {
-      this._resetTabRestoringState(aTab);
-      if (previousState == TAB_STATE_RESTORING)
-        this.restoreNextTab();
-    }
+    this.cleanUpRemovedBrowser(aTab);
 
     if (!aNoNotification) {
       this.saveStateDelayed(aWindow);
     }
   },
 
   /**
    * When a tab closes, collect its properties
@@ -1975,16 +1967,67 @@ var SessionStoreInternal = {
     }
 
     // Remember the closed tab to properly handle any last updates included in
     // the final "update" message sent by the frame script's unload handler.
     this._closedTabs.set(permanentKey, {closedTabs, tabData});
   },
 
   /**
+   * Remove listeners which were added when browser was inserted and reset restoring state.
+   * Also re-instate lazy data and basically revert tab to its lazy browser state.
+   * @param aTab
+   *        Tab reference
+   */
+  resetBrowserToLazyState(aTab) {
+    let browser = aTab.linkedBrowser;
+    // Browser is already lazy so don't do anything.
+    if (!browser.isConnected) {
+      return;
+    }
+
+    this.cleanUpRemovedBrowser(aTab);
+
+    aTab.setAttribute("pending", "true");
+
+    this._lastKnownFrameLoader.delete(browser.permanentKey);
+    this._crashedBrowsers.delete(browser.permanentKey);
+    aTab.removeAttribute("crashed");
+
+    aTab.__SS_lazyData = {
+      url: browser.currentURI.spec,
+      title: aTab.label,
+      userTypedValue: browser.userTypedValue || "",
+      userTypedClear: browser.userTypedClear || 0
+    }
+  },
+
+  /**
+   * When a tab is removed or suspended, remove listeners and reset restoring state.
+   * @param aBrowser
+   *        Browser reference
+   */
+  cleanUpRemovedBrowser(aTab) {
+    let browser = aTab.linkedBrowser;
+
+    browser.removeEventListener("SwapDocShells", this);
+    browser.removeEventListener("oop-browser-crashed", this);
+
+    // If this tab was in the middle of restoring or still needs to be restored,
+    // we need to reset that state. If the tab was restoring, we will attempt to
+    // restore the next tab.
+    let previousState = browser.__SS_restoreState;
+    if (previousState) {
+      this._resetTabRestoringState(aTab);
+      if (previousState == TAB_STATE_RESTORING)
+        this.restoreNextTab();
+    }
+  },
+
+  /**
    * Insert a given |tabData| object into the list of |closedTabs|. We will
    * determine the right insertion point based on the .closedAt properties of
    * all tabs already in the list. The list will be truncated to contain a
    * maximum of |this._max_tabs_undo| entries.
    *
    * @param closedTabs (array)
    *        The list of closed tabs for a window.
    * @param tabData (object)
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -50,16 +50,17 @@ support-files =
   browser_739531_frame.html
   browser_911547_sample.html
   browser_911547_sample.html^headers^
   restore_redirect_http.html
   restore_redirect_http.html^headers^
   restore_redirect_js.html
   restore_redirect_target.html
   browser_1234021_page.html
+  browser_1284886_suspend_tab.html
 
 #NB: the following are disabled
 #  browser_464620_a.html
 #  browser_464620_b.html
 #  browser_464620_xd.html
 
 
 #disabled-for-intermittent-failures--bug-766044, browser_459906_empty.html
@@ -223,16 +224,18 @@ skip-if = true
 skip-if = true
 
 # Disabled on OS X:
 [browser_625016.js]
 skip-if = os == "mac" || (os == "linux" && debug) # linux, Bug 1348583
 
 [browser_906076_lazy_tabs.js]
 [browser_911547.js]
+[browser_1284886_suspend_tab.js]
+skip-if = !e10s
 [browser_send_async_message_oom.js]
 [browser_multiple_navigateAndRestore.js]
 run-if = e10s
 [browser_async_window_flushing.js]
 [browser_forget_async_closings.js]
 [browser_newtab_userTypedValue.js]
 [browser_parentProcessRestoreHash.js]
 run-if = e10s
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_1284886_suspend_tab.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+  <script>
+    window.onbeforeunload = function() {
+      return true;
+    }
+  </script>
+</head>
+<body>
+TEST PAGE
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_1284886_suspend_tab.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function test() {
+  let url = "about:robots";
+  let tab0 = gBrowser.tabs[0];
+  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+  gBrowser.discardBrowser(tab0.linkedBrowser);
+  ok(!tab0.linkedPanel, "tab0 is suspended");
+
+  await BrowserTestUtils.switchTab(gBrowser, tab0);
+
+  // Test that active tab is not able to be suspended.
+  gBrowser.discardBrowser(tab0.linkedBrowser);
+  ok(tab0.linkedPanel, "active tab is not able to be suspended");
+
+  // Test that tab that is closing is not able to be suspended.
+  gBrowser._beginRemoveTab(tab1);
+  gBrowser.discardBrowser(tab1.linkedBrowser);
+
+  ok(tab1.linkedPanel, "cannot suspend a tab that is closing");
+
+  gBrowser._endRemoveTab(tab1);
+
+  // Test that tab with beforeunload handler is not able to be suspended.
+  url = "http://example.com/browser/browser/components/sessionstore/test/browser_1284886_suspend_tab.html";
+
+  tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+  await BrowserTestUtils.switchTab(gBrowser, tab0);
+
+  gBrowser.discardBrowser(tab1.linkedBrowser);
+  ok(tab1.linkedPanel, "cannot suspend a tab with beforeunload handler");
+
+  await promiseRemoveTab(tab1);
+
+  // Test that remote tab is not able to be suspended.
+  url = "about:robots";
+  tab1 = BrowserTestUtils.addTab(gBrowser, url, { forceNotRemote: true });
+  await promiseBrowserLoaded(tab1.linkedBrowser, true, url);
+  await BrowserTestUtils.switchTab(gBrowser, tab1);
+  await BrowserTestUtils.switchTab(gBrowser, tab0);
+
+  gBrowser.discardBrowser(tab1.linkedBrowser);
+  ok(tab1.linkedPanel, "cannot suspend a remote tab");
+
+  promiseRemoveTab(tab1);
+});
+
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -548,17 +548,17 @@ These should match what Safari and other
 <!ENTITY viewBGImageCmd.accesskey     "w">
 <!ENTITY setDesktopBackgroundCmd.label      "Set As Desktop Background…">
 <!ENTITY setDesktopBackgroundCmd.accesskey  "S">
 <!ENTITY bookmarkPageCmd2.label       "Bookmark This Page">
 <!ENTITY bookmarkThisLinkCmd.label      "Bookmark This Link">
 <!ENTITY bookmarkThisLinkCmd.accesskey  "L">
 <!ENTITY bookmarkThisFrameCmd.label      "Bookmark This Frame">
 <!ENTITY bookmarkThisFrameCmd.accesskey  "m">
-<!ENTITY copyURLCmd.label             "Copy URL">
+<!ENTITY copyLinkCmd.label             "Copy Link">
 <!ENTITY copyURLFeedback.label        "Copied!">
 <!ENTITY emailPageCmd.label           "Email Link…">
 <!ENTITY emailPageCmd.accesskey       "E">
 <!ENTITY savePageCmd.label            "Save Page As…">
 <!ENTITY savePageCmd.accesskey        "A">
 <!-- alternate for content area context menu -->
 <!ENTITY savePageCmd.accesskey2       "P">
 <!ENTITY savePageCmd.commandkey       "s">
@@ -969,17 +969,17 @@ you can use these alternative items. Oth
 <!ENTITY updateRestart.cancelButton.label "Not Now">
 <!ENTITY updateRestart.cancelButton.accesskey "N">
 <!ENTITY updateRestart.panelUI.label2 "Restart to update &brandShorterName;">
 
 <!ENTITY pageActionButton.tooltip "Page actions">
 <!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
 <!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
 
-<!ENTITY sendToDevice.label3 "Send Page to Device">
+<!ENTITY sendTabToDevice.label "Send Tab to Device">
 <!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">
 
 <!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more">
 
 <!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to
      display a tooltip for accessibility indicator in toolbar/tabbar. It is also
      used as a textual label for the indicator used by assistive technology
      users. -->
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
   doc_inspector_highlighter-geometry_02.html
   doc_inspector_highlighter_cssshapes.html
   doc_inspector_highlighter_csstransform.html
   doc_inspector_highlighter_dom.html
   doc_inspector_highlighter_inline.html
   doc_inspector_highlighter.html
   doc_inspector_highlighter_rect.html
   doc_inspector_highlighter_rect_iframe.html
+  doc_inspector_highlighter_scroll.html
   doc_inspector_highlighter_xbl.xul
   doc_inspector_infobar_01.html
   doc_inspector_infobar_02.html
   doc_inspector_infobar_03.html
   doc_inspector_infobar_textnode.html
   doc_inspector_long-divs.html
   doc_inspector_menu.html
   doc_inspector_outerhtml.html
@@ -65,16 +66,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_destroy-before-ready.js]
 [browser_inspector_expand-collapse.js]
 [browser_inspector_gcli-inspect-command.js]
 [browser_inspector_highlighter-01.js]
 [browser_inspector_highlighter-02.js]
 [browser_inspector_highlighter-03.js]
 [browser_inspector_highlighter-04.js]
 [browser_inspector_highlighter-05.js]
+[browser_inspector_highlighter-06.js]
 [browser_inspector_highlighter-by-type.js]
 [browser_inspector_highlighter-cancel.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-cssgrid_01.js]
 [browser_inspector_highlighter-cssgrid_02.js]
 [browser_inspector_highlighter-cssshape_01.js]
 [browser_inspector_highlighter-cssshape_02.js]
 [browser_inspector_highlighter-cssshape_03.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-06.js
@@ -0,0 +1,31 @@
+/* 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";
+
+// Test that the browser remembers the previous scroll position after reload, even with
+// Inspector opened - Bug 1382341.
+// NOTE: Using a real file instead data: URL since the bug won't happen on data: URL
+const TEST_URI = URL_ROOT + "doc_inspector_highlighter_scroll.html";
+
+add_task(function* () {
+  let { inspector, testActor } = yield openInspectorForURL(TEST_URI);
+
+  yield testActor.scrollIntoView("a");
+  yield selectAndHighlightNode("a", inspector);
+
+  let markupLoaded = inspector.once("markuploaded");
+
+  let y = yield testActor.eval("window.pageYOffset");
+  isnot(y, 0, "window scrolled vertically.");
+
+  info("Reloading page.");
+  yield testActor.reload();
+
+  info("Waiting for markupview to load after reload.");
+  yield markupLoaded;
+
+  let newY = yield testActor.eval("window.pageYOffset");
+  is(y, newY, "window remember the previous scroll position.");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/doc_inspector_highlighter_scroll.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <style>
+      p {
+        height: 200vh;
+      }
+    </style>
+  </head>
+  <body>
+    <p>Bug 1382341 - test page reload and scroll position</p>
+    <a>An element anchor, used to scroll the page</a>
+  </body>
+</html>
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -303,17 +303,16 @@ CanvasFrameAnonymousContentHelper.protot
    * The "window-ready" event can be triggered when:
    *   - a new window is created
    *   - a window is unfrozen from bfcache
    *   - when first attaching to a page
    *   - when swapping frame loaders (moving tabs, toggling RDM)
    */
   _onWindowReady(e, {isTopLevel}) {
     if (isTopLevel) {
-      this._remove();
       this._removeAllListeners();
       this.elements.clear();
       this._insert();
       this.anonymousContentDocument = this.highlighterEnv.document;
     }
   },
 
   getComputedStylePropertyValue(id, property) {
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2911,16 +2911,30 @@ NS_IMETHODIMP
 nsDocShell::SetSecurityUI(nsISecureBrowserUI* aSecurityUI)
 {
   mSecurityUI = aSecurityUI;
   mSecurityUI->SetDocShell(this);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocShell::GetLoadURIDelegate(nsILoadURIDelegate** aLoadURIDelegate)
+{
+  NS_IF_ADDREF(*aLoadURIDelegate = mLoadURIDelegate);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetLoadURIDelegate(nsILoadURIDelegate* aLoadURIDelegate)
+{
+  mLoadURIDelegate = aLoadURIDelegate;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::GetUseErrorPages(bool* aUseErrorPages)
 {
   *aUseErrorPages = UseErrorPages();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetUseErrorPages(bool aUseErrorPages)
@@ -9923,51 +9937,70 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       // an iframe since that's more common.
       contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME;
     }
   } else {
     contentType = nsIContentPolicy::TYPE_DOCUMENT;
     isTargetTopLevelDocShell = true;
   }
 
+  if (contentType == nsIContentPolicy::TYPE_DOCUMENT &&
+      nsIOService::BlockToplevelDataUriNavigations()) {
+    bool isDataURI =
+      (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
+    // Let's block all toplevel document navigations to a data: URI.
+    // In all cases where the toplevel document is navigated to a
+    // data: URI the triggeringPrincipal is a codeBasePrincipal, or
+    // a NullPrincipal. In other cases, e.g. typing a data: URL into
+    // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
+    // we don't want to block those loads. Only exception, loads coming
+    // from an external applicaton (e.g. Thunderbird) don't load
+    // using a codeBasePrincipal, but we want to block those loads.
+    bool loadFromExternal = (aLoadType == LOAD_NORMAL_EXTERNAL);
+    if (isDataURI && (loadFromExternal || 
+        !nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal))) {
+      NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
+      if (specUTF16.Length() > 50) {
+        specUTF16.Truncate(50);
+        specUTF16.AppendLiteral("...");
+      }
+      const char16_t* params[] = { specUTF16.get() };
+      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                      NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
+                                      // no doc available, log to browser console
+                                      nullptr,
+                                      nsContentUtils::eSECURITY_PROPERTIES,
+                                      "BlockTopLevelDataURINavigation",
+                                      params, ArrayLength(params));
+      return NS_OK;
+    }
+  }
+
   // If there's no targetDocShell, that means we are about to create a new
   // window (or aWindowTarget is empty). Perform a content policy check before
-  // creating the window.
-  if (!targetDocShell) {
-    nsCOMPtr<Element> requestingElement;
+  // creating the window. Please note for all other docshell loads
+  // content policy checks are performed within the contentSecurityManager
+  // when the channel is about to be openend.
+  if (!targetDocShell && !aWindowTarget.IsEmpty()) {
+    MOZ_ASSERT(contentType == nsIContentPolicy::TYPE_DOCUMENT,
+               "opening a new window requires type to be TYPE_DOCUMENT");
+
     nsISupports* requestingContext = nullptr;
-
-    if (contentType == nsIContentPolicy::TYPE_DOCUMENT) {
-      if (XRE_IsContentProcess()) {
-        // In e10s the child process doesn't have access to the element that
-        // contains the browsing context (because that element is in the chrome
-        // process). So we just pass mScriptGlobal.
-        requestingContext = ToSupports(mScriptGlobal);
-      } else {
-        // This is for loading non-e10s tabs and toplevel windows of various
-        // sorts.
-        // For the toplevel window cases, requestingElement will be null.
-        requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal();
-        requestingContext = requestingElement;
-      }
+    if (XRE_IsContentProcess()) {
+      // In e10s the child process doesn't have access to the element that
+      // contains the browsing context (because that element is in the chrome
+      // process). So we just pass mScriptGlobal.
+      requestingContext = ToSupports(mScriptGlobal);
     } else {
-      requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal();
+      // This is for loading non-e10s tabs and toplevel windows of various
+      // sorts.
+      // For the toplevel window cases, requestingElement will be null.
+      nsCOMPtr<Element> requestingElement =
+        mScriptGlobal->AsOuter()->GetFrameElementInternal();
       requestingContext = requestingElement;
-
-#ifdef DEBUG
-      if (requestingElement) {
-        // Get the docshell type for requestingElement.
-        nsCOMPtr<nsIDocument> requestingDoc = requestingElement->OwnerDoc();
-        nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell();
-
-        // requestingElement docshell type = current docshell type.
-        MOZ_ASSERT(mItemType == elementDocShell->ItemType(),
-                  "subframes should have the same docshell type as their parent");
-      }
-#endif
     }
 
     // Since Content Policy checks are performed within docShell as well as
     // the ContentSecurityManager we need a reliable way to let certain
     // nsIContentPolicy consumers ignore duplicate calls. Let's use the 'extra'
     // argument to pass a specific identifier.
     nsCOMPtr<nsISupportsString> extraStr =
       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
@@ -10061,16 +10094,41 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
         nsCOMPtr<nsIDocShellTreeItem> parent;
         treeItem->GetSameTypeParent(getter_AddRefs(parent));
         parent.swap(treeItem);
       } while (treeItem);
     }
   }
 
+  const nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument()
+                                          : nullptr;
+  const bool isDocumentAuxSandboxed = doc &&
+    (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION);
+
+  if (aURI && mLoadURIDelegate &&
+      (!targetDocShell || targetDocShell == static_cast<nsIDocShell*>(this))) {
+    // Dispatch only load requests for the current or a new window to the
+    // delegate, e.g., to allow for GeckoView apps to handle the load event
+    // outside of Gecko.
+    const int where = (aWindowTarget.IsEmpty() || targetDocShell)
+                      ? nsIBrowserDOMWindow::OPEN_CURRENTWINDOW
+                      : nsIBrowserDOMWindow::OPEN_NEW;
+
+    if (where == nsIBrowserDOMWindow::OPEN_NEW && isDocumentAuxSandboxed) {
+      return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+    }
+
+    if (NS_SUCCEEDED(mLoadURIDelegate->LoadURI(aURI, where, aFlags,
+                                               aTriggeringPrincipal))) {
+      // The request has been handled, nothing to do here.
+      return NS_OK;
+    }
+  }
+
   //
   // Resolve the window target before going any further...
   // If the load has been targeted to another DocShell, then transfer the
   // load to it...
   //
   if (!aWindowTarget.IsEmpty()) {
     // We've already done our owner-inheriting.  Mask out that bit, so we
     // don't try inheriting an owner from the target window if we came up
@@ -10078,24 +10136,18 @@ nsDocShell::InternalLoad(nsIURI* aURI,
     aFlags = aFlags & ~INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
 
     bool isNewWindow = false;
     if (!targetDocShell) {
       // If the docshell's document is sandboxed, only open a new window
       // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set.
       // (i.e. if allow-popups is specified)
       NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
-      nsIDocument* doc = mContentViewer->GetDocument();
-      uint32_t sandboxFlags = 0;
-
-      if (doc) {
-        sandboxFlags = doc->GetSandboxFlags();
-        if (sandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) {
-          return NS_ERROR_DOM_INVALID_ACCESS_ERR;
-        }
+      if (isDocumentAuxSandboxed) {
+        return NS_ERROR_DOM_INVALID_ACCESS_ERR;
       }
 
       nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
       NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE);
 
       nsCOMPtr<nsPIDOMWindowOuter> newWin;
       nsAutoCString spec;
       if (aURI) {
@@ -11024,27 +11076,50 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   // * Top-level load: In this case, loadingNode is null, but loadingWindow
   //   is our mScriptGlobal. We pass null for loadingPrincipal in this case.
   // * Subframe load: loadingWindow is null, but loadingNode is the frame
   //   element for the load. loadingPrincipal is the NodePrincipal of the frame
   //   element.
   nsCOMPtr<nsINode> loadingNode;
   nsCOMPtr<nsPIDOMWindowOuter> loadingWindow;
   nsCOMPtr<nsIPrincipal> loadingPrincipal;
+  nsCOMPtr<nsISupports> topLevelLoadingContext;
 
   if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
     loadingNode = nullptr;
     loadingPrincipal = nullptr;
     loadingWindow = mScriptGlobal->AsOuter();
+    if (XRE_IsContentProcess()) {
+      // In e10s the child process doesn't have access to the element that
+      // contains the browsing context (because that element is in the chrome
+      // process).
+      nsCOMPtr<nsITabChild> tabChild = GetTabChild();
+      topLevelLoadingContext = ToSupports(tabChild);
+    } else {
+      // This is for loading non-e10s tabs and toplevel windows of various
+      // sorts.
+      // For the toplevel window cases, requestingElement will be null.
+      nsCOMPtr<Element> requestingElement =
+        loadingWindow->GetFrameElementInternal();
+      topLevelLoadingContext = requestingElement;
+    }
   } else {
     loadingWindow = nullptr;
     loadingNode = mScriptGlobal->AsOuter()->GetFrameElementInternal();
     if (loadingNode) {
       // If we have a loading node, then use that as our loadingPrincipal.
       loadingPrincipal = loadingNode->NodePrincipal();
+#ifdef DEBUG
+      // Get the docshell type for requestingElement.
+      nsCOMPtr<nsIDocument> requestingDoc = loadingNode->OwnerDoc();
+      nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell();
+      // requestingElement docshell type = current docshell type.
+      MOZ_ASSERT(mItemType == elementDocShell->ItemType(),
+                "subframes should have the same docshell type as their parent");
+#endif
     } else {
       // If this isn't a top-level load and mScriptGlobal's frame element is
       // null, then the element got removed from the DOM while we were trying
       // to load this resource. This docshell is scheduled for destruction
       // already, so bail out here.
       return NS_OK;
     }
   }
@@ -11095,52 +11170,21 @@ nsDocShell::DoURILoad(nsIURI* aURI,
     securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
   }
   if (isSandBoxed) {
     securityFlags |= nsILoadInfo::SEC_SANDBOXED;
   }
 
   nsCOMPtr<nsILoadInfo> loadInfo =
     (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) ?
-      new LoadInfo(loadingWindow, aTriggeringPrincipal,
+      new LoadInfo(loadingWindow, aTriggeringPrincipal, topLevelLoadingContext,
                    securityFlags) :
       new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode,
                    securityFlags, aContentPolicyType);
 
-  if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT &&
-      nsIOService::BlockToplevelDataUriNavigations()) {
-    bool isDataURI =
-      (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
-    // Let's block all toplevel document navigations to a data: URI.
-    // In all cases where the toplevel document is navigated to a
-    // data: URI the triggeringPrincipal is a codeBasePrincipal, or
-    // a NullPrincipal. In other cases, e.g. typing a data: URL into
-    // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
-    // we don't want to block those loads. Only exception, loads coming
-    // from an external applicaton (e.g. Thunderbird) don't load
-    // using a codeBasePrincipal, but we want to block those loads.
-    if (isDataURI && (aLoadFromExternal || 
-        !nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal))) {
-      NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
-      if (specUTF16.Length() > 50) {
-        specUTF16.Truncate(50);
-        specUTF16.AppendLiteral("...");
-      }
-      const char16_t* params[] = { specUTF16.get() };
-      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                      NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
-                                      // no doc available, log to browser console
-                                      nullptr,
-                                      nsContentUtils::eSECURITY_PROPERTIES,
-                                      "BlockTopLevelDataURINavigation",
-                                      params, ArrayLength(params));
-      return NS_OK;
-    }
-  }
-
   if (aPrincipalToInherit) {
     loadInfo->SetPrincipalToInherit(aPrincipalToInherit);
   }
 
   // We have to do this in case our OriginAttributes are different from the
   // OriginAttributes of the parent document. Or in case there isn't a
   // parent document.
   bool isTopLevelDoc = mItemType == typeContent &&
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -56,16 +56,17 @@
 #include "nsILinkHandler.h"
 #include "nsIClipboardCommands.h"
 #include "nsITabParent.h"
 #include "nsCRT.h"
 #include "prtime.h"
 #include "nsRect.h"
 #include "Units.h"
 #include "nsIDeprecationWarner.h"
+#include "nsILoadURIDelegate.h"
 
 namespace mozilla {
 class Encoding;
 class HTMLEditor;
 enum class TaskCategory;
 namespace dom {
 class EventTarget;
 class PendingGlobalHistoryEntry;
@@ -907,16 +908,18 @@ protected:
   // Note these are intentionally not addrefd. Doing so will create a cycle.
   // For that reasons don't use nsCOMPtr.
 
   nsIDocShellTreeOwner* mTreeOwner; // Weak Reference
   mozilla::dom::EventTarget* mChromeEventHandler; // Weak Reference
 
   eCharsetReloadState mCharsetReloadState;
 
+  nsCOMPtr<nsILoadURIDelegate> mLoadURIDelegate;
+
   // Offset in the parent's child list.
   // -1 if the docshell is added dynamically to the parent shell.
   int32_t mChildOffset;
   uint32_t mBusyFlags;
   uint32_t mAppType;
   uint32_t mLoadType;
 
   int32_t mMarginWidth;
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -48,16 +48,17 @@ interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
 interface nsITabChild;
 interface nsICommandManager;
 interface nsICommandParams;
+interface nsILoadURIDelegate;
 native TabChildRef(already_AddRefed<nsITabChild>);
 
 [scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing this interface.  If it can't be loaded here
@@ -459,16 +460,23 @@ interface nsIDocShell : nsIDocShellTreeI
 
   /**
    * The SecureBrowserUI object for this docshell.  This is set by XUL
    * <browser> or nsWebBrowser for their root docshell.
    */
   attribute nsISecureBrowserUI securityUI;
 
   /**
+   * Object used to delegate URI loading to an upper context.
+   * Currently only set for GeckoView to allow handling of load requests
+   * at the application level.
+   */
+  attribute nsILoadURIDelegate loadURIDelegate;
+
+  /**
    * Cancel the XPCOM timers for each meta-refresh URI in this docshell,
    * and this docshell's children, recursively. The meta-refresh timers can be
    * restarted using resumeRefreshURIs().  If the timers are already suspended,
    * this has no effect.
    */
   void suspendRefreshURIs();
 
   /**
--- a/dom/base/nsContentPolicy.cpp
+++ b/dom/base/nsContentPolicy.cpp
@@ -15,16 +15,17 @@
 #include "nsContentPolicyUtils.h"
 #include "mozilla/dom/nsCSPService.h"
 #include "nsContentPolicy.h"
 #include "nsIURI.h"
 #include "nsIDocShell.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMWindow.h"
+#include "nsITabChild.h"
 #include "nsIContent.h"
 #include "nsIImageLoadingContent.h"
 #include "nsILoadContext.h"
 #include "nsCOMArray.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "nsIContentSecurityPolicy.h"
 #include "mozilla/dom/TabGroup.h"
@@ -88,18 +89,19 @@ nsContentPolicy::CheckPolicy(CPMethod   
     NS_PRECONDITION(decision, "Null out pointer");
     WARN_IF_URI_UNINITIALIZED(contentLocation, "Request URI");
     WARN_IF_URI_UNINITIALIZED(requestingLocation, "Requesting URI");
 
 #ifdef DEBUG
     {
         nsCOMPtr<nsIDOMNode> node(do_QueryInterface(requestingContext));
         nsCOMPtr<nsIDOMWindow> window(do_QueryInterface(requestingContext));
-        NS_ASSERTION(!requestingContext || node || window,
-                     "Context should be a DOM node or a DOM window!");
+        nsCOMPtr<nsITabChild> tabChild(do_QueryInterface(requestingContext));
+        NS_ASSERTION(!requestingContext || node || window || tabChild,
+                     "Context should be a DOM node, DOM window or a tabChild!");
     }
 #endif
 
     /*
      * There might not be a requestinglocation. This can happen for
      * iframes with an image as src. Get the uri from the dom node.
      * See bug 254510
      */
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -170,17 +170,17 @@ DoCORSChecks(nsIChannel* aChannel, nsILo
 static nsresult
 DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo)
 {
   nsContentPolicyType contentPolicyType =
     aLoadInfo->GetExternalContentPolicyType();
   nsContentPolicyType internalContentPolicyType =
     aLoadInfo->InternalContentPolicyType();
   nsCString mimeTypeGuess;
-  nsCOMPtr<nsINode> requestingContext = nullptr;
+  nsCOMPtr<nsISupports> requestingContext = nullptr;
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
       contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
     // TYPE_DOCUMENT and TYPE_SUBDOCUMENT loads might potentially
@@ -224,17 +224,17 @@ DoContentSecurityChecks(nsIChannel* aCha
     case nsIContentPolicy::TYPE_OBJECT: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
 
     case nsIContentPolicy::TYPE_DOCUMENT: {
       mimeTypeGuess = EmptyCString();
-      requestingContext = aLoadInfo->LoadingNode();
+      requestingContext = aLoadInfo->ContextForTopLevelLoad();
       break;
     }
 
     case nsIContentPolicy::TYPE_SUBDOCUMENT: {
       mimeTypeGuess = NS_LITERAL_CSTRING("text/html");
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
@@ -254,20 +254,23 @@ DoContentSecurityChecks(nsIChannel* aCha
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
 
     case nsIContentPolicy::TYPE_XMLHTTPREQUEST: {
       // alias nsIContentPolicy::TYPE_DATAREQUEST:
       requestingContext = aLoadInfo->LoadingNode();
-      MOZ_ASSERT(!requestingContext ||
-                 requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
-                 "type_xml requires requestingContext of type Document");
-
+#ifdef DEBUG
+      {
+        nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext);
+        MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+                   "type_xml requires requestingContext of type Document");
+      }
+#endif
       // We're checking for the external TYPE_XMLHTTPREQUEST here in case
       // an addon creates a request with that type.
       if (internalContentPolicyType ==
             nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST ||
           internalContentPolicyType ==
             nsIContentPolicy::TYPE_XMLHTTPREQUEST) {
         mimeTypeGuess = EmptyCString();
       }
@@ -278,28 +281,36 @@ DoContentSecurityChecks(nsIChannel* aCha
         mimeTypeGuess = NS_LITERAL_CSTRING(TEXT_EVENT_STREAM);
       }
       break;
     }
 
     case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
-      MOZ_ASSERT(!requestingContext ||
-                 requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE,
-                 "type_subrequest requires requestingContext of type Element");
+#ifdef DEBUG
+      {
+        nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext);
+        MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::ELEMENT_NODE,
+                   "type_subrequest requires requestingContext of type Element");
+      }
+#endif
       break;
     }
 
     case nsIContentPolicy::TYPE_DTD: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
-      MOZ_ASSERT(!requestingContext ||
-                 requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
-                 "type_dtd requires requestingContext of type Document");
+#ifdef DEBUG
+      {
+        nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext);
+        MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+                   "type_dtd requires requestingContext of type Document");
+      }
+#endif
       break;
     }
 
     case nsIContentPolicy::TYPE_FONT: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
@@ -307,19 +318,23 @@ DoContentSecurityChecks(nsIChannel* aCha
     case nsIContentPolicy::TYPE_MEDIA: {
       if (internalContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_TRACK) {
         mimeTypeGuess = NS_LITERAL_CSTRING("text/vtt");
       }
       else {
         mimeTypeGuess = EmptyCString();
       }
       requestingContext = aLoadInfo->LoadingNode();
-      MOZ_ASSERT(!requestingContext ||
-                 requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE,
-                 "type_media requires requestingContext of type Element");
+#ifdef DEBUG
+      {
+        nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext);
+        MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::ELEMENT_NODE,
+                   "type_media requires requestingContext of type Element");
+      }
+#endif
       break;
     }
 
     case nsIContentPolicy::TYPE_WEBSOCKET: {
       // Websockets have to use the proxied URI:
       // ws:// instead of http:// for CSP checks
       nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal
         = do_QueryInterface(aChannel);
@@ -337,28 +352,36 @@ DoContentSecurityChecks(nsIChannel* aCha
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
 
     case nsIContentPolicy::TYPE_XSLT: {
       mimeTypeGuess = NS_LITERAL_CSTRING("application/xml");
       requestingContext = aLoadInfo->LoadingNode();
-      MOZ_ASSERT(!requestingContext ||
-                 requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
-                 "type_xslt requires requestingContext of type Document");
+#ifdef DEBUG
+      {
+        nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext);
+        MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+                   "type_xslt requires requestingContext of type Document");
+      }
+#endif
       break;
     }
 
     case nsIContentPolicy::TYPE_BEACON: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
-      MOZ_ASSERT(!requestingContext ||
-                 requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
-                 "type_beacon requires requestingContext of type Document");
+#ifdef DEBUG
+      {
+        nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext);
+        MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+                   "type_beacon requires requestingContext of type Document");
+      }
+#endif
       break;
     }
 
     case nsIContentPolicy::TYPE_FETCH: {
       mimeTypeGuess = EmptyCString();
       requestingContext = aLoadInfo->LoadingNode();
       break;
     }
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -686,59 +686,16 @@ HTMLEditor::HandleKeyPressEvent(WidgetKe
     // we don't PreventDefault() here or keybindings like control-x won't work
     return NS_OK;
   }
   aKeyboardEvent->PreventDefault();
   nsAutoString str(aKeyboardEvent->mCharCode);
   return TypedText(str, eTypedText);
 }
 
-static void
-AssertParserServiceIsCorrect(nsIAtom* aTag, bool aIsBlock)
-{
-#ifdef DEBUG
-  // Check this against what we would have said with the old code:
-  if (aTag == nsGkAtoms::p ||
-      aTag == nsGkAtoms::div ||
-      aTag == nsGkAtoms::blockquote ||
-      aTag == nsGkAtoms::h1 ||
-      aTag == nsGkAtoms::h2 ||
-      aTag == nsGkAtoms::h3 ||
-      aTag == nsGkAtoms::h4 ||
-      aTag == nsGkAtoms::h5 ||
-      aTag == nsGkAtoms::h6 ||
-      aTag == nsGkAtoms::ul ||
-      aTag == nsGkAtoms::ol ||
-      aTag == nsGkAtoms::dl ||
-      aTag == nsGkAtoms::noscript ||
-      aTag == nsGkAtoms::form ||
-      aTag == nsGkAtoms::hr ||
-      aTag == nsGkAtoms::table ||
-      aTag == nsGkAtoms::fieldset ||
-      aTag == nsGkAtoms::address ||
-      aTag == nsGkAtoms::col ||
-      aTag == nsGkAtoms::colgroup ||
-      aTag == nsGkAtoms::li ||
-      aTag == nsGkAtoms::dt ||
-      aTag == nsGkAtoms::dd ||
-      aTag == nsGkAtoms::legend) {
-    if (!aIsBlock) {
-      nsAutoString assertmsg (NS_LITERAL_STRING("Parser and editor disagree on blockness: "));
-
-      nsAutoString tagName;
-      aTag->ToString(tagName);
-      assertmsg.Append(tagName);
-      char* assertstr = ToNewCString(assertmsg);
-      NS_ASSERTION(aIsBlock, assertstr);
-      free(assertstr);
-    }
-  }
-#endif // DEBUG
-}
-
 /**
  * Returns true if the id represents an element of block type.
  * Can be used to determine if a new paragraph should be started.
  */
 bool
 HTMLEditor::NodeIsBlockStatic(const nsINode* aElement)
 {
   MOZ_ASSERT(aElement);
@@ -748,36 +705,32 @@ HTMLEditor::NodeIsBlockStatic(const nsIN
   if (aElement->IsAnyOfHTMLElements(nsGkAtoms::body,
                                     nsGkAtoms::head,
                                     nsGkAtoms::tbody,
                                     nsGkAtoms::thead,
                                     nsGkAtoms::tfoot,
                                     nsGkAtoms::tr,
                                     nsGkAtoms::th,
                                     nsGkAtoms::td,
-                                    nsGkAtoms::li,
                                     nsGkAtoms::dt,
-                                    nsGkAtoms::dd,
-                                    nsGkAtoms::pre)) {
+                                    nsGkAtoms::dd)) {
     return true;
   }
 
   bool isBlock;
 #ifdef DEBUG
   // XXX we can't use DebugOnly here because VC++ is stupid (bug 802884)
   nsresult rv =
 #endif
     nsContentUtils::GetParserService()->
     IsBlock(nsContentUtils::GetParserService()->HTMLAtomTagToId(
               aElement->NodeInfo()->NameAtom()),
             isBlock);
   MOZ_ASSERT(rv == NS_OK);
 
-  AssertParserServiceIsCorrect(aElement->NodeInfo()->NameAtom(), isBlock);
-
   return isBlock;
 }
 
 nsresult
 HTMLEditor::NodeIsBlockStatic(nsIDOMNode* aNode,
                               bool* aIsBlock)
 {
   if (!aNode || !aIsBlock) {
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -430,28 +430,32 @@ GPUProcessManager::SimulateDeviceReset()
 }
 
 void
 GPUProcessManager::DisableWebRender(wr::WebRenderError aError)
 {
   if (!gfx::gfxVars::UseWebRender()) {
     return;
   }
+  // Disable WebRender
   if (aError == wr::WebRenderError::INITIALIZE) {
-    // Disable WebRender
     gfx::gfxConfig::GetFeature(gfx::Feature::WEBRENDER).ForceDisable(
       gfx::FeatureStatus::Unavailable,
       "WebRender initialization failed",
       NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBRENDER_INITIALIZE"));
   } else if (aError == wr::WebRenderError::MAKE_CURRENT) {
-    // Disable WebRender
     gfx::gfxConfig::GetFeature(gfx::Feature::WEBRENDER).ForceDisable(
       gfx::FeatureStatus::Unavailable,
       "Failed to make render context current",
       NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBRENDER_MAKE_CURRENT"));
+  } else if (aError == wr::WebRenderError::RENDER) {
+    gfx::gfxConfig::GetFeature(gfx::Feature::WEBRENDER).ForceDisable(
+      gfx::FeatureStatus::Unavailable,
+      "Failed to render WebRender",
+      NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBRENDER_RENDER"));
   } else {
     MOZ_ASSERT_UNREACHABLE("Invalid value");
   }
   gfx::gfxVars::SetUseWebRender(false);
 
   if (mProcess) {
     OnRemoteProcessDeviceReset(mProcess);
   } else {
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -297,28 +297,26 @@ RotatedContentBuffer::BorrowDrawTargetFo
   // Figure out which quadrant to draw in
   int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x;
   int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y;
   XSide sideX = bounds.XMost() <= xBoundary ? RIGHT : LEFT;
   YSide sideY = bounds.YMost() <= yBoundary ? BOTTOM : TOP;
   IntRect quadrantRect = GetQuadrantRectangle(sideX, sideY);
   NS_ASSERTION(quadrantRect.Contains(bounds), "Messed up quadrants");
 
-  mLoanedTransform = mLoanedDrawTarget->GetTransform();
-
-  Matrix transform = Matrix(mLoanedTransform)
-                          .PreTranslate(-quadrantRect.x,
-                                        -quadrantRect.y);
-
   if (aSetTransform) {
+    mLoanedTransform = mLoanedDrawTarget->GetTransform();
+    Matrix transform = Matrix(mLoanedTransform)
+                            .PreTranslate(-quadrantRect.x,
+                                          -quadrantRect.y);
     mLoanedDrawTarget->SetTransform(transform);
     mSetTransform = true;
   } else {
     MOZ_ASSERT(aOutMatrix);
-    *aOutMatrix = transform;
+    *aOutMatrix = Matrix::Translation(-quadrantRect.x, -quadrantRect.y);
     mSetTransform = false;
   }
 
   return mLoanedDrawTarget;
 }
 
 void
 BorrowDrawTarget::ReturnDrawTarget(gfx::DrawTarget*& aReturned)
--- a/gfx/skia/skia/src/core/SkAnalyticEdge.h
+++ b/gfx/skia/skia/src/core/SkAnalyticEdge.h
@@ -134,21 +134,28 @@ bool SkAnalyticEdge::setLine(const SkPoi
 #if defined(__arm__)
     asm volatile("dsb");  // crbug.com/710131
 #endif
     fRiteE = nullptr;
 
     // We must set X/Y using the same way (e.g., times 4, to FDot6, then to Fixed) as Quads/Cubics.
     // Otherwise the order of the edge might be wrong due to precision limit.
     const int accuracy = kDefaultAccuracy;
+#ifdef SK_RASTERIZE_EVEN_ROUNDING
+    SkFixed x0 = SkFDot6ToFixed(SkScalarRoundToFDot6(p0.fX, accuracy)) >> accuracy;
+    SkFixed y0 = SnapY(SkFDot6ToFixed(SkScalarRoundToFDot6(p0.fY, accuracy)) >> accuracy);
+    SkFixed x1 = SkFDot6ToFixed(SkScalarRoundToFDot6(p1.fX, accuracy)) >> accuracy;
+    SkFixed y1 = SnapY(SkFDot6ToFixed(SkScalarRoundToFDot6(p1.fY, accuracy)) >> accuracy);
+#else
     const int multiplier = (1 << kDefaultAccuracy);
     SkFixed x0 = SkFDot6ToFixed(SkScalarToFDot6(p0.fX * multiplier)) >> accuracy;
     SkFixed y0 = SnapY(SkFDot6ToFixed(SkScalarToFDot6(p0.fY * multiplier)) >> accuracy);
     SkFixed x1 = SkFDot6ToFixed(SkScalarToFDot6(p1.fX * multiplier)) >> accuracy;
     SkFixed y1 = SnapY(SkFDot6ToFixed(SkScalarToFDot6(p1.fY * multiplier)) >> accuracy);
+#endif
 
     int winding = 1;
 
     if (y0 > y1) {
         SkTSwap(x0, x1);
         SkTSwap(y0, y1);
         winding = -1;
     }
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -496,16 +496,17 @@ private:
   DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist",       IgnoreDXInterop2Blacklist, bool, false);
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
 #if defined(XP_MACOSX)
   DECL_GFX_PREF(Live, "gl.multithreaded",                      GLMultithreaded, bool, false);
 #endif
   DECL_GFX_PREF(Live, "gl.require-hardware",                   RequireHardwareGL, bool, false);
   DECL_GFX_PREF(Live, "gl.use-tls-is-current",                 UseTLSIsCurrent, int32_t, 0);
 
+  DECL_GFX_PREF(Live, "image.cache.factor2.threshold-surfaces", ImageCacheFactor2ThresholdSurfaces, int32_t, -1);
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Live, "image.decode-immediately.enabled",      ImageDecodeImmediatelyEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
   DECL_GFX_PREF(Live, "image.layout_network_priority",         ImageLayoutNetworkPriority, bool, true);
   DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -1,14 +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/. */
 
-extern crate app_units;
-extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 extern crate rayon;
 
 #[path="common/boilerplate.rs"]
 mod boilerplate;
 
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -196,14 +196,14 @@ pub fn main_wrapper(example: &mut Exampl
                         );
                         api.generate_frame(document_id, None);
                     }
                 }
             }
         }
 
         renderer.update();
-        renderer.render(DeviceUintSize::new(width, height));
+        renderer.render(DeviceUintSize::new(width, height)).unwrap();
         window.swap_buffers().ok();
     }
 
     renderer.deinit();
 }
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -1,14 +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/. */
 
-extern crate app_units;
-extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 
 #[path="common/boilerplate.rs"]
 mod boilerplate;
 
 use boilerplate::Example;
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -12,16 +12,19 @@
 
 #define SUBPX_DIR_NONE        0
 #define SUBPX_DIR_HORIZONTAL  1
 #define SUBPX_DIR_VERTICAL    2
 
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
+// An A8 target for standalone tasks that is available to all passes.
+uniform sampler2DArray sSharedCacheA8;
+
 uniform sampler2D sGradients;
 
 struct RectWithSize {
     vec2 p0;
     vec2 size;
 };
 
 struct RectWithEndpoint {
@@ -762,17 +765,17 @@ BoxShadow fetch_boxshadow(int address) {
 }
 
 BoxShadow fetch_boxshadow_direct(ivec2 address) {
     vec4 data[4] = fetch_from_resource_cache_4_direct(address);
     return BoxShadow(data[0], data[1], data[2], data[3]);
 }
 
 void write_clip(vec2 global_pos, ClipArea area) {
-    vec2 texture_size = vec2(textureSize(sCacheA8, 0).xy);
+    vec2 texture_size = vec2(textureSize(sSharedCacheA8, 0).xy);
     vec2 uv = global_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
     vClipMaskUvBounds = area.task_bounds / texture_size.xyxy;
     vClipMaskUv = vec3(uv / texture_size, area.screen_origin_target_index.z);
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 
@@ -801,17 +804,17 @@ vec2 init_transform_fs(vec3 local_pos, o
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
-        all(inside) ? textureLod(sCacheA8, vClipMaskUv, 0.0).r : 0.0;
+        all(inside) ? textureLod(sSharedCacheA8, vClipMaskUv, 0.0).r : 0.0;
 }
 
 #ifdef WR_FEATURE_DITHERING
 vec4 dither(vec4 color) {
     const int matrix_mask = 7;
 
     ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
     float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
--- a/gfx/webrender/res/ps_box_shadow.fs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.fs.glsl
@@ -13,11 +13,11 @@ void main(void) {
     // shadow corner. This can happen, for example, when
     // drawing the outer parts of an inset box shadow.
     uv = clamp(uv, vec2(0.0), vec2(1.0));
 
     // Map the unit UV to the actual UV rect in the cache.
     uv = mix(vCacheUvRectCoords.xy, vCacheUvRectCoords.zw, uv);
 
     // Modulate the box shadow by the color.
-    float mask = texture(sColor1, vec3(uv, vUv.z)).r;
+    float mask = texture(sSharedCacheA8, vec3(uv, vUv.z)).r;
     oFragColor = clip_scale * dither(vColor * vec4(1.0, 1.0, 1.0, mask));
 }
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -22,15 +22,15 @@ void main(void) {
     // Constant offsets to inset from bilinear filtering border.
     vec2 patch_origin = child_task.data0.xy + vec2(1.0);
     vec2 patch_size_device_pixels = child_task.data0.zw - vec2(2.0);
     vec2 patch_size = patch_size_device_pixels / uDevicePixelRatio;
 
     vUv.xy = (vi.local_pos - prim.local_rect.p0) / patch_size;
     vMirrorPoint = 0.5 * prim.local_rect.size / patch_size;
 
-    vec2 texture_size = vec2(textureSize(sCacheA8, 0));
+    vec2 texture_size = vec2(textureSize(sSharedCacheA8, 0));
     vCacheUvRectCoords = vec4(patch_origin, patch_origin + patch_size_device_pixels) / texture_size.xyxy;
 
     vColor = bs.color;
 
     write_clip(vi.screen_pos, prim.clip_area);
 }
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -246,17 +246,17 @@ impl ClipScrollTree {
         };
         self.update_node_transform(root_reference_frame_id, &state);
     }
 
     fn update_node_transform(&mut self, layer_id: ClipId, state: &TransformUpdateState) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let (state, node_children) = {
-            let mut node = match self.nodes.get_mut(&layer_id) {
+            let node = match self.nodes.get_mut(&layer_id) {
                 Some(node) => node,
                 None => return,
             };
             node.update_transform(&state);
 
             // The transformation we are passing is the transformation of the parent
             // reference frame and the offset is the accumulated offset of all the nodes
             // between us and the parent reference frame. If we are a reference frame,
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -404,17 +404,17 @@ impl FontContext {
         }
 
         // We need to invert the pixels back since right now
         // transparent pixels are actually opaque white.
         for i in 0..metrics.rasterized_height {
             let current_height = (i * metrics.rasterized_width * 4) as usize;
             let end_row = current_height + (metrics.rasterized_width as usize * 4);
 
-            for mut pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
+            for pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
                 pixel[0] = 255 - pixel[0];
                 pixel[1] = 255 - pixel[1];
                 pixel[2] = 255 - pixel[2];
 
                 pixel[3] = match font.render_mode {
                     FontRenderMode::Subpixel => 255,
                     _ => {
                         assert_eq!(pixel[0], pixel[1]);
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{FontInstance, FontInstancePlatformOptions, FontKey, FontRenderMode};
-use api::{GlyphDimensions, GlyphKey, GlyphOptions, SubpixelDirection};
+use api::{GlyphDimensions, GlyphKey};
 use gamma_lut::{GammaLut, Color as ColorLut};
 use internal_types::FastHashMap;
 
 use dwrote;
 use std::sync::Arc;
 
 lazy_static! {
     static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -171,17 +171,17 @@ impl RenderBackend {
             notifier,
             recorder,
 
             enable_render_on_scroll,
         }
     }
 
     fn process_document(&mut self, document_id: DocumentId, message: DocumentMsg,
-                        frame_counter: u32, mut profile_counters: &mut BackendProfileCounters)
+                        frame_counter: u32, profile_counters: &mut BackendProfileCounters)
                         -> DocumentOp
     {
         let doc = self.documents.get_mut(&document_id).expect("No document?");
 
         match message {
             DocumentMsg::SetPageZoom(factor) => {
                 doc.page_zoom_factor = factor.get();
                 DocumentOp::Nop
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -63,16 +63,26 @@ impl RenderTaskTree {
             RenderTaskLocation::Fixed => {
                 debug_assert!(pass_index == passes.len() - 1);
             }
             RenderTaskLocation::Dynamic(..) => {
                 debug_assert!(pass_index < passes.len() - 1);
             }
         }
 
+        // If this task can be shared between multiple
+        // passes, render it in the first pass so that
+        // it is available to all subsequent passes.
+        let pass_index = if task.is_shared() {
+            debug_assert!(task.children.is_empty());
+            0
+        } else {
+            pass_index
+        };
+
         let pass = &mut passes[pass_index];
         pass.add_render_task(id);
     }
 
     pub fn get(&self, id: RenderTaskId) -> &RenderTask {
         &self.tasks[id.0 as usize]
     }
 
@@ -509,16 +519,39 @@ impl RenderTask {
             RenderTaskKind::BoxShadow(..) => RenderTargetKind::Alpha,
 
             RenderTaskKind::Alias(..) => {
                 panic!("BUG: target_kind() called on invalidated task");
             }
         }
     }
 
+    // Check if this task wants to be made available as an input
+    // to all passes (except the first) in the render task tree.
+    // To qualify for this, the task needs to have no children / dependencies.
+    // Currently, this is only supported for A8 targets, but it can be
+    // trivially extended to also support RGBA8 targets in the future
+    // if we decide that is useful.
+    pub fn is_shared(&self) -> bool {
+        match self.kind {
+            RenderTaskKind::Alpha(..) |
+            RenderTaskKind::CachePrimitive(..) |
+            RenderTaskKind::VerticalBlur(..) |
+            RenderTaskKind::Readback(..) |
+            RenderTaskKind::HorizontalBlur(..) => false,
+
+            RenderTaskKind::CacheMask(..) |
+            RenderTaskKind::BoxShadow(..) => true,
+
+            RenderTaskKind::Alias(..) => {
+                panic!("BUG: is_shared() called on aliased task");
+            }
+        }
+    }
+
     pub fn set_alias(&mut self, id: RenderTaskId) {
         debug_assert!(self.cache_key.is_some());
         // TODO(gw): We can easily handle invalidation of tasks that
         //           contain children in the future. Since we don't
         //           have any cases of that yet, just assert to simplify
         //           the current implementation.
         debug_assert!(self.children.is_empty());
         self.kind = RenderTaskKind::Alias(id);
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -136,16 +136,20 @@ enum TextureSampler {
     Color1,
     Color2,
     CacheA8,
     CacheRGBA8,
     ResourceCache,
     Layers,
     RenderTasks,
     Dither,
+    // A special sampler that is bound to the A8 output of
+    // the *first* pass. Items rendered in this target are
+    // available as inputs to tasks in any subsequent pass.
+    SharedCacheA8,
 }
 
 impl TextureSampler {
     fn color(n: usize) -> TextureSampler {
         match n {
             0 => TextureSampler::Color0,
             1 => TextureSampler::Color1,
             2 => TextureSampler::Color2,
@@ -163,16 +167,17 @@ impl Into<TextureSlot> for TextureSample
             TextureSampler::Color1 => TextureSlot(1),
             TextureSampler::Color2 => TextureSlot(2),
             TextureSampler::CacheA8 => TextureSlot(3),
             TextureSampler::CacheRGBA8 => TextureSlot(4),
             TextureSampler::ResourceCache => TextureSlot(5),
             TextureSampler::Layers => TextureSlot(6),
             TextureSampler::RenderTasks => TextureSlot(7),
             TextureSampler::Dither => TextureSlot(8),
+            TextureSampler::SharedCacheA8 => TextureSlot(9),
         }
     }
 }
 
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedVertex {
     pub pos: [f32; 2],
@@ -389,24 +394,41 @@ impl SourceTextureResolver {
     fn deinit(self, device: &mut Device) {
         device.delete_texture(self.dummy_cache_texture);
 
         for texture in self.cache_texture_map {
             device.delete_texture(texture);
         }
     }
 
-    fn set_cache_textures(&mut self,
-                          a8_texture: Option<Texture>,
-                          rgba8_texture: Option<Texture>) {
-        // todo(gw): make the texture recycling cleaner...
-        debug_assert!(self.cache_a8_texture.is_none());
-        debug_assert!(self.cache_rgba8_texture.is_none());
-        self.cache_a8_texture = a8_texture;
-        self.cache_rgba8_texture = rgba8_texture;
+    fn end_pass(&mut self,
+                pass_index: usize,
+                pass_count: usize,
+                mut a8_texture: Option<Texture>,
+                mut rgba8_texture: Option<Texture>,
+                a8_pool: &mut Vec<Texture>,
+                rgba8_pool: &mut Vec<Texture>) {
+        // If we have cache textures from previous pass, return them to the pool.
+        rgba8_pool.extend(self.cache_rgba8_texture.take());
+        a8_pool.extend(self.cache_a8_texture.take());
+
+        if pass_index == pass_count-1 {
+            // On the last pass, return the textures from this pass to the pool.
+            if let Some(texture) = rgba8_texture.take() {
+                rgba8_pool.push(texture);
+            }
+            if let Some(texture) = a8_texture.take() {
+                a8_pool.push(texture);
+            }
+        } else {
+            // We have another pass to process, make these textures available
+            // as inputs to the next pass.
+            self.cache_rgba8_texture = rgba8_texture.take();
+            self.cache_a8_texture = a8_texture.take();
+        }
     }
 
     // Bind a source texture to the device.
     fn bind(&self,
             texture_id: &SourceTexture,
             sampler: TextureSampler,
             device: &mut Device) {
         match *texture_id {
@@ -711,19 +733,24 @@ impl LazilyCompiledShader {
 
         if precache {
             try!{ shader.get(device) };
         }
 
         Ok(shader)
     }
 
-    fn bind(&mut self, device: &mut Device, projection: &Transform3D<f32>) {
-        let program = self.get(device)
-                          .expect("Unable to get shader!");
+    fn bind(&mut self, device: &mut Device, projection: &Transform3D<f32>, renderer_errors: &mut Vec<RendererError>) {
+        let program = match self.get(device) {
+            Ok(program) => program,
+            Err(e) => {
+                renderer_errors.push(RendererError::from(e));
+                return;
+            }
+        };
         device.bind_program(program);
         device.set_uniforms(program, projection);
     }
 
     fn get(&mut self, device: &mut Device) -> Result<&Program, ShaderError> {
         if self.program.is_none() {
             let program = try!{
                 match self.kind {
@@ -803,20 +830,21 @@ impl PrimitiveShader {
             simple,
             transform,
         })
     }
 
     fn bind(&mut self,
             device: &mut Device,
             transform_kind: TransformedRectKind,
-            projection: &Transform3D<f32>) {
+            projection: &Transform3D<f32>,
+            renderer_errors: &mut Vec<RendererError>) {
         match transform_kind {
-            TransformedRectKind::AxisAligned => self.simple.bind(device, projection),
-            TransformedRectKind::Complex => self.transform.bind(device, projection),
+            TransformedRectKind::AxisAligned => self.simple.bind(device, projection, renderer_errors),
+            TransformedRectKind::Complex => self.transform.bind(device, projection, renderer_errors),
         }
     }
 
     fn deinit(self, device: &mut Device) {
         self.simple.deinit(device);
         self.transform.deinit(device);
     }
 }
@@ -849,16 +877,17 @@ fn create_prim_shader(name: &'static str
             ("sColor1", TextureSampler::Color1),
             ("sColor2", TextureSampler::Color2),
             ("sDither", TextureSampler::Dither),
             ("sCacheA8", TextureSampler::CacheA8),
             ("sCacheRGBA8", TextureSampler::CacheRGBA8),
             ("sLayers", TextureSampler::Layers),
             ("sRenderTasks", TextureSampler::RenderTasks),
             ("sResourceCache", TextureSampler::ResourceCache),
+            ("sSharedCacheA8", TextureSampler::SharedCacheA8),
         ]);
     }
 
     program
 }
 
 fn create_clip_shader(name: &'static str, device: &mut Device) -> Result<Program, ShaderError> {
     let prefix = format!("#define WR_MAX_VERTEX_TEXTURE_WIDTH {}\n
@@ -870,16 +899,17 @@ fn create_clip_shader(name: &'static str
     let program = device.create_program(name, &prefix, &DESC_CLIP);
 
     if let Ok(ref program) = program {
         device.bind_shader_samplers(program, &[
             ("sColor0", TextureSampler::Color0),
             ("sLayers", TextureSampler::Layers),
             ("sRenderTasks", TextureSampler::RenderTasks),
             ("sResourceCache", TextureSampler::ResourceCache),
+            ("sSharedCacheA8", TextureSampler::SharedCacheA8),
         ]);
     }
 
     program
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum ReadPixelsFormat {
@@ -978,35 +1008,37 @@ pub struct Renderer {
     texture_cache_upload_pbo: PBOId,
 
     dither_matrix_texture: Option<Texture>,
 
     /// Optional trait object that allows the client
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
+    renderer_errors: Vec<RendererError>,
+
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
 }
 
 #[derive(Debug)]
-pub enum InitError {
+pub enum RendererError {
     Shader(ShaderError),
     Thread(std::io::Error),
     MaxTextureSize,
 }
 
-impl From<ShaderError> for InitError {
-    fn from(err: ShaderError) -> Self { InitError::Shader(err) }
+impl From<ShaderError> for RendererError {
+    fn from(err: ShaderError) -> Self { RendererError::Shader(err) }
 }
 
-impl From<std::io::Error> for InitError {
-    fn from(err: std::io::Error) -> Self { InitError::Thread(err) }
+impl From<std::io::Error> for RendererError {
+    fn from(err: std::io::Error) -> Self { RendererError::Thread(err) }
 }
 
 impl Renderer {
     /// Initializes webrender and creates a `Renderer` and `RenderApiSender`.
     ///
     /// # Examples
     /// Initializes a `Renderer` with some reasonable values. For more information see
     /// [`RendererOptions`][rendereroptions].
@@ -1017,17 +1049,17 @@ impl Renderer {
     /// let opts = webrender::RendererOptions {
     ///    device_pixel_ratio: 1.0,
     ///    resource_override_path: None,
     ///    enable_aa: false,
     /// };
     /// let (renderer, sender) = Renderer::new(opts);
     /// ```
     /// [rendereroptions]: struct.RendererOptions.html
-    pub fn new(gl: Rc<gl::Gl>, mut options: RendererOptions) -> Result<(Renderer, RenderApiSender), InitError> {
+    pub fn new(gl: Rc<gl::Gl>, mut options: RendererOptions) -> Result<(Renderer, RenderApiSender), RendererError> {
 
         let (api_tx, api_rx) = try!{ channel::msg_channel() };
         let (payload_tx, payload_rx) = try!{ channel::payload_channel() };
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
 
         let notifier = Arc::new(Mutex::new(None));
         let debug_server = DebugServer::new(api_tx.clone());
@@ -1045,17 +1077,17 @@ impl Renderer {
 
         let device_max_size = device.max_texture_size();
         // 512 is the minimum that the texture cache can work with.
         // Broken GL contexts can return a max texture size of zero (See #1260). Better to
         // gracefully fail now than panic as soon as a texture is allocated.
         let min_texture_size = 512;
         if device_max_size < min_texture_size {
             println!("Device reporting insufficient max texture size ({})", device_max_size);
-            return Err(InitError::MaxTextureSize);
+            return Err(RendererError::MaxTextureSize);
         }
         let max_device_size = cmp::max(
             cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size)),
             min_texture_size
         );
 
         register_thread_with_profiler("Compositor".to_owned());
 
@@ -1497,16 +1529,17 @@ impl Renderer {
             pipeline_epoch_map: FastHashMap::default(),
             dither_matrix_texture,
             external_image_handler: None,
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             texture_cache_upload_pbo,
             texture_resolver,
+            renderer_errors: Vec::new(),
         };
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
 
     pub fn get_max_texture_size(&self) -> u32 {
         self.max_texture_size
@@ -1706,17 +1739,17 @@ impl Renderer {
         let gpu_profiles = self.gpu_profiles.drain(..).collect();
         (cpu_profiles, gpu_profiles)
     }
 
     /// Renders the current frame.
     ///
     /// A Frame is supplied by calling [`generate_frame()`][genframe].
     /// [genframe]: ../../webrender_api/struct.DocumentApi.html#method.generate_frame
-    pub fn render(&mut self, framebuffer_size: DeviceUintSize) {
+    pub fn render(&mut self, framebuffer_size: DeviceUintSize) -> Result<(), Vec<RendererError>> {
         profile_scope!("render");
 
         if let Some(mut frame) = self.current_frame.take() {
             if let Some(ref mut frame) = frame.frame {
                 let mut profile_timers = RendererProfileTimers::new();
                 let mut profile_samplers = Vec::new();
 
                 {
@@ -1801,16 +1834,21 @@ impl Renderer {
                     self.device.end_frame();
                 }
                 self.last_time = current_time;
             }
 
             // Restore frame - avoid borrow checker!
             self.current_frame = Some(frame);
         }
+        if !self.renderer_errors.is_empty() {
+            let errors = mem::replace(&mut self.renderer_errors, Vec::new());
+            return Err(errors);
+        }
+        Ok(())
     }
 
     pub fn layers_are_bouncing_back(&self) -> bool {
         match self.current_frame {
             None => false,
             Some(ref current_frame) => !current_frame.layers_bouncing_back.is_empty(),
         }
     }
@@ -1948,99 +1986,99 @@ impl Renderer {
                           BlendMode::Alpha |
                           BlendMode::PremultipliedAlpha |
                           BlendMode::Subpixel(..) => true,
                           BlendMode::None => false,
                       });
 
         let marker = match key.kind {
             AlphaBatchKind::Composite { .. } => {
-                self.ps_composite.bind(&mut self.device, projection);
+                self.ps_composite.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_COMPOSITE
             }
             AlphaBatchKind::HardwareComposite => {
-                self.ps_hw_composite.bind(&mut self.device, projection);
+                self.ps_hw_composite.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_HW_COMPOSITE
             }
             AlphaBatchKind::SplitComposite => {
-                self.ps_split_composite.bind(&mut self.device, projection);
+                self.ps_split_composite.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_SPLIT_COMPOSITE
             }
             AlphaBatchKind::Blend => {
-                self.ps_blend.bind(&mut self.device, projection);
+                self.ps_blend.bind(&mut self.device, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BLEND
             }
             AlphaBatchKind::Rectangle => {
                 if needs_clipping {
-                    self.ps_rectangle_clip.bind(&mut self.device, transform_kind, projection);
+                    self.ps_rectangle_clip.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 } else {
-                    self.ps_rectangle.bind(&mut self.device, transform_kind, projection);
+                    self.ps_rectangle.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 }
                 GPU_TAG_PRIM_RECT
             }
             AlphaBatchKind::Line => {
-                self.ps_line.bind(&mut self.device, transform_kind, projection);
+                self.ps_line.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_LINE
             }
             AlphaBatchKind::TextRun => {
                 match key.blend_mode {
                     BlendMode::Subpixel(..) => {
-                        self.ps_text_run_subpixel.bind(&mut self.device, transform_kind, projection);
+                        self.ps_text_run_subpixel.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                     }
                     BlendMode::Alpha |
                     BlendMode::PremultipliedAlpha |
                     BlendMode::None => {
-                        self.ps_text_run.bind(&mut self.device, transform_kind, projection);
+                        self.ps_text_run.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                     }
                 };
                 GPU_TAG_PRIM_TEXT_RUN
             }
             AlphaBatchKind::Image(image_buffer_kind) => {
                 self.ps_image[image_buffer_kind as usize]
                     .as_mut()
                     .expect("Unsupported image shader kind")
-                    .bind(&mut self.device, transform_kind, projection);
+                    .bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_IMAGE
             }
             AlphaBatchKind::YuvImage(image_buffer_kind, format, color_space) => {
                 let shader_index = Renderer::get_yuv_shader_index(image_buffer_kind,
                                                                   format,
                                                                   color_space);
                 self.ps_yuv_image[shader_index]
                     .as_mut()
                     .expect("Unsupported YUV shader kind")
-                    .bind(&mut self.device, transform_kind, projection);
+                    .bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_YUV_IMAGE
             }
             AlphaBatchKind::BorderCorner => {
-                self.ps_border_corner.bind(&mut self.device, transform_kind, projection);
+                self.ps_border_corner.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BORDER_CORNER
             }
             AlphaBatchKind::BorderEdge => {
-                self.ps_border_edge.bind(&mut self.device, transform_kind, projection);
+                self.ps_border_edge.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BORDER_EDGE
             }
             AlphaBatchKind::AlignedGradient => {
-                self.ps_gradient.bind(&mut self.device, transform_kind, projection);
+                self.ps_gradient.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_GRADIENT
             }
             AlphaBatchKind::AngleGradient => {
-                self.ps_angle_gradient.bind(&mut self.device, transform_kind, projection);
+                self.ps_angle_gradient.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_ANGLE_GRADIENT
             }
             AlphaBatchKind::RadialGradient => {
-                self.ps_radial_gradient.bind(&mut self.device, transform_kind, projection);
+                self.ps_radial_gradient.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_RADIAL_GRADIENT
             }
             AlphaBatchKind::BoxShadow => {
-                self.ps_box_shadow.bind(&mut self.device, transform_kind, projection);
+                self.ps_box_shadow.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_BOX_SHADOW
             }
             AlphaBatchKind::CacheImage => {
-                self.ps_cache_image.bind(&mut self.device, transform_kind, projection);
+                self.ps_cache_image.bind(&mut self.device, transform_kind, projection, &mut self.renderer_errors);
                 GPU_TAG_PRIM_CACHE_IMAGE
             }
         };
 
         // Handle special case readback for composites.
         match key.kind {
             AlphaBatchKind::Composite { task_id, source_id, backdrop_id } => {
                 // composites can't be grouped together because
@@ -2146,17 +2184,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_BLUR);
 
             self.device.set_blend(false);
-            self.cs_blur.bind(&mut self.device, projection);
+            self.cs_blur.bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(&target.vertical_blurs,
                                           VertexArrayKind::Blur,
                                           &BatchTextures::no_texture());
             }
 
             if !target.horizontal_blurs.is_empty() {
@@ -2172,29 +2210,29 @@ impl Renderer {
         // considering using this for (some) other text runs, since
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
         if !target.text_run_cache_prims.is_empty() {
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_TEXT_RUN);
-            self.cs_text_run.bind(&mut self.device, projection);
+            self.cs_text_run.bind(&mut self.device, projection, &mut self.renderer_errors);
             self.draw_instanced_batch(&target.text_run_cache_prims,
                                       VertexArrayKind::Primitive,
                                       &target.text_run_textures);
         }
         if !target.line_cache_prims.is_empty() {
             // TODO(gw): Technically, we don't need blend for solid
             //           lines. We could check that here?
             self.device.set_blend(true);
             self.device.set_blend_mode_alpha();
 
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_LINE);
-            self.cs_line.bind(&mut self.device, projection);
+            self.cs_line.bind(&mut self.device, projection, &mut self.renderer_errors);
             self.draw_instanced_batch(&target.line_cache_prims,
                                       VertexArrayKind::Primitive,
                                       &BatchTextures::no_texture());
         }
 
         //TODO: record the pixel count for cached primitives
 
         if !target.alpha_batcher.is_empty() {
@@ -2288,76 +2326,76 @@ impl Renderer {
                                           None,
                                           target.used_rect());
         }
 
         // Draw any box-shadow caches for this target.
         if !target.box_shadow_cache_prims.is_empty() {
             self.device.set_blend(false);
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_BOX_SHADOW);
-            self.cs_box_shadow.bind(&mut self.device, projection);
+            self.cs_box_shadow.bind(&mut self.device, projection, &mut self.renderer_errors);
             self.draw_instanced_batch(&target.box_shadow_cache_prims,
                                       VertexArrayKind::CacheBoxShadow,
                                       &BatchTextures::no_texture());
         }
 
         // Draw the clip items into the tiled alpha mask.
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
 
             // If we have border corner clips, the first step is to clear out the
             // area in the clip mask. This allows drawing multiple invididual clip
             // in regions below.
             if !target.clip_batcher.border_clears.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders [clear]");
                 self.device.set_blend(false);
-                self.cs_clip_border.bind(&mut self.device, projection);
+                self.cs_clip_border.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(&target.clip_batcher.border_clears,
                                           VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
 
             // Draw any dots or dashes for border corners.
             if !target.clip_batcher.borders.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders");
                 // We are masking in parts of the corner (dots or dashes) here.
                 // Blend mode is set to max to allow drawing multiple dots.
                 // The individual dots and dashes in a border never overlap, so using
                 // a max blend mode here is fine.
                 self.device.set_blend(true);
                 self.device.set_blend_mode_max();
-                self.cs_clip_border.bind(&mut self.device, projection);
+                self.cs_clip_border.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(&target.clip_batcher.borders,
                                           VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
 
             // switch to multiplicative blending
             self.device.set_blend(true);
             self.device.set_blend_mode_multiply();
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip rectangles");
-                self.cs_clip_rectangle.bind(&mut self.device, projection);
+                self.cs_clip_rectangle.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(&target.clip_batcher.rectangles,
                                           VertexArrayKind::Clip,
                                           &BatchTextures::no_texture());
             }
             // draw image masks
             for (mask_texture_id, items) in target.clip_batcher.images.iter() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         SourceTexture::Invalid,
                         SourceTexture::Invalid,
                     ]
                 };
-                self.cs_clip_image.bind(&mut self.device, projection);
+                self.cs_clip_image.bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(items,
                                           VertexArrayKind::Clip,
                                           &textures);
             }
         }
 
         self.gpu_profile.done_sampler();
     }
@@ -2482,17 +2520,18 @@ impl Renderer {
         }
 
         self.layer_texture.update(&mut self.device, &mut frame.layer_texture_data);
         self.render_task_texture.update(&mut self.device, &mut frame.render_tasks.task_data);
 
         self.device.bind_texture(TextureSampler::Layers, &self.layer_texture.texture);
         self.device.bind_texture(TextureSampler::RenderTasks, &self.render_task_texture.texture);
 
-        self.texture_resolver.set_cache_textures(None, None);
+        debug_assert!(self.texture_resolver.cache_a8_texture.is_none());
+        debug_assert!(self.texture_resolver.cache_rgba8_texture.is_none());
     }
 
     fn draw_tile_frame(&mut self,
                        frame: &mut Frame,
                        framebuffer_size: &DeviceUintSize) {
         let _gm = GpuMarker::new(self.device.rc_gl(), "tile frame draw");
 
         // Some tests use a restricted viewport smaller than the main screen size.
@@ -2504,18 +2543,19 @@ impl Renderer {
         self.device.disable_depth_write();
         self.device.disable_stencil();
         self.device.set_blend(false);
 
         if frame.passes.is_empty() {
             self.device.clear_target(Some(self.clear_color.to_array()), Some(1.0));
         } else {
             self.start_frame(frame);
-
-            for pass in &mut frame.passes {
+            let pass_count = frame.passes.len();
+
+            for (pass_index, pass) in frame.passes.iter_mut().enumerate() {
                 let size;
                 let clear_color;
                 let projection;
 
                 if pass.is_framebuffer {
                     clear_color = if self.clear_framebuffer || needs_clear {
                         Some(frame.background_color.map_or(self.clear_color.to_array(), |color| {
                             color.to_array()
@@ -2559,35 +2599,30 @@ impl Renderer {
                                            target,
                                            *size,
                                            clear_color,
                                            &frame.render_tasks,
                                            &projection);
 
                 }
 
-                // Return the texture IDs to the pool for next frame.
-                if let Some(texture) = self.texture_resolver.cache_rgba8_texture.take() {
-                    self.color_render_targets.push(texture);
-                }
-                if let Some(texture) = self.texture_resolver.cache_a8_texture.take() {
-                    self.alpha_render_targets.push(texture);
+                self.texture_resolver.end_pass(pass_index,
+                                               pass_count,
+                                               pass.alpha_texture.take(),
+                                               pass.color_texture.take(),
+                                               &mut self.alpha_render_targets,
+                                               &mut self.color_render_targets);
+
+                // After completing the first pass, make the A8 target available as an
+                // input to any subsequent passes.
+                if pass_index == 0 {
+                    if let Some(shared_alpha_texture) = self.texture_resolver.resolve(&SourceTexture::CacheA8) {
+                        self.device.bind_texture(TextureSampler::SharedCacheA8, shared_alpha_texture);
+                    }
                 }
-
-                self.texture_resolver.set_cache_textures(pass.alpha_texture.take(),
-                                                         pass.color_texture.take());
-
-            }
-
-            // Return the texture IDs to the pool for next frame.
-            if let Some(texture) = self.texture_resolver.cache_rgba8_texture.take() {
-                self.color_render_targets.push(texture);
-            }
-            if let Some(texture) = self.texture_resolver.cache_a8_texture.take() {
-                self.alpha_render_targets.push(texture);
             }
 
             self.color_render_targets.reverse();
             self.alpha_render_targets.reverse();
             self.draw_render_target_debug(framebuffer_size);
             self.draw_texture_cache_debug(framebuffer_size);
         }
 
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -621,17 +621,17 @@ impl DisplayListBuilder {
             self.cache_glyphs(font_key, color, split_glyphs.iter().map(|glyph| glyph.index));
         }
     }
 
     fn cache_glyphs<I: Iterator<Item=GlyphIndex>>(&mut self,
                                                   font_key: FontInstanceKey,
                                                   color: ColorF,
                                                   glyphs: I) {
-        let mut font_glyphs = self.glyphs.entry((font_key, color))
+        let font_glyphs = self.glyphs.entry((font_key, color))
                                          .or_insert(FastHashSet::default());
 
         font_glyphs.extend(glyphs);
     }
 
     // Gradients can be defined with stops outside the range of [0, 1]
     // when this happens the gradient needs to be normalized by adjusting
     // the gradient stops and gradient line into an equivalent gradient
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -169,17 +169,19 @@ RendererOGL::Render()
 
   auto size = mWidget->GetClientSize();
 
   if (mSyncObject) {
     // XXX: if the synchronization is failed, we should handle the device reset.
     mSyncObject->Synchronize();
   }
 
-  wr_renderer_render(mRenderer, size.width, size.height);
+  if (!wr_renderer_render(mRenderer, size.width, size.height)) {
+    NotifyWebRenderError(WebRenderError::RENDER);
+  }
 
   mGL->SwapBuffers();
   mWidget->PostRender(&widgetContext);
 
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   if (mFrameStartTime) {
     uint32_t latencyMs = round((TimeStamp::Now() - mFrameStartTime).ToMilliseconds());
     printf_stderr("generate frame latencyMs latencyMs %d\n", latencyMs);
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -698,16 +698,17 @@ struct WrClipId {
   }
 };
 
 typedef Variant<layers::FrameMetrics::ViewID, WrClipId> ScrollOrClipId;
 
 enum class WebRenderError : int8_t {
   INITIALIZE = 0,
   MAKE_CURRENT,
+  RENDER,
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
 } // namespace wr
 } // namespace mozilla
 
 #endif /* GFX_WEBRENDERTYPES_H */
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -438,18 +438,30 @@ pub extern "C" fn wr_renderer_set_extern
 #[no_mangle]
 pub extern "C" fn wr_renderer_update(renderer: &mut Renderer) {
     renderer.update();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_renderer_render(renderer: &mut Renderer,
                                      width: u32,
-                                     height: u32) {
-    renderer.render(DeviceUintSize::new(width, height));
+                                     height: u32) -> bool {
+    match renderer.render(DeviceUintSize::new(width, height)) {
+        Ok(()) => true,
+        Err(errors) => {
+            for e in errors {
+                println!(" Failed to render: {:?}", e);
+                let msg = CString::new(format!("wr_renderer_render: {:?}", e)).unwrap();
+                unsafe {
+                    gfx_critical_note(msg.as_ptr());
+               }
+            }
+            false
+        },
+    }
 }
 
 // Call wr_renderer_render() before calling this function.
 #[no_mangle]
 pub unsafe extern "C" fn wr_renderer_readback(renderer: &mut Renderer,
                                               width: u32,
                                               height: u32,
                                               dst_buffer: *mut u8,
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -1140,17 +1140,17 @@ WR_INLINE
 void wr_renderer_readback(Renderer *aRenderer,
                           uint32_t aWidth,
                           uint32_t aHeight,
                           uint8_t *aDstBuffer,
                           size_t aBufferSize)
 WR_FUNC;
 
 WR_INLINE
-void wr_renderer_render(Renderer *aRenderer,
+bool wr_renderer_render(Renderer *aRenderer,
                         uint32_t aWidth,
                         uint32_t aHeight)
 WR_FUNC;
 
 WR_INLINE
 void wr_renderer_set_debug_flags(Renderer *aRenderer,
                                  WrDebugFlags aFlags)
 WR_FUNC;
--- a/image/DecodedSurfaceProvider.cpp
+++ b/image/DecodedSurfaceProvider.cpp
@@ -195,16 +195,22 @@ DecodedSurfaceProvider::FinishDecoding()
 {
   mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mImage);
   MOZ_ASSERT(mDecoder);
 
   // Send notifications.
   NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
 
+  // If we have a new and complete surface, we can try to prune similarly sized
+  // surfaces if the cache supports it.
+  if (mSurface && mSurface->IsFinished()) {
+    SurfaceCache::PruneImage(ImageKey(mImage));
+  }
+
   // Destroy our decoder; we don't need it anymore. (And if we don't destroy it,
   // our surface can never be optimized, because the decoder has a
   // RawAccessFrameRef to it.)
   mDecoder = nullptr;
 
   // We don't need a reference to our image anymore, either, and we don't want
   // one. We may be stored in the surface cache for a long time after decoding
   // finishes. If we don't drop our reference to the image, we'll end up
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -140,16 +140,19 @@ DecoderFactory::CreateDecoder(DecoderTyp
   // Create a DecodedSurfaceProvider which will manage the decoding process and
   // make this decoder's output available in the surface cache.
   SurfaceKey surfaceKey =
     RasterSurfaceKey(aOutputSize, aSurfaceFlags, PlaybackType::eStatic);
   NotNull<RefPtr<DecodedSurfaceProvider>> provider =
     WrapNotNull(new DecodedSurfaceProvider(aImage,
                                            surfaceKey,
                                            WrapNotNull(decoder)));
+  if (aDecoderFlags & DecoderFlags::CANNOT_SUBSTITUTE) {
+    provider->Availability().SetCannotSubstitute();
+  }
 
   // Attempt to insert the surface provider into the surface cache right away so
   // we won't trigger any more decoders with the same parameters.
   if (SurfaceCache::Insert(provider) != InsertOutcome::SUCCESS) {
     return nullptr;
   }
 
   // Return the surface provider in its IDecodingTask guise.
--- a/image/DecoderFlags.h
+++ b/image/DecoderFlags.h
@@ -18,17 +18,25 @@ namespace image {
  * instead either influence which surfaces are generated at all or the tune the
  * decoder's behavior for a particular scenario.
  */
 enum class DecoderFlags : uint8_t
 {
   FIRST_FRAME_ONLY               = 1 << 0,
   IS_REDECODE                    = 1 << 1,
   IMAGE_IS_TRANSIENT             = 1 << 2,
-  ASYNC_NOTIFY                   = 1 << 3
+  ASYNC_NOTIFY                   = 1 << 3,
+
+  /**
+   * By default, a surface is considered substitutable. That means callers are
+   * willing to accept a less than ideal match to display. If a caller requires
+   * a specific size and won't accept alternatives, then this flag should be
+   * set.
+   */
+  CANNOT_SUBSTITUTE              = 1 << 4
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags)
 
 /**
  * @return the default set of decode flags.
  */
 inline DecoderFlags
 DefaultDecoderFlags()
--- a/image/DynamicImage.cpp
+++ b/image/DynamicImage.cpp
@@ -129,16 +129,22 @@ DynamicImage::GetHeight(int32_t* aHeight
 }
 
 nsresult
 DynamicImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+size_t
+DynamicImage::GetNativeSizesLength() const
+{
+  return 0;
+}
+
 NS_IMETHODIMP
 DynamicImage::GetIntrinsicSize(nsSize* aSize)
 {
   IntSize intSize(mDrawable->Size());
   *aSize = nsSize(intSize.width, intSize.height);
   return NS_OK;
 }
 
--- a/image/DynamicImage.h
+++ b/image/DynamicImage.h
@@ -27,16 +27,17 @@ public:
   explicit DynamicImage(gfxDrawable* aDrawable)
     : mDrawable(aDrawable)
   {
     MOZ_ASSERT(aDrawable, "Must have a gfxDrawable to wrap");
   }
 
   // Inherited methods from Image.
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
+  size_t GetNativeSizesLength() const override;
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
   virtual size_t SizeOfSourceWithComputedFallback(
                                  SizeOfState& aState) const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
   virtual void DecrementAnimationConsumers() override;
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -525,17 +525,19 @@ DoCollectSizeOfCompositingSurfaces(const
                                    MallocSizeOf aMallocSizeOf)
 {
   // Concoct a SurfaceKey for this surface.
   SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
                                     DefaultSurfaceFlags(),
                                     PlaybackType::eStatic);
 
   // Create a counter for this surface.
-  SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
+  SurfaceMemoryCounter counter(key, /* aIsLocked = */ true,
+                               /* aCannotSubstitute */ false,
+                               /* aIsFactor2 */ false, aType);
 
   // Extract the surface's memory usage information.
   size_t heap = 0, nonHeap = 0, handles = 0;
   aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap, handles);
   counter.Values().SetDecodedHeap(heap);
   counter.Values().SetDecodedNonHeap(nonHeap);
   counter.Values().SetSharedHandles(handles);
 
--- a/image/Image.h
+++ b/image/Image.h
@@ -67,34 +67,42 @@ enum class SurfaceMemoryCounterType
   COMPOSITING,
   COMPOSITING_PREV
 };
 
 struct SurfaceMemoryCounter
 {
   SurfaceMemoryCounter(const SurfaceKey& aKey,
                        bool aIsLocked,
+                       bool aCannotSubstitute,
+                       bool aIsFactor2,
                        SurfaceMemoryCounterType aType =
                          SurfaceMemoryCounterType::NORMAL)
     : mKey(aKey)
     , mType(aType)
     , mIsLocked(aIsLocked)
+    , mCannotSubstitute(aCannotSubstitute)
+    , mIsFactor2(aIsFactor2)
   { }
 
   const SurfaceKey& Key() const { return mKey; }
   MemoryCounter& Values() { return mValues; }
   const MemoryCounter& Values() const { return mValues; }
   SurfaceMemoryCounterType Type() const { return mType; }
   bool IsLocked() const { return mIsLocked; }
+  bool CannotSubstitute() const { return mCannotSubstitute; }
+  bool IsFactor2() const { return mIsFactor2; }
 
 private:
   const SurfaceKey mKey;
   MemoryCounter mValues;
   const SurfaceMemoryCounterType mType;
   const bool mIsLocked;
+  const bool mCannotSubstitute;
+  const bool mIsFactor2;
 };
 
 struct ImageMemoryCounter
 {
   ImageMemoryCounter(Image* aImage, SizeOfState& aState, bool aIsUsed);
 
   nsCString& URI() { return mURI; }
   const nsCString& URI() const { return mURI; }
--- a/image/ImageWrapper.cpp
+++ b/image/ImageWrapper.cpp
@@ -140,16 +140,22 @@ ImageWrapper::GetHeight(int32_t* aHeight
 }
 
 nsresult
 ImageWrapper::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
 {
   return mInnerImage->GetNativeSizes(aNativeSizes);
 }
 
+size_t
+ImageWrapper::GetNativeSizesLength() const
+{
+  return mInnerImage->GetNativeSizesLength();
+}
+
 NS_IMETHODIMP
 ImageWrapper::GetIntrinsicSize(nsSize* aSize)
 {
   return mInnerImage->GetIntrinsicSize(aSize);
 }
 
 NS_IMETHODIMP
 ImageWrapper::GetIntrinsicRatio(nsSize* aSize)
--- a/image/ImageWrapper.h
+++ b/image/ImageWrapper.h
@@ -18,16 +18,17 @@ namespace image {
 class ImageWrapper : public Image
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGICONTAINER
 
   // Inherited methods from Image.
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
+  size_t GetNativeSizesLength() const override;
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
 
   virtual size_t
     SizeOfSourceWithComputedFallback(SizeOfState& aState) const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
--- a/image/LookupResult.h
+++ b/image/LookupResult.h
@@ -7,30 +7,38 @@
  * LookupResult is the return type of SurfaceCache's Lookup*() functions. It
  * combines a surface with relevant metadata tracked by SurfaceCache.
  */
 
 #ifndef mozilla_image_LookupResult_h
 #define mozilla_image_LookupResult_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/gfx/Point.h"  // for IntSize
 #include "mozilla/Move.h"
 #include "ISurfaceProvider.h"
 
 namespace mozilla {
 namespace image {
 
 enum class MatchType : uint8_t
 {
   NOT_FOUND,  // No matching surface and no placeholder.
   PENDING,    // Found a matching placeholder, but no surface.
   EXACT,      // Found a surface that matches exactly.
   SUBSTITUTE_BECAUSE_NOT_FOUND,  // No exact match, but found a similar one.
-  SUBSTITUTE_BECAUSE_PENDING     // Found a similar surface and a placeholder
+  SUBSTITUTE_BECAUSE_PENDING,    // Found a similar surface and a placeholder
                                  // for an exact match.
+
+  /* No exact match, but this should be considered an exact match for purposes
+   * of deciding whether or not to request a new decode. This is because the
+   * cache has determined that callers require too many size variants of this
+   * image. It determines the set of sizes which best represent the image, and
+   * will only suggest decoding of unavailable sizes from that set. */
+  SUBSTITUTE_BECAUSE_BEST
 };
 
 /**
  * LookupResult is the return type of SurfaceCache's Lookup*() functions. It
  * combines a surface with relevant metadata tracked by SurfaceCache.
  */
 class MOZ_STACK_CLASS LookupResult
 {
@@ -55,36 +63,55 @@ public:
     MOZ_ASSERT(!mSurface || !(mMatchType == MatchType::NOT_FOUND ||
                               mMatchType == MatchType::PENDING),
                "Only NOT_FOUND or PENDING make sense with no surface");
     MOZ_ASSERT(mSurface || mMatchType == MatchType::NOT_FOUND ||
                            mMatchType == MatchType::PENDING,
                "NOT_FOUND or PENDING do not make sense with a surface");
   }
 
+  LookupResult(DrawableSurface&& aSurface, MatchType aMatchType,
+               const gfx::IntSize& aSuggestedSize)
+    : mSurface(Move(aSurface))
+    , mMatchType(aMatchType)
+    , mSuggestedSize(aSuggestedSize)
+  {
+    MOZ_ASSERT(!mSuggestedSize.IsEmpty());
+    MOZ_ASSERT(!mSurface || aMatchType == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND,
+               "Only SUBSTITUTE_BECAUSE_NOT_FOUND make sense with no surface");
+    MOZ_ASSERT(mSurface || aMatchType == MatchType::NOT_FOUND,
+               "NOT_FOUND does not make sense with a surface");
+  }
+
   LookupResult& operator=(LookupResult&& aOther)
   {
     MOZ_ASSERT(&aOther != this, "Self-move-assignment is not supported");
     mSurface = Move(aOther.mSurface);
     mMatchType = aOther.mMatchType;
+    mSuggestedSize = aOther.mSuggestedSize;
     return *this;
   }
 
   DrawableSurface& Surface() { return mSurface; }
   const DrawableSurface& Surface() const { return mSurface; }
+  const gfx::IntSize& SuggestedSize() const { return mSuggestedSize; }
 
   /// @return true if this LookupResult contains a surface.
   explicit operator bool() const { return bool(mSurface); }
 
   /// @return what kind of match this is (exact, substitute, etc.)
   MatchType Type() const { return mMatchType; }
 
 private:
   LookupResult(const LookupResult&) = delete;
 
   DrawableSurface mSurface;
   MatchType mMatchType;
+
+  /// If given, the size the caller should request a decode at. This may or may
+  /// not match the size the caller requested from the cache.
+  gfx::IntSize mSuggestedSize;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_LookupResult_h
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -237,16 +237,31 @@ RasterImage::GetNativeSizes(nsTArray<Int
   } else {
     aNativeSizes = mNativeSizes;
   }
 
   return NS_OK;
 }
 
 //******************************************************************************
+size_t
+RasterImage::GetNativeSizesLength() const
+{
+  if (mError || !mHasSize) {
+    return 0;
+  }
+
+  if (mNativeSizes.IsEmpty()) {
+    return 1;
+  }
+
+  return mNativeSizes.Length();
+}
+
+//******************************************************************************
 NS_IMETHODIMP
 RasterImage::GetIntrinsicSize(nsSize* aSize)
 {
   if (mError) {
     return NS_ERROR_FAILURE;
   }
 
   *aSize = nsSize(nsPresContext::CSSPixelsToAppUnits(mSize.width),
@@ -346,16 +361,24 @@ RasterImage::LookupFrame(const IntSize& 
     // We don't have a copy of this frame, and there's no decoder working on
     // one. (Or we're sync decoding and the existing decoder hasn't even started
     // yet.) Trigger decoding so it'll be available next time.
     MOZ_ASSERT(aPlaybackType != PlaybackType::eAnimated ||
                gfxPrefs::ImageMemAnimatedDiscardable() ||
                !mAnimationState || mAnimationState->KnownFrameCount() < 1,
                "Animated frames should be locked");
 
+    // The surface cache may suggest the preferred size we are supposed to
+    // decode at. This should only happen if we accept substitutions.
+    if (!result.SuggestedSize().IsEmpty()) {
+      MOZ_ASSERT(!(aFlags & FLAG_SYNC_DECODE) &&
+                  (aFlags & FLAG_HIGH_QUALITY_SCALING));
+      requestedSize = result.SuggestedSize();
+    }
+
     bool ranSync = Decode(requestedSize, aFlags, aPlaybackType);
 
     // If we can or did sync decode, we should already have the frame.
     if (ranSync || (aFlags & FLAG_SYNC_DECODE)) {
       result = LookupFrameInternal(requestedSize, aFlags, aPlaybackType);
     }
   }
 
@@ -1258,16 +1281,22 @@ RasterImage::Decode(const IntSize& aSize
     decoderFlags |= DecoderFlags::ASYNC_NOTIFY;
   }
   if (mTransient) {
     decoderFlags |= DecoderFlags::IMAGE_IS_TRANSIENT;
   }
   if (mHasBeenDecoded) {
     decoderFlags |= DecoderFlags::IS_REDECODE;
   }
+  if ((aFlags & FLAG_SYNC_DECODE) || !(aFlags & FLAG_HIGH_QUALITY_SCALING)) {
+    // Used SurfaceCache::Lookup instead of SurfaceCache::LookupBestMatch. That
+    // means the caller can handle a differently sized surface to be returned
+    // at any point.
+    decoderFlags |= DecoderFlags::CANNOT_SUBSTITUTE;
+  }
 
   SurfaceFlags surfaceFlags = ToSurfaceFlags(aFlags);
   if (IsOpaque()) {
     // If there's no transparency, it doesn't matter whether we premultiply
     // alpha or not.
     surfaceFlags &= ~SurfaceFlags::NO_PREMULTIPLY_ALPHA;
   }
 
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -156,16 +156,17 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIPROPERTIES
   NS_DECL_IMGICONTAINER
 #ifdef DEBUG
   NS_DECL_IMGICONTAINERDEBUG
 #endif
 
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
+  size_t GetNativeSizesLength() const override;
   virtual nsresult StartAnimation() override;
   virtual nsresult StopAnimation() override;
 
   // Methods inherited from Image
   virtual void OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) override;
 
   virtual size_t SizeOfSourceWithComputedFallback(SizeOfState& aState)
     const override;
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -156,16 +156,19 @@ public:
     mProvider->SetLocked(aLocked);
   }
 
   bool IsLocked() const
   {
     return !IsPlaceholder() && mIsLocked && mProvider->IsLocked();
   }
 
+  void SetCannotSubstitute() { mProvider->Availability().SetCannotSubstitute(); }
+  bool CannotSubstitute() const { return mProvider->Availability().CannotSubstitute(); }
+
   bool IsPlaceholder() const { return mProvider->Availability().IsPlaceholder(); }
   bool IsDecoded() const { return !IsPlaceholder() && mProvider->IsFinished(); }
 
   ImageKey GetImageKey() const { return mProvider->GetImageKey(); }
   SurfaceKey GetSurfaceKey() const { return mProvider->GetSurfaceKey(); }
   nsExpirationState* GetExpirationState() { return &mExpirationState; }
 
   CostEntry GetCostEntry()
@@ -177,20 +180,22 @@ public:
   struct MOZ_STACK_CLASS SurfaceMemoryReport
   {
     SurfaceMemoryReport(nsTArray<SurfaceMemoryCounter>& aCounters,
                         MallocSizeOf                    aMallocSizeOf)
       : mCounters(aCounters)
       , mMallocSizeOf(aMallocSizeOf)
     { }
 
-    void Add(NotNull<CachedSurface*> aCachedSurface)
+    void Add(NotNull<CachedSurface*> aCachedSurface, bool aIsFactor2)
     {
       SurfaceMemoryCounter counter(aCachedSurface->GetSurfaceKey(),
-                                   aCachedSurface->IsLocked());
+                                   aCachedSurface->IsLocked(),
+                                   aCachedSurface->CannotSubstitute(),
+                                   aIsFactor2);
 
       if (aCachedSurface->IsPlaceholder()) {
         return;
       }
 
       // Record the memory used by the ISurfaceProvider. This may not have a
       // straightforward relationship to the size of the surface that
       // DrawableRef() returns if the surface is generated dynamically. (i.e.,
@@ -226,22 +231,36 @@ AreaOfIntSize(const IntSize& aSize) {
 /**
  * An ImageSurfaceCache is a per-image surface cache. For correctness we must be
  * able to remove all surfaces associated with an image when the image is
  * destroyed or invalidated. Since this will happen frequently, it makes sense
  * to make it cheap by storing the surfaces for each image separately.
  *
  * ImageSurfaceCache also keeps track of whether its associated image is locked
  * or unlocked.
+ *
+ * The cache may also enter "factor of 2" mode which occurs when the number of
+ * surfaces in the cache exceeds the "image.cache.factor2.threshold-surfaces"
+ * pref plus the number of native sizes of the image. When in "factor of 2"
+ * mode, the cache will strongly favour sizes which are a factor of 2 of the
+ * largest native size. It accomplishes this by suggesting a factor of 2 size
+ * when lookups fail and substituting the nearest factor of 2 surface to the
+ * ideal size as the "best" available (as opposed to subsitution but not found).
+ * This allows us to minimize memory consumption and CPU time spent decoding
+ * when a website requires many variants of the same surface.
  */
 class ImageSurfaceCache
 {
   ~ImageSurfaceCache() { }
 public:
-  ImageSurfaceCache() : mLocked(false) { }
+  ImageSurfaceCache()
+    : mLocked(false)
+    , mFactor2Mode(false)
+    , mFactor2Pruned(false)
+  { }
 
   MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache)
 
   typedef
     nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
 
   bool IsEmpty() const { return mSurfaces.Count() == 0; }
@@ -258,31 +277,73 @@ public:
     MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()),
         "Should not be removing a surface we don't have");
 
     RefPtr<CachedSurface> surface;
     mSurfaces.Remove(aSurface->GetSurfaceKey(), getter_AddRefs(surface));
     return surface.forget();
   }
 
-  already_AddRefed<CachedSurface> Lookup(const SurfaceKey& aSurfaceKey)
+  already_AddRefed<CachedSurface> Lookup(const SurfaceKey& aSurfaceKey,
+                                         bool aForAccess)
   {
     RefPtr<CachedSurface> surface;
     mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface));
+
+    if (aForAccess) {
+      if (surface) {
+        // We don't want to allow factor of 2 mode pruning to release surfaces
+        // for which the callers will accept no substitute.
+        surface->SetCannotSubstitute();
+      } else if (!mFactor2Mode) {
+        // If no exact match is found, and this is for use rather than internal
+        // accounting (i.e. insert and removal), we know this will trigger a
+        // decode. Make sure we switch now to factor of 2 mode if necessary.
+        MaybeSetFactor2Mode();
+      }
+    }
+
     return surface.forget();
   }
 
-  Pair<already_AddRefed<CachedSurface>, MatchType>
+  /**
+   * @returns A tuple containing the best matching CachedSurface if available,
+   *          a MatchType describing how the CachedSurface was selected, and
+   *          an IntSize which is the size the caller should choose to decode
+   *          at should it attempt to do so.
+   */
+  Tuple<already_AddRefed<CachedSurface>, MatchType, IntSize>
   LookupBestMatch(const SurfaceKey& aIdealKey)
   {
     // Try for an exact match first.
     RefPtr<CachedSurface> exactMatch;
     mSurfaces.Get(aIdealKey, getter_AddRefs(exactMatch));
-    if (exactMatch && exactMatch->IsDecoded()) {
-      return MakePair(exactMatch.forget(), MatchType::EXACT);
+    if (exactMatch) {
+      if (exactMatch->IsDecoded()) {
+        return MakeTuple(exactMatch.forget(), MatchType::EXACT, IntSize());
+      }
+    } else if (!mFactor2Mode) {
+      // If no exact match is found, and we are not in factor of 2 mode, then
+      // we know that we will trigger a decode because at best we will provide
+      // a substitute. Make sure we switch now to factor of 2 mode if necessary.
+      MaybeSetFactor2Mode();
+    }
+
+    // Try for a best match second, if using compact.
+    IntSize suggestedSize = SuggestedSize(aIdealKey.Size());
+    if (mFactor2Mode) {
+      if (!exactMatch) {
+        SurfaceKey compactKey = aIdealKey.CloneWithSize(suggestedSize);
+        mSurfaces.Get(compactKey, getter_AddRefs(exactMatch));
+        if (exactMatch && exactMatch->IsDecoded()) {
+          return MakeTuple(exactMatch.forget(),
+                           MatchType::SUBSTITUTE_BECAUSE_BEST,
+                           suggestedSize);
+        }
+      }
     }
 
     // There's no perfect match, so find the best match we can.
     RefPtr<CachedSurface> bestMatch;
     for (auto iter = ConstIter(); !iter.Done(); iter.Next()) {
       NotNull<CachedSurface*> current = WrapNotNull(iter.UserData());
       const SurfaceKey& currentKey = current->GetSurfaceKey();
 
@@ -314,76 +375,270 @@ public:
         continue;
       }
       if (!bestMatchIsDecoded && current->IsDecoded()) {
         bestMatch = current;
         continue;
       }
 
       SurfaceKey bestMatchKey = bestMatch->GetSurfaceKey();
-
-      // Compare sizes. We use an area-based heuristic here instead of computing a
-      // truly optimal answer, since it seems very unlikely to make a difference
-      // for realistic sizes.
-      int64_t idealArea = AreaOfIntSize(aIdealKey.Size());
-      int64_t currentArea = AreaOfIntSize(currentKey.Size());
-      int64_t bestMatchArea = AreaOfIntSize(bestMatchKey.Size());
-
-      // If the best match is smaller than the ideal size, prefer bigger sizes.
-      if (bestMatchArea < idealArea) {
-        if (currentArea > bestMatchArea) {
-          bestMatch = current;
-        }
-        continue;
+      if (CompareArea(aIdealKey.Size(), bestMatchKey.Size(),
+                      currentKey.Size())) {
+        bestMatch = current;
       }
-      // Other, prefer sizes closer to the ideal size, but still not smaller.
-      if (idealArea <= currentArea && currentArea < bestMatchArea) {
-        bestMatch = current;
-        continue;
-      }
-      // This surface isn't an improvement over the current best match.
     }
 
     MatchType matchType;
     if (bestMatch) {
       if (!exactMatch) {
-        // No exact match, but we found a substitute.
-        matchType = MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND;
+        const IntSize& bestMatchSize = bestMatch->GetSurfaceKey().Size();
+        if (mFactor2Mode && suggestedSize == bestMatchSize) {
+          // No exact match, but this is the best we are willing to decode.
+          matchType = MatchType::SUBSTITUTE_BECAUSE_BEST;
+        } else {
+          // No exact match, but we found a substitute.
+          matchType = MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND;
+        }
       } else if (exactMatch != bestMatch) {
         // The exact match is still decoding, but we found a substitute.
         matchType = MatchType::SUBSTITUTE_BECAUSE_PENDING;
       } else {
-        // The exact match is still decoding, but it's the best we've got.
-        matchType = MatchType::EXACT;
+        const IntSize& bestMatchSize = bestMatch->GetSurfaceKey().Size();
+        if (mFactor2Mode && aIdealKey.Size() != bestMatchSize) {
+          // The best factor of 2 match is still decoding.
+          matchType = MatchType::SUBSTITUTE_BECAUSE_BEST;
+        } else {
+          // The exact match is still decoding, but it's the best we've got.
+          matchType = MatchType::EXACT;
+        }
       }
     } else {
       if (exactMatch) {
         // We found an "exact match"; it must have been a placeholder.
         MOZ_ASSERT(exactMatch->IsPlaceholder());
         matchType = MatchType::PENDING;
       } else {
         // We couldn't find an exact match *or* a substitute.
         matchType = MatchType::NOT_FOUND;
       }
     }
 
-    return MakePair(bestMatch.forget(), matchType);
+    return MakeTuple(bestMatch.forget(), matchType, suggestedSize);
+  }
+
+  void MaybeSetFactor2Mode()
+  {
+    MOZ_ASSERT(!mFactor2Mode);
+
+    // Typically an image cache will not have too many size-varying surfaces, so
+    // if we exceed the given threshold, we should consider using a subset.
+    int32_t thresholdSurfaces = gfxPrefs::ImageCacheFactor2ThresholdSurfaces();
+    if (thresholdSurfaces < 0 ||
+        mSurfaces.Count() <= static_cast<uint32_t>(thresholdSurfaces)) {
+      return;
+    }
+
+    // Determine how many native surfaces this image has. Zero means we either
+    // don't know yet (in which case do nothing), or we don't want to limit the
+    // number of surfaces for this image.
+    //
+    // XXX(aosmond): Vector images have zero native sizes. This is because they
+    // are regenerated at the given size. There isn't an equivalent concept to
+    // the native size (and w/h ratio) to provide a frame of reference to what
+    // are "good" sizes. While it is desirable to have a similar mechanism as
+    // that for raster images, it will need a different approach.
+    auto first = ConstIter();
+    NotNull<CachedSurface*> current = WrapNotNull(first.UserData());
+    Image* image = static_cast<Image*>(current->GetImageKey());
+    size_t nativeSizes = image->GetNativeSizesLength();
+    if (nativeSizes == 0) {
+      return;
+    }
+
+    // Increase the threshold by the number of native sizes. This ensures that
+    // we do not prevent decoding of the image at all its native sizes. It does
+    // not guarantee we will provide a surface at that size however (i.e. many
+    // other sized surfaces are requested, in addition to the native sizes).
+    thresholdSurfaces += nativeSizes;
+    if (mSurfaces.Count() <= static_cast<uint32_t>(thresholdSurfaces)) {
+      return;
+    }
+
+    // Get our native size. While we know the image should be fully decoded,
+    // if it is an SVG, it is valid to have a zero size. We can't do compacting
+    // in that case because we need to know the width/height ratio to define a
+    // candidate set.
+    IntSize nativeSize;
+    if (NS_FAILED(image->GetWidth(&nativeSize.width)) ||
+        NS_FAILED(image->GetHeight(&nativeSize.height)) ||
+        nativeSize.IsEmpty()) {
+      return;
+    }
+
+    // We have a valid size, we can change modes.
+    mFactor2Mode = true;
+  }
+
+  template<typename Function>
+  void Prune(Function&& aRemoveCallback)
+  {
+    if (!mFactor2Mode || mFactor2Pruned) {
+      return;
+    }
+
+    // Attempt to discard any surfaces which are not factor of 2 and the best
+    // factor of 2 match exists.
+    bool hasNotFactorSize = false;
+    for (auto iter = mSurfaces.Iter(); !iter.Done(); iter.Next()) {
+      NotNull<CachedSurface*> current = WrapNotNull(iter.UserData());
+      const SurfaceKey& currentKey = current->GetSurfaceKey();
+      const IntSize& currentSize = currentKey.Size();
+
+      // First we check if someone requested this size and would not accept
+      // an alternatively sized surface.
+      if (current->CannotSubstitute()) {
+        continue;
+      }
+
+      // Next we find the best factor of 2 size for this surface. If this
+      // surface is a factor of 2 size, then we want to keep it.
+      IntSize bestSize = SuggestedSize(currentSize);
+      if (bestSize == currentSize) {
+        continue;
+      }
+
+      // Check the cache for a surface with the same parameters except for the
+      // size which uses the closest factor of 2 size.
+      SurfaceKey compactKey = currentKey.CloneWithSize(bestSize);
+      RefPtr<CachedSurface> compactMatch;
+      mSurfaces.Get(compactKey, getter_AddRefs(compactMatch));
+      if (compactMatch && compactMatch->IsDecoded()) {
+        aRemoveCallback(current);
+        iter.Remove();
+      } else {
+        hasNotFactorSize = true;
+      }
+    }
+
+    // We have no surfaces that are not factor of 2 sized, so we can stop
+    // pruning henceforth, because we avoid the insertion of new surfaces that
+    // don't match our sizing set (unless the caller won't accept a
+    // substitution.)
+    if (!hasNotFactorSize) {
+      mFactor2Pruned = true;
+    }
+  }
+
+  IntSize SuggestedSize(const IntSize& aSize) const
+  {
+    // When not in factor of 2 mode, we can always decode at the given size.
+    if (!mFactor2Mode) {
+      return aSize;
+    }
+
+    MOZ_ASSERT(!IsEmpty());
+
+    // This bit of awkwardness gets the largest native size of the image.
+    auto iter = ConstIter();
+    NotNull<CachedSurface*> firstSurface = WrapNotNull(iter.UserData());
+    Image* image = static_cast<Image*>(firstSurface->GetImageKey());
+    IntSize factorSize;
+    if (NS_FAILED(image->GetWidth(&factorSize.width)) ||
+        NS_FAILED(image->GetHeight(&factorSize.height)) ||
+        factorSize.IsEmpty()) {
+      // We should not have entered factor of 2 mode without a valid size, and
+      // several successfully decoded surfaces.
+      MOZ_ASSERT_UNREACHABLE("Expected valid native size!");
+      return aSize;
+    }
+
+    // Start with the native size as the best first guess.
+    IntSize bestSize = factorSize;
+    factorSize.width /= 2;
+    factorSize.height /= 2;
+
+    while (!factorSize.IsEmpty()) {
+      if (!CompareArea(aSize, bestSize, factorSize)) {
+        // This size is not better than the last. Since we proceed from largest
+        // to smallest, we know that the next size will not be better if the
+        // previous size was rejected. Break early.
+        break;
+      }
+
+      // The current factor of 2 size is better than the last selected size.
+      bestSize = factorSize;
+      factorSize.width /= 2;
+      factorSize.height /= 2;
+    }
+
+    return bestSize;
+  }
+
+  bool CompareArea(const IntSize& aIdealSize,
+                   const IntSize& aBestSize,
+                   const IntSize& aSize) const
+  {
+    // Compare sizes. We use an area-based heuristic here instead of computing a
+    // truly optimal answer, since it seems very unlikely to make a difference
+    // for realistic sizes.
+    int64_t idealArea = AreaOfIntSize(aIdealSize);
+    int64_t currentArea = AreaOfIntSize(aSize);
+    int64_t bestMatchArea = AreaOfIntSize(aBestSize);
+
+    // If the best match is smaller than the ideal size, prefer bigger sizes.
+    if (bestMatchArea < idealArea) {
+      if (currentArea > bestMatchArea) {
+        return true;
+      }
+      return false;
+    }
+
+    // Other, prefer sizes closer to the ideal size, but still not smaller.
+    if (idealArea <= currentArea && currentArea < bestMatchArea) {
+      return true;
+    }
+
+    // This surface isn't an improvement over the current best match.
+    return false;
+  }
+
+  void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                             MallocSizeOf                    aMallocSizeOf)
+  {
+    CachedSurface::SurfaceMemoryReport report(aCounters, aMallocSizeOf);
+    for (auto iter = ConstIter(); !iter.Done(); iter.Next()) {
+      NotNull<CachedSurface*> surface = WrapNotNull(iter.UserData());
+      const IntSize& size = surface->GetSurfaceKey().Size();
+      bool factor2Size = false;
+      if (mFactor2Mode) {
+        factor2Size = (size == SuggestedSize(size));
+      }
+      report.Add(surface, factor2Size);
+    }
   }
 
   SurfaceTable::Iterator ConstIter() const
   {
     return mSurfaces.ConstIter();
   }
 
   void SetLocked(bool aLocked) { mLocked = aLocked; }
   bool IsLocked() const { return mLocked; }
 
 private:
-  SurfaceTable mSurfaces;
-  bool         mLocked;
+  SurfaceTable      mSurfaces;
+
+  bool              mLocked;
+
+  // True in "factor of 2" mode.
+  bool              mFactor2Mode;
+
+  // True if all non-factor of 2 surfaces have been removed from the cache. Note
+  // that this excludes unsubstitutable sizes.
+  bool              mFactor2Pruned;
 };
 
 /**
  * SurfaceCacheImpl is responsible for determining which surfaces will be cached
  * and managing the surface cache data structures. Rather than interact with
  * SurfaceCacheImpl directly, client code interacts with SurfaceCache, which
  * maintains high-level invariants and encapsulates the details of the surface
  * cache's implementation.
@@ -498,17 +753,17 @@ public:
       }
       return InsertOutcome::FAILURE;
     }
 
     StartTracking(surface, aAutoLock);
     return InsertOutcome::SUCCESS;
   }
 
-  void Remove(NotNull<CachedSurface*> aSurface,
+  bool Remove(NotNull<CachedSurface*> aSurface,
               const StaticMutexAutoLock& aAutoLock)
   {
     ImageKey imageKey = aSurface->GetImageKey();
 
     RefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
     MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
 
     // If the surface was not a placeholder, tell its image that we discarded it.
@@ -522,17 +777,20 @@ public:
     mCachedSurfacesDiscard.AppendElement(cache->Remove(aSurface));
 
     // Remove the per-image cache if it's unneeded now. Keep it if the image is
     // locked, since the per-image cache is where we store that state. Note that
     // we don't push it into mImageCachesDiscard because all of its surfaces
     // have been removed, so it is safe to free while holding the lock.
     if (cache->IsEmpty() && !cache->IsLocked()) {
       mImageCaches.Remove(imageKey);
+      return true;
     }
+
+    return false;
   }
 
   void StartTracking(NotNull<CachedSurface*> aSurface,
                      const StaticMutexAutoLock& aAutoLock)
   {
     CostEntry costEntry = aSurface->GetCostEntry();
     MOZ_ASSERT(costEntry.GetCost() <= mAvailableCost,
                "Cost too large and the caller didn't catch it");
@@ -586,17 +844,17 @@ public:
                       bool aMarkUsed = true)
   {
     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       // No cached surfaces for this image.
       return LookupResult(MatchType::NOT_FOUND);
     }
 
-    RefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
+    RefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey, aMarkUsed);
     if (!surface) {
       // Lookup in the per-image cache missed.
       return LookupResult(MatchType::NOT_FOUND);
     }
 
     if (surface->IsPlaceholder()) {
       return LookupResult(MatchType::PENDING);
     }
@@ -632,45 +890,58 @@ public:
     // has been freed by the operating system, until we can either lock a
     // surface for drawing or there are no matching surfaces left.
     // XXX(seth): This is O(N^2), but N is expected to be very small. If we
     // encounter a performance problem here we can revisit this.
 
     RefPtr<CachedSurface> surface;
     DrawableSurface drawableSurface;
     MatchType matchType = MatchType::NOT_FOUND;
+    IntSize suggestedSize;
     while (true) {
-      Tie(surface, matchType) = cache->LookupBestMatch(aSurfaceKey);
+      Tie(surface, matchType, suggestedSize)
+        = cache->LookupBestMatch(aSurfaceKey);
 
       if (!surface) {
         return LookupResult(matchType);  // Lookup in the per-image cache missed.
       }
 
       drawableSurface = surface->GetDrawableSurface();
       if (drawableSurface) {
         break;
       }
 
       // The surface was released by the operating system. Remove the cache
       // entry as well.
-      Remove(WrapNotNull(surface), aAutoLock);
+      if (Remove(WrapNotNull(surface), aAutoLock)) {
+        break;
+      }
     }
 
     MOZ_ASSERT_IF(matchType == MatchType::EXACT,
                   surface->GetSurfaceKey() == aSurfaceKey);
     MOZ_ASSERT_IF(matchType == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND ||
                   matchType == MatchType::SUBSTITUTE_BECAUSE_PENDING,
       surface->GetSurfaceKey().SVGContext() == aSurfaceKey.SVGContext() &&
       surface->GetSurfaceKey().Playback() == aSurfaceKey.Playback() &&
       surface->GetSurfaceKey().Flags() == aSurfaceKey.Flags());
 
-    if (matchType == MatchType::EXACT) {
+    if (matchType == MatchType::EXACT ||
+        matchType == MatchType::SUBSTITUTE_BECAUSE_BEST) {
       MarkUsed(WrapNotNull(surface), WrapNotNull(cache), aAutoLock);
     }
 
+    // When the caller may choose to decode at an uncached size because there is
+    // no pending decode at the requested size, we should give it the alternative
+    // size it should decode at.
+    if (matchType == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND ||
+        matchType == MatchType::NOT_FOUND) {
+      return LookupResult(Move(drawableSurface), matchType, suggestedSize);
+    }
+
     return LookupResult(Move(drawableSurface), matchType);
   }
 
   bool CanHold(const Cost aCost) const
   {
     return aCost <= mMaxCost;
   }
 
@@ -755,16 +1026,28 @@ public:
     // This implicitly unlocks the image if it was locked.
     mImageCaches.Remove(aImageKey);
 
     // Since we did not actually remove any of the surfaces from the cache
     // itself, only stopped tracking them, we should free it outside the lock.
     return cache.forget();
   }
 
+  void PruneImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
+  {
+    RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
+    if (!cache) {
+      return;  // No cached surfaces for this image, so nothing to do.
+    }
+
+    cache->Prune([this, &aAutoLock](NotNull<CachedSurface*> aSurface) -> void {
+      StopTracking(aSurface, aAutoLock);
+    });
+  }
+
   void DiscardAll(const StaticMutexAutoLock& aAutoLock)
   {
     // Remove in order of cost because mCosts is an array and the other data
     // structures are all hash tables. Note that locked surfaces are not
     // removed, since they aren't present in mCosts.
     while (!mCosts.IsEmpty()) {
       Remove(mCosts.LastElement().Surface(), aAutoLock);
     }
@@ -851,20 +1134,17 @@ public:
                              MallocSizeOf                    aMallocSizeOf)
   {
     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       return;  // No surfaces for this image.
     }
 
     // Report all surfaces in the per-image cache.
-    CachedSurface::SurfaceMemoryReport report(aCounters, aMallocSizeOf);
-    for (auto iter = cache->ConstIter(); !iter.Done(); iter.Next()) {
-      report.Add(WrapNotNull(iter.UserData()));
-    }
+    cache->CollectSizeOfSurfaces(aCounters, aMallocSizeOf);
   }
 
 private:
   already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
   {
     RefPtr<ImageSurfaceCache> imageCache;
     mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
     return imageCache.forget();
@@ -913,17 +1193,18 @@ private:
                    const SurfaceKey& aSurfaceKey,
                    const StaticMutexAutoLock& aAutoLock)
   {
     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       return;  // No cached surfaces for this image.
     }
 
-    RefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
+    RefPtr<CachedSurface> surface =
+      cache->Lookup(aSurfaceKey, /* aForAccess = */ false);
     if (!surface) {
       return;  // Lookup in the per-image cache missed.
     }
 
     Remove(WrapNotNull(surface), aAutoLock);
   }
 
   class SurfaceTracker final :
@@ -1204,16 +1485,25 @@ SurfaceCache::RemoveImage(const ImageKey
     StaticMutexAutoLock lock(sInstanceMutex);
     if (sInstance) {
       discard = sInstance->RemoveImage(aImageKey, lock);
     }
   }
 }
 
 /* static */ void
+SurfaceCache::PruneImage(const ImageKey aImageKey)
+{
+  StaticMutexAutoLock lock(sInstanceMutex);
+  if (sInstance) {
+    sInstance->PruneImage(aImageKey, lock);
+  }
+}
+
+/* static */ void
 SurfaceCache::DiscardAll()
 {
   nsTArray<RefPtr<CachedSurface>> discard;
   {
     StaticMutexAutoLock lock(sInstanceMutex);
     if (sInstance) {
       sInstance->DiscardAll(lock);
       sInstance->TakeDiscard(discard, lock);
--- a/image/SurfaceCache.h
+++ b/image/SurfaceCache.h
@@ -63,16 +63,21 @@ public:
   PLDHashNumber Hash() const
   {
     PLDHashNumber hash = HashGeneric(mSize.width, mSize.height);
     hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0));
     hash = AddToHash(hash, uint8_t(mPlayback), uint32_t(mFlags));
     return hash;
   }
 
+  SurfaceKey CloneWithSize(const IntSize& aSize) const
+  {
+    return SurfaceKey(aSize, mSVGContext, mPlayback, mFlags);
+  }
+
   const IntSize& Size() const { return mSize; }
   Maybe<SVGImageContext> SVGContext() const { return mSVGContext; }
   PlaybackType Playback() const { return mPlayback; }
   SurfaceFlags Flags() const { return mFlags; }
 
 private:
   SurfaceKey(const IntSize& aSize,
              const Maybe<SVGImageContext>& aSVGContext,
@@ -124,34 +129,45 @@ VectorSurfaceKey(const gfx::IntSize& aSi
  * AvailabilityState is used to track whether an ISurfaceProvider has a surface
  * available or is just a placeholder.
  *
  * To ensure that availability changes are atomic (and especially that internal
  * SurfaceCache code doesn't have to deal with asynchronous availability
  * changes), an ISurfaceProvider which starts as a placeholder can only reveal
  * the fact that it now has a surface available via a call to
  * SurfaceCache::SurfaceAvailable().
+ *
+ * It also tracks whether or not there are "explicit" users of this surface
+ * which will not accept substitutes. This is used by SurfaceCache when pruning
+ * unnecessary surfaces from the cache.
  */
 class AvailabilityState
 {
 public:
   static AvailabilityState StartAvailable() { return AvailabilityState(true); }
   static AvailabilityState StartAsPlaceholder() { return AvailabilityState(false); }
 
   bool IsAvailable() const { return mIsAvailable; }
   bool IsPlaceholder() const { return !mIsAvailable; }
+  bool CannotSubstitute() const { return mCannotSubstitute; }
+
+  void SetCannotSubstitute() { mCannotSubstitute = true; }
 
 private:
   friend class SurfaceCacheImpl;
 
-  explicit AvailabilityState(bool aIsAvailable) : mIsAvailable(aIsAvailable) { }
+  explicit AvailabilityState(bool aIsAvailable)
+    : mIsAvailable(aIsAvailable)
+    , mCannotSubstitute(false)
+  { }
 
   void SetAvailable() { mIsAvailable = true; }
 
-  bool mIsAvailable;
+  bool mIsAvailable : 1;
+  bool mCannotSubstitute : 1;
 };
 
 enum class InsertOutcome : uint8_t {
   SUCCESS,                 // Success (but see Insert documentation).
   FAILURE,                 // Couldn't insert (e.g., for capacity reasons).
   FAILURE_ALREADY_PRESENT  // A surface with the same key is already present.
 };
 
@@ -384,16 +400,26 @@ struct SurfaceCache
    * entries in the surface cache is destroyed. If another image were allocated
    * at the same address it could result in subtle, difficult-to-reproduce bugs.
    *
    * @param aImageKey  The image which should be removed from the cache.
    */
   static void RemoveImage(const ImageKey aImageKey);
 
   /**
+   * Attempts to remove cache entries (including placeholders) associated with
+   * the given image from the cache, assuming there is an equivalent entry that
+   * it is able substitute that entry with. Note that this only applies if the
+   * image is in factor of 2 mode. If it is not, this operation does nothing.
+   *
+   * @param aImageKey  The image whose cache which should be pruned.
+   */
+  static void PruneImage(const ImageKey aImageKey);
+
+  /**
    * Evicts all evictable entries from the cache.
    *
    * All entries are evictable except for entries associated with locked images.
    * Non-evictable entries can only be removed by RemoveImage().
    */
   static void DiscardAll();
 
   /**
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -524,16 +524,23 @@ VectorImage::GetWidth(int32_t* aWidth)
 //******************************************************************************
 nsresult
 VectorImage::GetNativeSizes(nsTArray<IntSize>& aNativeSizes) const
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 //******************************************************************************
+size_t
+VectorImage::GetNativeSizesLength() const
+{
+  return 0;
+}
+
+//******************************************************************************
 NS_IMETHODIMP_(void)
 VectorImage::RequestRefresh(const TimeStamp& aTime)
 {
   if (HadRecentRefresh(aTime)) {
     return;
   }
 
   PendingAnimationTracker* tracker =
--- a/image/VectorImage.h
+++ b/image/VectorImage.h
@@ -30,16 +30,17 @@ public:
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_IMGICONTAINER
 
   // (no public constructor - use ImageFactory)
 
   // Methods inherited from Image
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
+  size_t GetNativeSizesLength() const override;
   virtual size_t SizeOfSourceWithComputedFallback(SizeOfState& aState)
     const override;
   virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
                                      MallocSizeOf aMallocSizeOf) const override;
 
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
                                         nsISupports* aContext,
                                         nsIInputStream* aInStr,
--- a/image/imgIContainer.idl
+++ b/image/imgIContainer.idl
@@ -560,10 +560,12 @@ interface imgIContainer : nsISupports
    */
   [noscript, notxpcom] void propagateUseCounters(in nsIDocument aDocument);
 
   %{C++
   /*
    * Get the set of sizes the image can decode to natively.
    */
   virtual nsresult GetNativeSizes(nsTArray<nsIntSize>& aNativeSizes) const = 0;
+
+  virtual size_t GetNativeSizesLength() const = 0;
   %}
 };
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -276,16 +276,22 @@ private:
   static void ReportSurfaces(nsIHandleReportCallback* aHandleReport,
                              nsISupports* aData,
                              const nsACString& aPathPrefix,
                              const ImageMemoryCounter& aCounter)
   {
     for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
       nsAutoCString surfacePathPrefix(aPathPrefix);
       surfacePathPrefix.Append(counter.IsLocked() ? "locked/" : "unlocked/");
+      if (counter.IsFactor2()) {
+        surfacePathPrefix.Append("factor2/");
+      }
+      if (counter.CannotSubstitute()) {
+        surfacePathPrefix.Append("cannot_substitute/");
+      }
       surfacePathPrefix.Append("surface(");
       surfacePathPrefix.AppendInt(counter.Key().Size().width);
       surfacePathPrefix.Append("x");
       surfacePathPrefix.AppendInt(counter.Key().Size().height);
 
       if (counter.Values().SharedHandles() > 0) {
         surfacePathPrefix.Append(", shared:");
         surfacePathPrefix.AppendInt(uint32_t(counter.Values().SharedHandles()));
new file mode 100644
--- /dev/null
+++ b/js/rust/.gitignore
@@ -0,0 +1,1 @@
+target/
new file mode 100644
--- /dev/null
+++ b/js/rust/CMakeLists.txt
@@ -0,0 +1,35 @@
+project(rust-mozjs)
+cmake_minimum_required(VERSION 2.6)
+
+set(DUMMY ${CMAKE_BUILD_TYPE})
+
+set(SOURCES
+  src/jsglue.cpp
+  )
+
+include_directories($ENV{DEP_MOZJS_OUTDIR}/dist/include)
+
+if(MSVC)
+  if(NOT "$ENV{CARGO_FEATURE_DEBUGMOZJS}" STREQUAL "")
+    add_definitions(-MDd -Od -DDEBUG -D_DEBUG)
+  else()
+    add_definitions(-MD)
+  endif()
+  add_definitions(-FI$ENV{DEP_MOZJS_OUTDIR}/js/src/js-confdefs.h)
+  add_definitions(-DWIN32)
+  add_definitions(-Zi -GR-)
+else()
+  if(NOT "$ENV{CARGO_FEATURE_DEBUGMOZJS}" STREQUAL "")
+    add_definitions(-g -O0 -DDEBUG -D_DEBUG)
+  endif()
+  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    add_definitions(-Wno-c++0x-extensions -Wno-return-type-c-linkage -Wno-invalid-offsetof)
+  endif()
+  add_definitions(-fPIC -fno-rtti)
+  add_definitions(-std=c++11 -DJS_NO_JSVAL_JSID_STRUCT_TYPES)
+  add_definitions(-include $ENV{DEP_MOZJS_OUTDIR}/js/src/js-confdefs.h)
+endif()
+
+add_library(jsglue STATIC ${SOURCES})
+install(TARGETS jsglue ARCHIVE DESTINATION lib)
+
new file mode 100644
--- /dev/null
+++ b/js/rust/Cargo.lock
@@ -0,0 +1,494 @@
+[root]
+name = "js"
+version = "0.1.4"
+dependencies = [
+ "bindgen 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cmake 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mozjs_sys 0.0.0",
+ "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "aster"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "clang-sys 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "clap 2.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quasi_codegen 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "which 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bitflags"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitflags"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cexpr"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "clang-sys"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libloading 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "clap"
+version = "2.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "textwrap 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gcc"
+version = "0.3.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "glob"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "heapsize"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libloading"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mozjs_sys"
+version = "0.0.0"
+dependencies = [
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "nom"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num-traits"
+version = "0.1.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num_cpus"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "quasi"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quasi_codegen"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc-serialize"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "strsim"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "syntex"
+version = "0.58.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syntex_errors"
+version = "0.58.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syntex_pos"
+version = "0.58.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syntex_syntax"
+version = "0.58.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "term"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "term_size"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread-id"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread_local"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-xid"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unreachable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "utf8-ranges"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "vec_map"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "which"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
+"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
+"checksum aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfdf7355d9db158df68f976ed030ab0f6578af811f5a7bb6dcf221ec24e0e0"
+"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
+"checksum bindgen 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33024f55a754d920637461adf87fb485702a69bdf7ac1d307b7e18da93bae505"
+"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
+"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
+"checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d"
+"checksum cfg-if 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c47d456a36ebf0536a6705c83c1cbbcb9255fbc1d905a6ded104f479268a29"
+"checksum clang-sys 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "611ec2e3a7623afd8a8c0d027887b6b55759d894abbf5fe11b9dc11b50d5b49a"
+"checksum clap 2.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "867a885995b4184be051b70a592d4d70e32d7a188db6e8dff626af286a962771"
+"checksum cmake 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b8ebbb35d3dc9cd09497168f33de1acb79b265d350ab0ac34133b98f8509af1f"
+"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b"
+"checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a"
+"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
+"checksum heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c7593b1522161003928c959c20a2ca421c68e940d63d75573316a009e48a6d4"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf"
+"checksum libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "38f5c2b18a287cf78b4097db62e20f43cace381dc76ae5c0a3073067f78b7ddc"
+"checksum libloading 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "be99f814beb3e9503a786a592c909692bb6d4fc5a695f6ed7987223acfbd5194"
+"checksum libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd64ef8ee652185674455c1d450b83cbc8ad895625d543b5324d923f82e4d8"
+"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
+"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
+"checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
+"checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6"
+"checksum num_cpus 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83df569ffd47dbf87f36bead512632f89e90882b8e7a14286d0471daf6b72de9"
+"checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
+"checksum quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c45c4854d6d1cf5d531db97c75880feb91c958b0720f4ec1057135fec358b3"
+"checksum quasi_codegen 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9e25fa23c044c1803f43ca59c98dac608976dd04ce799411edd58ece776d4"
+"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
+"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
+"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
+"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
+"checksum syntex 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f5e3aaa79319573d19938ea38d068056b826db9883a5d47f86c1cecc688f0e"
+"checksum syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "867cc5c2d7140ae7eaad2ae9e8bf39cb18a67ca651b7834f88d46ca98faadb9c"
+"checksum syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13ad4762fe52abc9f4008e85c4fb1b1fe3aa91ccb99ff4826a439c7c598e1047"
+"checksum syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e0e4dbae163dd98989464c23dd503161b338790640e11537686f2ef0f25c791"
+"checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989"
+"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
+"checksum textwrap 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f86300c3e7416ee233abd7cda890c492007a3980f941f79185c753a701257167"
+"checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773"
+"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
+"checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3"
+"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
+"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
+"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
+"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
+"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
+"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
+"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+"checksum which 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d238435618c0f298d2d75596c2d4fa7d4ea469c0c1c3ff824737ed50ad5ab61c"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
new file mode 100644
--- /dev/null
+++ b/js/rust/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "js"
+version = "0.1.4"
+authors = ["The Servo Project Developers"]
+build = "build.rs"
+license = "MPL-2.0"
+
+[build-dependencies]
+env_logger = "0.4"
+log = "0.3"
+bindgen = "0.30.0"
+cmake = "0.1"
+glob = "0.2.11"
+
+[[test]]
+name = "callback"
+[[test]]
+name = "enumerate"
+[[test]]
+name = "evaluate"
+[[test]]
+name = "panic"
+[[test]]
+name = "rooting"
+[[test]]
+name = "runtime"
+[[test]]
+name = "typedarray"
+[[test]]
+name = "stack_limit"
+[[test]]
+name = "vec_conversion"
+
+[lib]
+doctest = false
+
+[features]
+debugmozjs = ['mozjs_sys/debugmozjs']
+promises = ['mozjs_sys/promises']
+nonzero = []
+
+[dependencies.mozjs_sys]
+path = "../src"
+
+[dependencies]
+lazy_static = "0.2.1"
+libc = "0.2"
+log = "0.3"
+heapsize = "0.4"
+num-traits = "0.1.32"
new file mode 100644
--- /dev/null
+++ b/js/rust/README.md
@@ -0,0 +1,46 @@
+# The `js` Crate: Rust Bindings to SpiderMonkey
+
+[User Documentation](http://doc.servo.org/js/)
+
+## Building
+
+To build a release version of SpiderMonkey and the Rust code with optimizations
+enabled:
+
+```
+$ cargo build --release
+```
+
+To build with SpiderMonkey's DEBUG checks and assertions:
+
+```
+$ cargo build --features debugmozjs
+```
+
+Raw FFI bindings to JSAPI are machine generated with
+[`rust-lang-nursery/rust-bindgen`][bindgen], and requires libclang >= 3.9. See
+`./build.rs` for details.
+
+[bindgen]: https://github.com/rust-lang-nursery/rust-bindgen
+
+## Cargo Features
+
+* `debugmozjs`: Create a DEBUG build of SpiderMonkey with many extra assertions
+  enabled. This is decoupled from whether the crate and its Rust code is built
+  in debug or release mode.
+
+* `promises`: Enable SpiderMonkey native promises.
+
+* `nonzero`: Leverage the unstable `NonZero` type. Requires nightly Rust.
+
+## Testing
+
+Make sure to test both with and without the `debugmozjs` feature because various
+structures have different sizes and get passed through functions differently at
+the ABI level! At minimum, you should test with `debugmozjs` to get extra
+assertions and checking.
+
+```
+$ cargo test
+$ cargo test --features debugmozjs
+```
new file mode 100644
--- /dev/null
+++ b/js/rust/build.rs
@@ -0,0 +1,468 @@
+/* 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/. */
+
+extern crate bindgen;
+extern crate cmake;
+extern crate glob;
+
+extern crate log;
+extern crate env_logger;
+
+use std::env;
+use std::path;
+
+fn main() {
+    log::set_logger(|max_log_level| {
+        use env_logger::Logger;
+        let env_logger = Logger::new();
+        max_log_level.set(env_logger.filter());
+        Box::new(env_logger)
+    }).expect("Failed to set logger.");
+
+    build_jsapi_bindings();
+    build_jsglue_cpp();
+}
+
+/// Build the ./src/jsglue.cpp file containing C++ glue methods built on top of
+/// JSAPI.
+fn build_jsglue_cpp() {
+    let dst = cmake::Config::new(".").build();
+
+    println!("cargo:rustc-link-search=native={}/lib", dst.display());
+    println!("cargo:rustc-link-lib=static=jsglue");
+    println!("cargo:rerun-if-changed=src/jsglue.cpp");
+}
+
+/// Find the public include directory within our mozjs-sys crate dependency.
+fn get_mozjs_include_dir() -> path::PathBuf {
+    let out_dir = env::var("OUT_DIR")
+        .expect("cargo should invoke us with the OUT_DIR env var set");
+
+    let mut target_build_dir = path::PathBuf::from(out_dir);
+    target_build_dir.push("../../");
+
+    let mut include_dir_glob = target_build_dir.display().to_string();
+    include_dir_glob.push_str("mozjs_sys-*/out/dist/include");
+
+    let entries = glob::glob(&include_dir_glob)
+        .expect("Should find entries for mozjs-sys include dir");
+
+    for entry in entries {
+        if let Ok(path) = entry {
+            return path.canonicalize()
+                .expect("Should canonicalize include path");
+        }
+    }
+
+    panic!("Should have found either a mozjs_sys in target/debug or in target/release");
+}
+
+/// Invoke bindgen on the JSAPI headers to produce raw FFI bindings for use from
+/// Rust.
+///
+/// To add or remove which functions, types, and variables get bindings
+/// generated, see the `const` configuration variables below.
+fn build_jsapi_bindings() {
+    let mut builder = bindgen::builder()
+        .rust_target(bindgen::RustTarget::Stable_1_19)
+        .header("./etc/wrapper.hpp")
+        .raw_line("pub use self::root::*;")
+        .enable_cxx_namespaces();
+
+    if cfg!(feature = "debugmozjs") {
+        builder = builder
+            .clang_arg("-DJS_GC_ZEAL")
+            .clang_arg("-DDEBUG")
+            .clang_arg("-DJS_DEBUG");
+    }
+
+    let include_dir = get_mozjs_include_dir();
+    let include_dir = include_dir.to_str()
+        .expect("Path to mozjs include dir should be utf-8");
+    builder = builder.clang_arg("-I");
+    builder = builder.clang_arg(include_dir);
+
+    for ty in UNSAFE_IMPL_SYNC_TYPES {
+        builder = builder.raw_line(format!("unsafe impl Sync for {} {{}}", ty));
+    }
+
+    for extra in EXTRA_CLANG_FLAGS {
+        builder = builder.clang_arg(*extra);
+    }
+
+    for ty in WHITELIST_TYPES {
+        builder = builder.whitelisted_type(ty);
+    }
+
+    for var in WHITELIST_VARS {
+        builder = builder.whitelisted_var(var);
+    }
+
+    for func in WHITELIST_FUNCTIONS {
+        builder = builder.whitelisted_function(func);
+    }
+
+    for ty in OPAQUE_TYPES {
+        builder = builder.opaque_type(ty);
+    }
+
+    for ty in BLACKLIST_TYPES {
+        builder = builder.hide_type(ty);
+    }
+
+    let bindings = builder.generate()
+        .expect("Should generate JSAPI bindings OK");
+
+    let out = path::PathBuf::from(env::var("OUT_DIR").unwrap());
+
+    if cfg!(feature = "debugmozjs") {
+        bindings.write_to_file(out.join("jsapi_debug.rs"))
+            .expect("Should write bindings to file OK");
+    } else {
+        bindings.write_to_file(out.join("jsapi.rs"))
+            .expect("Should write bindings to file OK");
+    }
+
+    println!("cargo:rerun-if-changed=etc/wrapper.hpp");
+}
+
+/// JSAPI types for which we should implement `Sync`.
+const UNSAFE_IMPL_SYNC_TYPES: &'static [&'static str] = &[
+    "JSClass",
+    "JSFunctionSpec",
+    "JSNativeWrapper",
+    "JSPropertySpec",
+    "JSTypedMethodJitInfo",
+];
+
+/// Flags passed through bindgen directly to Clang.
+const EXTRA_CLANG_FLAGS: &'static [&'static str] = &[
+    "-x", "c++",
+    "-std=c++14",
+    "-DRUST_BINDGEN",
+];
+
+/// Types which we want to generate bindings for (and every other type they
+/// transitively use).
+const WHITELIST_TYPES: &'static [&'static str] = &[
+    "JS::AutoCheckCannotGC",
+    "JS::AutoIdVector",
+    "JS::AutoObjectVector",
+    "JS::CallArgs",
+    "js::Class",
+    "JS::CompartmentOptions",
+    "JS::ContextOptions",
+    "js::DOMCallbacks",
+    "js::DOMProxyShadowsResult",
+    "js::ESClass",
+    "JS::ForOfIterator",
+    "JS::Handle",
+    "JS::HandleId",
+    "JS::HandleObject",
+    "JS::HandleString",
+    "JS::HandleValue",
+    "JS::HandleValueArray",
+    "JS::IsAcceptableThis",
+    "JSAutoCompartment",
+    "JSAutoStructuredCloneBuffer",
+    "JSClass",
+    "JSClassOps",
+    "JSContext",
+    "JSErrNum",
+    "JSErrorCallback",
+    "JSErrorFormatString",
+    "JSErrorReport",
+    "JSExnType",
+    "JSFlatString",
+    "JSFunction",
+    "JSFunctionSpec",
+    "JS::GCDescription",
+    "JSGCInvocationKind",
+    "JSGCMode",
+    "JSGCParamKey",
+    "JS::GCProgress",
+    "JSGCStatus",
+    "JSJitCompilerOption",
+    "JSJitGetterCallArgs",
+    "JSJitMethodCallArgs",
+    "JSJitSetterCallArgs",
+    "JSNativeWrapper",
+    "JSPropertySpec",
+    "JSProtoKey",
+    "JSObject",
+    "JSString",
+    "JSStructuredCloneReader",
+    "JSStructuredCloneWriter",
+    "JSScript",
+    "JSType",
+    "JSTypedMethodJitInfo",
+    "JSValueTag",
+    "JSValueType",
+    "JSVersion",
+    "JSWrapObjectCallbacks",
+    "jsid",
+    "JS::Latin1Char",
+    "JS::detail::MaybeWrapped",
+    "JS::MutableHandle",
+    "JS::MutableHandleObject",
+    "JS::MutableHandleValue",
+    "JS::NativeImpl",
+    "js::ObjectOps",
+    "JS::ObjectOpResult",
+    "JS::PromiseState",
+    "JS::PropertyDescriptor",
+    "JS::Rooted",
+    "JS::RootedObject",
+    "JS::RootingContext",
+    "JS::RootKind",
+    "js::Scalar::Type",
+    "JS::ServoSizes",
+    "js::shadow::Object",
+    "js::shadow::ObjectGroup",
+    "JS::SourceBufferHolder",
+    "JSStructuredCloneCallbacks",
+    "JS::Symbol",
+    "JS::SymbolCode",
+    "JS::TraceKind",
+    "JS::TransferableOwnership",
+    "JS::Value",
+    "JS::WarningReporter",
+    "JS::shadow::Zone",
+    "JS::Zone",
+];
+
+/// Global variables we want to generate bindings to.
+const WHITELIST_VARS: &'static [&'static str] = &[
+    "JS_STRUCTURED_CLONE_VERSION",
+    "JSCLASS_.*",
+    "JSFUN_.*",
+    "JSID_VOID",
+    "JSITER_.*",
+    "JSPROP_.*",
+    "JS::FalseHandleValue",
+    "JS::NullHandleValue",
+    "JS::TrueHandleValue",
+    "JS::UndefinedHandleValue",
+];
+
+/// Functions we want to generate bindings to.
+const WHITELIST_FUNCTIONS: &'static [&'static str] = &[
+    "INTERNED_STRING_TO_JSID",
+    "JS_AddExtraGCRootsTracer",
+    "JS_AddInterruptCallback",
+    "JS::AddPromiseReactions",
+    "js::AddRawValueRoot",
+    "JS_AlreadyHasOwnPropertyById",
+    "JS_AtomizeAndPinString",
+    "js::AssertSameCompartment",
+    "JS::Call",
+    "JS_CallFunctionValue",
+    "JS::CallOriginalPromiseThen",
+    "JS::CallOriginalPromiseResolve",
+    "JS::CallOriginalPromiseReject",
+    "JS::CompileFunction",
+    "JS::Construct",
+    "JS::ContextOptionsRef",
+    "JS_CopyPropertiesFrom",
+    "JS::CurrentGlobalOrNull",
+    "JS_DeletePropertyById",
+    "js::detail::IsWindowSlow",
+    "JS::Evaluate",
+    "JS_ForwardGetPropertyTo",
+    "JS_ForwardSetPropertyTo",
+    "JS::GCTraceKindToAscii",
+    "js::GetArrayBufferLengthAndData",
+    "js::GetArrayBufferViewLengthAndData",
+    "JS_GetErrorPrototype",
+    "js::GetFunctionNativeReserved",
+    "JS_GetFunctionPrototype",
+    "js::GetGlobalForObjectCrossCompartment",
+    "JS_GetIteratorPrototype",
+    "js::GetObjectProto",
+    "JS_GetObjectPrototype",
+    "JS_GetObjectRuntime",
+    "JS_GetOwnPropertyDescriptorById",
+    "JS::GetPromiseState",
+    "JS_GetPropertyDescriptorById",
+    "js::GetPropertyKeys",
+    "JS_GetPrototype",
+    "JS_GetRuntime",
+    "js::GetStaticPrototype",
+    "JS_HasOwnPropertyById",
+    "JS_HasProperty",
+    "JS_HasPropertyById",
+    "JS::HeapObjectPostBarrier",
+    "JS::HeapValuePostBarrier",
+    "JS_InitializePropertiesFromCompatibleNativeObject",
+    "JS::InitSelfHostedCode",
+    "JS::IsConstructor",
+    "JS::IsPromiseObject",
+    "JS_BeginRequest",
+    "JS_ClearPendingException",
+    "JS_DefineElement",
+    "JS_DefineFunction",
+    "JS_DefineFunctions",
+    "JS_DefineProperties",
+    "JS_DefineProperty",
+    "JS_DefinePropertyById",
+    "JS_DefineUCProperty",
+    "JS::detail::InitWithFailureDiagnostic",
+    "JS_DestroyContext",
+    "JS::DisableIncrementalGC",
+    "js::Dump.*",
+    "JS_EncodeStringToUTF8",
+    "JS_EndRequest",
+    "JS_EnterCompartment",
+    "JS_EnumerateStandardClasses",
+    "JS_ErrorFromException",
+    "JS_FireOnNewGlobalObject",
+    "JS_GC",
+    "JS_GetArrayBufferData",
+    "JS_GetArrayBufferViewType",
+    "JS_GetFloat32ArrayData",
+    "JS_GetFloat64ArrayData",
+    "JS_GetFunctionObject",
+    "JS_GetGCParameter",
+    "JS_GetInt16ArrayData",
+    "JS_GetInt32ArrayData",
+    "JS_GetInt8ArrayData",
+    "JS_GetLatin1StringCharsAndLength",
+    "JS_GetParentRuntime",
+    "JS_GetPendingException",
+    "JS_GetProperty",
+    "JS_GetPropertyById",
+    "js::GetPropertyKeys",
+    "JS_GetPrototype",
+    "JS_GetReservedSlot",
+    "JS::GetScriptedCallerGlobal",
+    "JS_GetTwoByteStringCharsAndLength",
+    "JS_GetUint16ArrayData",
+    "JS_GetUint32ArrayData",
+    "JS_GetUint8ArrayData",
+    "JS_GetUint8ClampedArrayData",
+    "JS::GetWellKnownSymbol",
+    "JS_GlobalObjectTraceHook",
+    "JS::HideScriptedCaller",
+    "JS_InitStandardClasses",
+    "JS_IsArrayObject",
+    "JS_IsExceptionPending",
+    "JS_IsGlobalObject",
+    "JS::IsCallable",
+    "JS_LeaveCompartment",
+    "JS_LinkConstructorAndPrototype",
+    "JS_MayResolveStandardClass",
+    "JS_NewArrayBuffer",
+    "JS_NewArrayObject",
+    "JS_NewContext",
+    "JS_NewFloat32Array",
+    "JS_NewFloat64Array",
+    "JS_NewFunction",
+    "js::NewFunctionWithReserved",
+    "JS_NewGlobalObject",
+    "JS_NewInt16Array",
+    "JS_NewInt32Array",
+    "JS_NewInt8Array",
+    "JS_NewObject",
+    "JS_NewObjectWithGivenProto",
+    "JS_NewObjectWithoutMetadata",
+    "JS_NewObjectWithUniqueType",
+    "JS_NewPlainObject",
+    "JS::NewPromiseObject",
+    "JS_NewStringCopyN",
+    "JS_NewUCStringCopyN",
+    "JS_NewUint16Array",
+    "JS_NewUint32Array",
+    "JS_NewUint8Array",
+    "JS_NewUint8ClampedArray",
+    "js::ObjectClassName",
+    "JS_ObjectIsDate",
+    "JS_ParseJSON",
+    "JS_ReadBytes",
+    "JS_ReadStructuredClone",
+    "JS_ReadUint32Pair",
+    "js::RemoveRawValueRoot",
+    "JS_ReportErrorASCII",
+    "JS_ReportErrorNumberUTF8",
+    "JS_RequestInterruptCallback",
+    "JS_ResolveStandardClass",
+    "JS_SameValue",
+    "js::SetDOMCallbacks",
+    "js::SetDOMProxyInformation",
+    "JS::SetEnqueuePromiseJobCallback",
+    "js::SetFunctionNativeReserved",
+    "JS_SetGCCallback",
+    "JS::SetGCSliceCallback",
+    "JS_SetGCParameter",
+    "JS_SetGCZeal",
+    "JS::SetGetIncumbentGlobalCallback",
+    "JS_SetGlobalJitCompilerOption",
+    "JS_SetImmutablePrototype",
+    "JS_SetNativeStackQuota",
+    "JS_SetOffthreadIonCompilationEnabled",
+    "JS_SetParallelParsingEnabled",
+    "JS_SetPendingException",
+    "js::SetPreserveWrapperCallback",
+    "JS_SetPrototype",
+    "js::SetWindowProxy",
+    "js::SetWindowProxyClass",
+    "JS_SetProperty",
+    "JS_SetReservedSlot",
+    "JS_SetWrapObjectCallbacks",
+    "JS_ShutDown",
+    "JS_SplicePrototype",
+    "JS_StrictPropertyStub",
+    "JS_StringEqualsAscii",
+    "JS_StringHasLatin1Chars",
+    "JS_WrapObject",
+    "JS_WrapValue",
+    "JS_WriteBytes",
+    "JS_WriteStructuredClone",
+    "JS_WriteUint32Pair",
+    "JS::ResolvePromise",
+    "JS::RejectPromise",
+    "JS::SetWarningReporter",
+    "js::ToBooleanSlow",
+    "js::ToInt32Slow",
+    "js::ToInt64Slow",
+    "js::ToNumberSlow",
+    "js::ToStringSlow",
+    "js::ToUint16Slow",
+    "js::ToUint32Slow",
+    "js::ToUint64Slow",
+    "JS_TransplantObject",
+    "js::detail::ToWindowProxyIfWindowSlow",
+    "JS::UnhideScriptedCaller",
+    "js::UnwrapArrayBuffer",
+    "js::UnwrapArrayBufferView",
+    "js::UnwrapFloat32Array",
+    "js::UnwrapFloat64Array",
+    "js::UnwrapInt16Array",
+    "js::UnwrapInt32Array",
+    "js::UnwrapInt8Array",
+    "js::UnwrapUint16Array",
+    "js::UnwrapUint32Array",
+    "js::UnwrapUint8Array",
+    "js::UnwrapUint8ClampedArray",
+];
+
+/// Types that should be treated as an opaque blob of bytes whenever they show
+/// up within a whitelisted type.
+///
+/// These are types which are too tricky for bindgen to handle, and/or use C++
+/// features that don't have an equivalent in rust, such as partial template
+/// specialization.
+const OPAQUE_TYPES: &'static [&'static str] = &[
+    "JS::ReadOnlyCompileOptions",
+    "mozilla::BufferList",
+    "mozilla::UniquePtr.*",
+    "JS::Rooted<JS::Auto.*Vector.*>",
+];
+
+/// Types for which we should NEVER generate bindings, even if it is used within
+/// a type or function signature that we are generating bindings for.
+const BLACKLIST_TYPES: &'static [&'static str] = &[
+    // We provide our own definition because we need to express trait bounds in
+    // the definition of the struct to make our Drop implementation correct.
+    "JS::Heap",
+];
new file mode 100644
--- /dev/null
+++ b/js/rust/etc/wrapper.hpp
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#include <stdint.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+typedef uint32_t HashNumber;
+
+#include "jsfriendapi.h"
+#include "js/Conversions.h"
+#include "js/Initialization.h"
+#include "js/MemoryMetrics.h"
+
+// Replacements for types that are too difficult for rust-bindgen.
+
+/// <div rustbindgen replaces="JS::detail::MaybeWrapped" />
+template <typename T>
+using replaces_MaybeWrapped = T;
new file mode 100644
--- /dev/null
+++ b/js/rust/src/ac.rs
@@ -0,0 +1,56 @@
+use jsapi::root::*;
+#[cfg(feature = "debugmozjs")]
+use std::ptr;
+
+#[derive(Debug)]
+pub struct AutoCompartment(JSAutoCompartment);
+
+impl AutoCompartment {
+    #[cfg(feature = "debugmozjs")]
+    pub unsafe fn with_obj(cx: *mut JSContext,
+                           target: *mut JSObject)
+                           -> AutoCompartment
+    {
+        let mut notifier = mozilla::detail::GuardObjectNotifier {
+            mStatementDone: ptr::null_mut(),
+        };
+
+        AutoCompartment(
+            JSAutoCompartment::new(
+                cx,
+                target,
+                &mut notifier as *mut _))
+    }
+
+    #[cfg(not(feature = "debugmozjs"))]
+    pub unsafe fn with_obj(cx: *mut JSContext,
+                           target: *mut JSObject)
+                           -> AutoCompartment
+    {
+        AutoCompartment(JSAutoCompartment::new(cx, target))
+    }
+
+    #[cfg(feature = "debugmozjs")]
+    pub unsafe fn with_script(cx: *mut JSContext,
+                              target: *mut JSScript)
+                              -> AutoCompartment
+    {
+        let mut notifier = mozilla::detail::GuardObjectNotifier {
+            mStatementDone: ptr::null_mut(),
+        };
+
+        AutoCompartment(
+            JSAutoCompartment::new1(
+                cx,
+                target,
+                &mut notifier as *mut _))
+    }
+
+    #[cfg(not(feature = "debugmozjs"))]
+    pub unsafe fn with_script(cx: *mut JSContext,
+                              target: *mut JSScript)
+                              -> AutoCompartment
+    {
+        AutoCompartment(JSAutoCompartment::new1(cx, target))
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/conversions.rs
@@ -0,0 +1,679 @@
+/* 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/. */
+
+//! Conversions of Rust values to and from `JSVal`.
+//!
+//! | IDL type                | Type                             |
+//! |-------------------------|----------------------------------|
+//! | any                     | `JSVal`                          |
+//! | boolean                 | `bool`                           |
+//! | byte                    | `i8`                             |
+//! | octet                   | `u8`                             |
+//! | short                   | `i16`                            |
+//! | unsigned short          | `u16`                            |
+//! | long                    | `i32`                            |
+//! | unsigned long           | `u32`                            |
+//! | long long               | `i64`                            |
+//! | unsigned long long      | `u64`                            |
+//! | unrestricted float      | `f32`                            |
+//! | float                   | `Finite<f32>`                    |
+//! | unrestricted double     | `f64`                            |
+//! | double                  | `Finite<f64>`                    |
+//! | USVString               | `String`                         |
+//! | object                  | `*mut JSObject`                  |
+//! | nullable types          | `Option<T>`                      |
+//! | sequences               | `Vec<T>`                         |
+
+#![deny(missing_docs)]
+
+#[cfg(feature = "nonzero")]
+use core::nonzero::NonZero;
+
+use error::throw_type_error;
+use glue::RUST_JS_NumberValue;
+use heap::Heap;
+use jsapi::root::*;
+use jsval::{BooleanValue, Int32Value, NullValue, UInt32Value, UndefinedValue};
+use jsval::{ObjectValue, ObjectOrNullValue, StringValue};
+use rust::{ToBoolean, ToInt32, ToInt64, ToNumber, ToUint16, ToUint32, ToUint64};
+use rust::{ToString, maybe_wrap_object_or_null_value, maybe_wrap_value};
+use libc;
+use num_traits::{Bounded, Zero};
+use std::borrow::Cow;
+use std::rc::Rc;
+use std::{ptr, slice};
+
+trait As<O>: Copy {
+    fn cast(self) -> O;
+}
+
+macro_rules! impl_as {
+    ($I:ty, $O:ty) => (
+        impl As<$O> for $I {
+            fn cast(self) -> $O {
+                self as $O
+            }
+        }
+    )
+}
+
+impl_as!(f64, u8);
+impl_as!(f64, u16);
+impl_as!(f64, u32);
+impl_as!(f64, u64);
+impl_as!(f64, i8);
+impl_as!(f64, i16);
+impl_as!(f64, i32);
+impl_as!(f64, i64);
+
+impl_as!(u8, f64);
+impl_as!(u16, f64);
+impl_as!(u32, f64);
+impl_as!(u64, f64);
+impl_as!(i8, f64);
+impl_as!(i16, f64);
+impl_as!(i32, f64);
+impl_as!(i64, f64);
+
+impl_as!(i32, i8);
+impl_as!(i32, u8);
+impl_as!(i32, i16);
+impl_as!(u16, u16);
+impl_as!(i32, i32);
+impl_as!(u32, u32);
+impl_as!(i64, i64);
+impl_as!(u64, u64);
+
+/// A trait to convert Rust types to `JSVal`s.
+pub trait ToJSValConvertible {
+    /// Convert `self` to a `JSVal`. JSAPI failure causes a panic.
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue);
+}
+
+/// An enum to better support enums through FromJSValConvertible::from_jsval.
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum ConversionResult<T> {
+    /// Everything went fine.
+    Success(T),
+    /// Conversion failed, without a pending exception.
+    Failure(Cow<'static, str>),
+}
+
+impl<T> ConversionResult<T> {
+    /// Returns Some(value) if it is `ConversionResult::Success`.
+    pub fn get_success_value(&self) -> Option<&T> {
+        match *self {
+            ConversionResult::Success(ref v) => Some(v),
+            _ => None,
+        }
+    }
+}
+
+/// A trait to convert `JSVal`s to Rust types.
+pub trait FromJSValConvertible: Sized {
+    /// Optional configurable behaviour switch; use () for no configuration.
+    type Config;
+    /// Convert `val` to type `Self`.
+    /// Optional configuration of type `T` can be passed as the `option`
+    /// argument.
+    /// If it returns `Err(())`, a JSAPI exception is pending.
+    /// If it returns `Ok(Failure(reason))`, there is no pending JSAPI exception.
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: Self::Config)
+                         -> Result<ConversionResult<Self>, ()>;
+}
+
+/// Behavior for converting out-of-range integers.
+#[derive(PartialEq, Eq, Clone)]
+pub enum ConversionBehavior {
+    /// Wrap into the integer's range.
+    Default,
+    /// Throw an exception.
+    EnforceRange,
+    /// Clamp into the integer's range.
+    Clamp,
+}
+
+/// Try to cast the number to a smaller type, but
+/// if it doesn't fit, it will return an error.
+unsafe fn enforce_range<D>(cx: *mut JSContext, d: f64) -> Result<ConversionResult<D>, ()>
+    where D: Bounded + As<f64>,
+          f64: As<D>
+{
+    if d.is_infinite() {
+        throw_type_error(cx, "value out of range in an EnforceRange argument");
+        return Err(());
+    }
+
+    let rounded = d.round();
+    if D::min_value().cast() <= rounded && rounded <= D::max_value().cast() {
+        Ok(ConversionResult::Success(rounded.cast()))
+    } else {
+        throw_type_error(cx, "value out of range in an EnforceRange argument");
+        Err(())
+    }
+}
+
+/// Try to cast the number to a smaller type, but if it doesn't fit,
+/// round it to the MAX or MIN of the source type before casting it to
+/// the destination type.
+fn clamp_to<D>(d: f64) -> D
+    where D: Bounded + As<f64> + Zero,
+          f64: As<D>
+{
+    if d.is_nan() {
+        D::zero()
+    } else if d > D::max_value().cast() {
+        D::max_value()
+    } else if d < D::min_value().cast() {
+        D::min_value()
+    } else {
+        d.cast()
+    }
+}
+
+// https://heycam.github.io/webidl/#es-void
+impl ToJSValConvertible for () {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(UndefinedValue());
+    }
+}
+
+impl FromJSValConvertible for JS::HandleValue {
+    type Config = ();
+    #[inline]
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         value: JS::HandleValue,
+                         _option: ())
+                         -> Result<ConversionResult<JS::HandleValue>, ()> {
+        if value.is_object() {
+            js::AssertSameCompartment(cx, value.to_object());
+        }
+        Ok(ConversionResult::Success(value))
+    }
+}
+
+impl FromJSValConvertible for JS::Value {
+    type Config = ();
+    unsafe fn from_jsval(_cx: *mut JSContext,
+                         value: JS::HandleValue,
+                         _option: ())
+                         -> Result<ConversionResult<JS::Value>, ()> {
+        Ok(ConversionResult::Success(value.get()))
+    }
+}
+
+impl FromJSValConvertible for Heap<JS::Value> {
+    type Config = ();
+    unsafe fn from_jsval(_cx: *mut JSContext,
+                         value: JS::HandleValue,
+                         _option: ())
+                         -> Result<ConversionResult<Self>, ()> {
+        Ok(ConversionResult::Success(Heap::<JS::Value>::new(value.get())))
+    }
+}
+
+impl ToJSValConvertible for JS::Value {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(*self);
+        maybe_wrap_value(cx, rval);
+    }
+}
+
+impl ToJSValConvertible for JS::HandleValue {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(self.get());
+        maybe_wrap_value(cx, rval);
+    }
+}
+
+impl ToJSValConvertible for Heap<JS::Value> {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(self.get());
+        maybe_wrap_value(cx, rval);
+    }
+}
+
+#[inline]
+unsafe fn convert_int_from_jsval<T, M>(cx: *mut JSContext, value: JS::HandleValue,
+                                       option: ConversionBehavior,
+                                       convert_fn: unsafe fn(*mut JSContext, JS::HandleValue) -> Result<M, ()>)
+                                       -> Result<ConversionResult<T>, ()>
+    where T: Bounded + Zero + As<f64>,
+          M: Zero + As<T>,
+          f64: As<T>
+{
+    match option {
+        ConversionBehavior::Default => Ok(ConversionResult::Success(try!(convert_fn(cx, value)).cast())),
+        ConversionBehavior::EnforceRange => enforce_range(cx, try!(ToNumber(cx, value))),
+        ConversionBehavior::Clamp => Ok(ConversionResult::Success(clamp_to(try!(ToNumber(cx, value))))),
+    }
+}
+
+// https://heycam.github.io/webidl/#es-boolean
+impl ToJSValConvertible for bool {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(BooleanValue(*self));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-boolean
+impl FromJSValConvertible for bool {
+    type Config = ();
+    unsafe fn from_jsval(_cx: *mut JSContext, val: JS::HandleValue, _option: ()) -> Result<ConversionResult<bool>, ()> {
+        Ok(ToBoolean(val)).map(ConversionResult::Success)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-byte
+impl ToJSValConvertible for i8 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(Int32Value(*self as i32));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-byte
+impl FromJSValConvertible for i8 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<i8>, ()> {
+        convert_int_from_jsval(cx, val, option, ToInt32)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-octet
+impl ToJSValConvertible for u8 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(Int32Value(*self as i32));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-octet
+impl FromJSValConvertible for u8 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<u8>, ()> {
+        convert_int_from_jsval(cx, val, option, ToInt32)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-short
+impl ToJSValConvertible for i16 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(Int32Value(*self as i32));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-short
+impl FromJSValConvertible for i16 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<i16>, ()> {
+        convert_int_from_jsval(cx, val, option, ToInt32)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-unsigned-short
+impl ToJSValConvertible for u16 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(Int32Value(*self as i32));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-unsigned-short
+impl FromJSValConvertible for u16 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<u16>, ()> {
+        convert_int_from_jsval(cx, val, option, ToUint16)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-long
+impl ToJSValConvertible for i32 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(Int32Value(*self));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-long
+impl FromJSValConvertible for i32 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<i32>, ()> {
+        convert_int_from_jsval(cx, val, option, ToInt32)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-unsigned-long
+impl ToJSValConvertible for u32 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(UInt32Value(*self));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-unsigned-long
+impl FromJSValConvertible for u32 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<u32>, ()> {
+        convert_int_from_jsval(cx, val, option, ToUint32)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-long-long
+impl ToJSValConvertible for i64 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(RUST_JS_NumberValue(*self as f64));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-long-long
+impl FromJSValConvertible for i64 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<i64>, ()> {
+        convert_int_from_jsval(cx, val, option, ToInt64)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-unsigned-long-long
+impl ToJSValConvertible for u64 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(RUST_JS_NumberValue(*self as f64));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-unsigned-long-long
+impl FromJSValConvertible for u64 {
+    type Config = ConversionBehavior;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         val: JS::HandleValue,
+                         option: ConversionBehavior)
+                         -> Result<ConversionResult<u64>, ()> {
+        convert_int_from_jsval(cx, val, option, ToUint64)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-float
+impl ToJSValConvertible for f32 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(RUST_JS_NumberValue(*self as f64));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-float
+impl FromJSValConvertible for f32 {
+    type Config = ();
+    unsafe fn from_jsval(cx: *mut JSContext, val: JS::HandleValue, _option: ()) -> Result<ConversionResult<f32>, ()> {
+        let result = ToNumber(cx, val);
+        result.map(|f| f as f32).map(ConversionResult::Success)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-double
+impl ToJSValConvertible for f64 {
+    #[inline]
+    unsafe fn to_jsval(&self, _cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(RUST_JS_NumberValue(*self));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-double
+impl FromJSValConvertible for f64 {
+    type Config = ();
+    unsafe fn from_jsval(cx: *mut JSContext, val: JS::HandleValue, _option: ()) -> Result<ConversionResult<f64>, ()> {
+        ToNumber(cx, val).map(ConversionResult::Success)
+    }
+}
+
+/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a
+/// `String`.
+pub unsafe fn latin1_to_string(cx: *mut JSContext, s: *mut JSString) -> String {
+    assert!(JS_StringHasLatin1Chars(s));
+
+    let mut length = 0;
+    let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), s, &mut length);
+    assert!(!chars.is_null());
+
+    let chars = slice::from_raw_parts(chars, length as usize);
+    let mut s = String::with_capacity(length as usize);
+    s.extend(chars.iter().map(|&c| c as char));
+    s
+}
+
+// https://heycam.github.io/webidl/#es-USVString
+impl ToJSValConvertible for str {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        let mut string_utf16: Vec<u16> = Vec::with_capacity(self.len());
+        string_utf16.extend(self.encode_utf16());
+        let jsstr = JS_NewUCStringCopyN(cx,
+                                        string_utf16.as_ptr(),
+                                        string_utf16.len() as libc::size_t);
+        if jsstr.is_null() {
+            panic!("JS_NewUCStringCopyN failed");
+        }
+        rval.set(StringValue(&*jsstr));
+    }
+}
+
+// https://heycam.github.io/webidl/#es-USVString
+impl ToJSValConvertible for String {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        (**self).to_jsval(cx, rval);
+    }
+}
+
+// https://heycam.github.io/webidl/#es-USVString
+impl FromJSValConvertible for String {
+    type Config = ();
+    unsafe fn from_jsval(cx: *mut JSContext, value: JS::HandleValue, _: ()) -> Result<ConversionResult<String>, ()> {
+        let jsstr = ToString(cx, value);
+        if jsstr.is_null() {
+            debug!("ToString failed");
+            return Err(());
+        }
+        if JS_StringHasLatin1Chars(jsstr) {
+            return Ok(latin1_to_string(cx, jsstr)).map(ConversionResult::Success);
+        }
+
+        let mut length = 0;
+        let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr, &mut length);
+        assert!(!chars.is_null());
+        let char_vec = slice::from_raw_parts(chars, length as usize);
+        Ok(String::from_utf16_lossy(char_vec)).map(ConversionResult::Success)
+    }
+}
+
+impl<T: ToJSValConvertible> ToJSValConvertible for Option<T> {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        match self {
+            &Some(ref value) => value.to_jsval(cx, rval),
+            &None => rval.set(NullValue()),
+        }
+    }
+}
+
+impl<T: ToJSValConvertible> ToJSValConvertible for Rc<T> {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        (**self).to_jsval(cx, rval)
+    }
+}
+
+impl<T: FromJSValConvertible> FromJSValConvertible for Option<T> {
+    type Config = T::Config;
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         value: JS::HandleValue,
+                         option: T::Config)
+                         -> Result<ConversionResult<Option<T>>, ()> {
+        if value.get().is_null_or_undefined() {
+            Ok(ConversionResult::Success(None))
+        } else {
+            Ok(match try!(FromJSValConvertible::from_jsval(cx, value, option)) {
+                ConversionResult::Success(v) => ConversionResult::Success(Some(v)),
+                ConversionResult::Failure(v) => ConversionResult::Failure(v),
+            })
+        }
+    }
+}
+
+// https://heycam.github.io/webidl/#es-sequence
+impl<T: ToJSValConvertible> ToJSValConvertible for Vec<T> {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rooted!(in(cx) let js_array = JS_NewArrayObject1(cx, self.len() as libc::size_t));
+        assert!(!js_array.handle().is_null());
+
+        rooted!(in(cx) let mut val = UndefinedValue());
+        for (index, obj) in self.iter().enumerate() {
+            obj.to_jsval(cx, val.handle_mut());
+
+            assert!(JS_DefineElement(
+                cx,
+                js_array.handle(),
+                index as u32,
+                val.handle(),
+                JSPROP_ENUMERATE as _
+            ));
+        }
+
+        rval.set(ObjectValue(js_array.handle().get()));
+    }
+}
+
+/// Rooting guard for the iterator field of ForOfIterator.
+/// Behaves like RootedGuard (roots on creation, unroots on drop),
+/// but borrows and allows access to the whole ForOfIterator, so
+/// that methods on ForOfIterator can still be used through it.
+struct ForOfIteratorGuard<'a> {
+    root: &'a mut JS::ForOfIterator
+}
+
+impl<'a> ForOfIteratorGuard<'a> {
+    fn new(cx: *mut JSContext, root: &'a mut JS::ForOfIterator) -> Self {
+        unsafe {
+            root.iterator.register_with_root_lists(cx);
+        }
+        ForOfIteratorGuard {
+            root: root
+        }
+    }
+}
+
+impl<'a> Drop for ForOfIteratorGuard<'a> {
+    fn drop(&mut self) {
+        unsafe {
+            self.root.iterator.remove_from_root_stack();
+        }
+    }
+}
+
+impl<C: Clone, T: FromJSValConvertible<Config=C>> FromJSValConvertible for Vec<T> {
+    type Config = C;
+
+    unsafe fn from_jsval(cx: *mut JSContext,
+                         value: JS::HandleValue,
+                         option: C)
+                         -> Result<ConversionResult<Vec<T>>, ()> {
+        let mut iterator = JS::ForOfIterator {
+            cx_: cx,
+            iterator: JS::RootedObject::new_unrooted(),
+            index: ::std::u32::MAX, // NOT_ARRAY
+        };
+        let iterator = ForOfIteratorGuard::new(cx, &mut iterator);
+        let iterator = &mut *iterator.root;
+
+        if !iterator.init(value, JS::ForOfIterator_NonIterableBehavior::AllowNonIterable) {
+            return Err(())
+        }
+
+        if iterator.iterator.ptr.is_null() {
+            return Ok(ConversionResult::Failure("Value is not iterable".into()));
+        }
+
+        let mut ret = vec![];
+
+        loop {
+            let mut done = false;
+            rooted!(in(cx) let mut val = UndefinedValue());
+            if !iterator.next(val.handle_mut(), &mut done) {
+                return Err(())
+            }
+
+            if done {
+                break;
+            }
+
+            ret.push(match try!(T::from_jsval(cx, val.handle(), option.clone())) {
+                ConversionResult::Success(v) => v,
+                ConversionResult::Failure(e) => return Ok(ConversionResult::Failure(e)),
+            });
+        }
+
+        Ok(ret).map(ConversionResult::Success)
+    }
+}
+
+// https://heycam.github.io/webidl/#es-object
+impl ToJSValConvertible for *mut JSObject {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(ObjectOrNullValue(*self));
+        maybe_wrap_object_or_null_value(cx, rval);
+    }
+}
+
+// https://heycam.github.io/webidl/#es-object
+#[cfg(feature = "nonzero")]
+impl ToJSValConvertible for NonZero<*mut JSObject> {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        use rust::maybe_wrap_object_value;
+        rval.set(ObjectValue(self.get()));
+        maybe_wrap_object_value(cx, rval);
+    }
+}
+
+// https://heycam.github.io/webidl/#es-object
+impl ToJSValConvertible for Heap<*mut JSObject> {
+    #[inline]
+    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: JS::MutableHandleValue) {
+        rval.set(ObjectOrNullValue(self.get()));
+        maybe_wrap_object_or_null_value(cx, rval);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/error.rs
@@ -0,0 +1,73 @@
+/* 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/. */
+
+//! Functions to throw JavaScript exceptions from Rust.
+
+#![deny(missing_docs)]
+
+use jsapi::root::*;
+use libc;
+use std::ffi::CString;
+use std::{mem, os, ptr};
+
+/// Format string used to throw javascript errors.
+static ERROR_FORMAT_STRING_STRING: [libc::c_char; 4] = [
+    '{' as libc::c_char,
+    '0' as libc::c_char,
+    '}' as libc::c_char,
+    0 as libc::c_char,
+];
+
+/// Format string struct used to throw `TypeError`s.
+static mut TYPE_ERROR_FORMAT_STRING: JSErrorFormatString = JSErrorFormatString {
+    name: b"RUSTMSG_TYPE_ERROR\0" as *const _ as *const libc::c_char,
+    format: &ERROR_FORMAT_STRING_STRING as *const libc::c_char,
+    argCount: 1,
+    exnType: JSExnType::JSEXN_TYPEERR as i16,
+};
+
+/// Format string struct used to throw `RangeError`s.
+static mut RANGE_ERROR_FORMAT_STRING: JSErrorFormatString = JSErrorFormatString {
+    name: b"RUSTMSG_RANGE_ERROR\0" as *const _ as *const libc::c_char,
+    format: &ERROR_FORMAT_STRING_STRING as *const libc::c_char,
+    argCount: 1,
+    exnType: JSExnType::JSEXN_RANGEERR as i16,
+};
+
+/// Callback used to throw javascript errors.
+/// See throw_js_error for info about error_number.
+unsafe extern "C" fn get_error_message(_user_ref: *mut os::raw::c_void,
+                                       error_number: libc::c_uint)
+                                       -> *const JSErrorFormatString {
+    let num: JSExnType = mem::transmute(error_number);
+    match num {
+        JSExnType::JSEXN_TYPEERR => &TYPE_ERROR_FORMAT_STRING as *const JSErrorFormatString,
+        JSExnType::JSEXN_RANGEERR => &RANGE_ERROR_FORMAT_STRING as *const JSErrorFormatString,
+        _ => panic!("Bad js error number given to get_error_message: {}",
+                    error_number),
+    }
+}
+
+/// Helper fn to throw a javascript error with the given message and number.
+/// Reuse the jsapi error codes to distinguish the error_number
+/// passed back to the get_error_message callback.
+/// c_uint is u32, so this cast is safe, as is casting to/from i32 from there.
+unsafe fn throw_js_error(cx: *mut JSContext, error: &str, error_number: u32) {
+    let error = CString::new(error).unwrap();
+    JS_ReportErrorNumberUTF8(cx,
+                             Some(get_error_message),
+                             ptr::null_mut(),
+                             error_number,
+                             error.as_ptr());
+}
+
+/// Throw a `TypeError` with the given message.
+pub unsafe fn throw_type_error(cx: *mut JSContext, error: &str) {
+    throw_js_error(cx, error, JSExnType::JSEXN_TYPEERR as u32);
+}
+
+/// Throw a `RangeError` with the given message.
+pub unsafe fn throw_range_error(cx: *mut JSContext, error: &str) {
+    throw_js_error(cx, error, JSExnType::JSEXN_RANGEERR as u32);
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/glue.rs
@@ -0,0 +1,348 @@
+use jsapi::root::*;
+use heap::Heap;
+use std::os::raw::c_void;
+
+
+pub enum Action { }
+unsafe impl Sync for ProxyTraps {}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct ProxyTraps {
+    pub enter: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                          proxy: JS::HandleObject,
+                                                          id: JS::HandleId,
+                                                          action: Action,
+                                                          bp: *mut bool)
+                                                          -> bool>,
+    pub getOwnPropertyDescriptor:
+        ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                   proxy: JS::HandleObject,
+                                                   id: JS::HandleId,
+                                                   desc: JS::MutableHandle<JS::PropertyDescriptor>)
+                                                   -> bool>,
+    pub defineProperty:
+        ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                   proxy: JS::HandleObject,
+                                                   id: JS::HandleId,
+                                                   desc: JS::Handle<JS::PropertyDescriptor>,
+                                                   result: *mut JS::ObjectOpResult)
+                                                   -> bool>,
+    pub ownPropertyKeys: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                                    proxy: JS::HandleObject,
+                                                                    props: *mut JS::AutoIdVector)
+                                                                    -> bool>,
+    pub delete_: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                            proxy: JS::HandleObject,
+                                                            id: JS::HandleId,
+                                                            result: *mut JS::ObjectOpResult)
+                                                            -> bool>,
+    pub enumerate: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                              proxy: JS::HandleObject,
+                                                              objp: JS::MutableHandleObject)
+                                                              -> bool>,
+    pub getPrototypeIfOrdinary:
+        ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                   proxy: JS::HandleObject,
+                                                   isOrdinary: *mut bool,
+                                                   protop: JS::MutableHandleObject)
+                                                   -> bool>,
+    pub preventExtensions:
+        ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                   proxy: JS::HandleObject,
+                                                   result: *mut JS::ObjectOpResult)
+                                                   -> bool>,
+    pub isExtensible: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                                 proxy: JS::HandleObject,
+                                                                 succeeded: *mut bool)
+                                                                 -> bool>,
+    pub has: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                        proxy: JS::HandleObject,
+                                                        id: JS::HandleId,
+                                                        bp: *mut bool)
+                                                        -> bool>,
+    pub get: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                        proxy: JS::HandleObject,
+                                                        receiver: JS::HandleValue,
+                                                        id: JS::HandleId,
+                                                        vp: JS::MutableHandleValue)
+                                                        -> bool>,
+    pub set: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                        proxy: JS::HandleObject,
+                                                        id: JS::HandleId,
+                                                        v: JS::HandleValue,
+                                                        receiver: JS::HandleValue,
+                                                        result: *mut JS::ObjectOpResult)
+                                                        -> bool>,
+    pub call: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                         proxy: JS::HandleObject,
+                                                         args: *const JS::CallArgs)
+                                                         -> bool>,
+    pub construct: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                              proxy: JS::HandleObject,
+                                                              args: *const JS::CallArgs)
+                                                              -> bool>,
+    pub getPropertyDescriptor:
+        ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                   proxy: JS::HandleObject,
+                                                   id: JS::HandleId,
+                                                   desc: JS::MutableHandle<JS::PropertyDescriptor>)
+                                                   -> bool>,
+    pub hasOwn: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                           proxy: JS::HandleObject,
+                                                           id: JS::HandleId,
+                                                           bp: *mut bool)
+                                                           -> bool>,
+    pub getOwnEnumerablePropertyKeys:
+        ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                   proxy: JS::HandleObject,
+                                                   props: *mut JS::AutoIdVector)
+                                                   -> bool>,
+    pub nativeCall: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                               test: JS::IsAcceptableThis,
+                                                               _impl: JS::NativeImpl,
+                                                               args: JS::CallArgs)
+                                                               -> bool>,
+    pub hasInstance: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                                proxy: JS::HandleObject,
+                                                                v: JS::MutableHandleValue,
+                                                                bp: *mut bool)
+                                                                -> bool>,
+    pub objectClassIs: ::std::option::Option<unsafe extern "C" fn(obj: JS::HandleObject,
+                                                                  classValue: js::ESClass,
+                                                                  cx: *mut JSContext)
+                                                                  -> bool>,
+    pub className: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                              proxy: JS::HandleObject)
+                                                              -> *const i8>,
+    pub fun_toString: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                                 proxy: JS::HandleObject,
+                                                                 indent: u32)
+                                                                 -> *mut JSString>,
+    pub boxedValue_unbox: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                                     proxy: JS::HandleObject,
+                                                                     vp: JS::MutableHandleValue)
+                                                                     -> bool>,
+    pub defaultValue: ::std::option::Option<unsafe extern "C" fn(cx: *mut JSContext,
+                                                                 obj: JS::HandleObject,
+                                                                 hint: JSType,
+                                                                 vp: JS::MutableHandleValue)
+                                                                 -> bool>,
+    pub trace:
+        ::std::option::Option<unsafe extern "C" fn(trc: *mut JSTracer, proxy: *mut JSObject)>,
+    pub finalize:
+        ::std::option::Option<unsafe extern "C" fn(fop: *mut JSFreeOp, proxy: *mut JSObject)>,
+    pub objectMoved:
+        ::std::option::Option<unsafe extern "C" fn(proxy: *mut JSObject, old: *const JSObject)>,
+    pub isCallable: ::std::option::Option<unsafe extern "C" fn(obj: *mut JSObject) -> bool>,
+    pub isConstructor: ::std::option::Option<unsafe extern "C" fn(obj: *mut JSObject) -> bool>,
+}
+impl ::std::default::Default for ProxyTraps {
+    fn default() -> ProxyTraps {
+        unsafe { ::std::mem::zeroed() }
+    }
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct WrapperProxyHandler {
+    pub mTraps: ProxyTraps,
+}
+impl ::std::default::Default for WrapperProxyHandler {
+    fn default() -> WrapperProxyHandler {
+        unsafe { ::std::mem::zeroed() }
+    }
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct ForwardingProxyHandler {
+    pub mTraps: ProxyTraps,
+    pub mExtra: *const ::libc::c_void,
+}
+impl ::std::default::Default for ForwardingProxyHandler {
+    fn default() -> ForwardingProxyHandler {
+        unsafe { ::std::mem::zeroed() }
+    }
+}
+
+extern "C" {
+    pub fn InvokeGetOwnPropertyDescriptor(handler: *const ::libc::c_void,
+                                          cx: *mut JSContext,
+                                          proxy: JS::HandleObject,
+                                          id: JS::HandleId,
+                                          desc: JS::MutableHandle<JS::PropertyDescriptor>)
+                                          -> bool;
+    pub fn InvokeHasOwn(handler: *const ::libc::c_void,
+                        cx: *mut JSContext,
+                        proxy: JS::HandleObject,
+                        id: JS::HandleId,
+                        bp: *mut bool)
+                        -> bool;
+    pub fn RUST_JS_NumberValue(d: f64) -> JS::Value;
+    pub fn RUST_FUNCTION_VALUE_TO_JITINFO(v: JS::Value) -> *const JSJitInfo;
+    pub fn CreateCallArgsFromVp(argc: u32, v: *mut JS::Value) -> JS::CallArgs;
+    pub fn CallJitGetterOp(info: *const JSJitInfo,
+                           cx: *mut JSContext,
+                           thisObj: JS::HandleObject,
+                           specializedThis: *mut ::libc::c_void,
+                           argc: u32,
+                           vp: *mut JS::Value)
+                           -> bool;
+    pub fn CallJitSetterOp(info: *const JSJitInfo,
+                           cx: *mut JSContext,
+                           thisObj: JS::HandleObject,
+                           specializedThis: *mut ::libc::c_void,
+                           argc: u32,
+                           vp: *mut JS::Value)
+                           -> bool;
+    pub fn CallJitMethodOp(info: *const JSJitInfo,
+                           cx: *mut JSContext,
+                           thisObj: JS::HandleObject,
+                           specializedThis: *mut ::libc::c_void,
+                           argc: u32,
+                           vp: *mut JS::Value)
+                           -> bool;
+    pub fn CreateProxyHandler(aTraps: *const ProxyTraps,
+                              aExtra: *const ::libc::c_void)
+                              -> *const ::libc::c_void;
+    pub fn CreateWrapperProxyHandler(aTraps: *const ProxyTraps) -> *const ::libc::c_void;
+    pub fn CreateRustJSPrincipal(origin: *const ::libc::c_void,
+                                  destroy: Option<unsafe extern "C" fn
+                                                  (principal: *mut JSPrincipals)>,
+                                  write: Option<unsafe extern "C" fn
+                                                (cx: *mut JSContext,
+                                                 writer: *mut JSStructuredCloneWriter)
+                                                 -> bool>)
+-> *mut JSPrincipals;
+    pub fn GetPrincipalOrigin(principal: *const JSPrincipals) -> *const ::libc::c_void;
+    pub fn GetCrossCompartmentWrapper() -> *const ::libc::c_void;
+    pub fn GetSecurityWrapper() -> *const ::libc::c_void;
+    pub fn NewCompileOptions(aCx: *mut JSContext,
+                             aFile: *const ::libc::c_char,
+                             aLine: u32)
+                             -> *mut JS::ReadOnlyCompileOptions;
+    pub fn DeleteCompileOptions(aOpts: *mut JS::ReadOnlyCompileOptions);
+    pub fn NewProxyObject(aCx: *mut JSContext,
+                          aHandler: *const ::libc::c_void,
+                          aPriv: JS::HandleValue,
+                          proto: *mut JSObject,
+                          parent: *mut JSObject,
+                          call: *mut JSObject,
+                          construct: *mut JSObject)
+                          -> *mut JSObject;
+    pub fn WrapperNew(aCx: *mut JSContext,
+                      aObj: JS::HandleObject,
+                      aHandler: *const ::libc::c_void,
+                      aClass: *const JSClass,
+                      aSingleton: bool)
+                      -> *mut JSObject;
+    pub fn NewWindowProxy(aCx: *mut JSContext,
+                          aObj: JS::HandleObject,
+                          aHandler: *const ::libc::c_void)
+                          -> *mut JSObject;
+    pub fn GetWindowProxyClass() -> *const js::Class;
+    pub fn GetProxyPrivate(obj: *mut JSObject) -> JS::Value;
+    pub fn SetProxyPrivate(obj: *mut JSObject, private: *const JS::Value);
+    pub fn GetProxyReservedSlot(obj: *mut JSObject, slot: u32) -> JS::Value;
+    pub fn SetProxyReservedSlot(obj: *mut JSObject, slot: u32, val: *const JS::Value);
+    pub fn RUST_JSID_IS_INT(id: JS::HandleId) -> bool;
+    pub fn RUST_JSID_TO_INT(id: JS::HandleId) -> i32;
+    pub fn int_to_jsid(i: i32) -> jsid;
+    pub fn RUST_JSID_IS_STRING(id: JS::HandleId) -> bool;
+    pub fn RUST_JSID_TO_STRING(id: JS::HandleId) -> *mut JSString;
+    pub fn RUST_SYMBOL_TO_JSID(sym: *mut JS::Symbol) -> jsid;
+    pub fn RUST_SET_JITINFO(func: *mut JSFunction, info: *const JSJitInfo);
+    pub fn RUST_INTERNED_STRING_TO_JSID(cx: *mut JSContext, str: *mut JSString) -> jsid;
+    pub fn RUST_js_GetErrorMessage(userRef: *mut ::libc::c_void,
+                                   errorNumber: u32)
+                                   -> *const JSErrorFormatString;
+    pub fn IsProxyHandlerFamily(obj: *mut JSObject) -> u8;
+    pub fn GetProxyHandlerExtra(obj: *mut JSObject) -> *const ::libc::c_void;
+    pub fn GetProxyHandler(obj: *mut JSObject) -> *const ::libc::c_void;
+    pub fn ReportError(aCx: *mut JSContext, aError: *const i8);
+    pub fn IsWrapper(obj: *mut JSObject) -> bool;
+    pub fn UnwrapObject(obj: *mut JSObject, stopAtOuter: u8) -> *mut JSObject;
+    pub fn UncheckedUnwrapObject(obj: *mut JSObject, stopAtOuter: u8) -> *mut JSObject;
+    pub fn CreateAutoIdVector(cx: *mut JSContext) -> *mut JS::AutoIdVector;
+    pub fn AppendToAutoIdVector(v: *mut JS::AutoIdVector, id: jsid) -> bool;
+    pub fn SliceAutoIdVector(v: *const JS::AutoIdVector, length: *mut usize) -> *const jsid;
+    pub fn DestroyAutoIdVector(v: *mut JS::AutoIdVector);
+    pub fn CreateAutoObjectVector(aCx: *mut JSContext) -> *mut JS::AutoObjectVector;
+    pub fn AppendToAutoObjectVector(v: *mut JS::AutoObjectVector, obj: *mut JSObject) -> bool;
+    pub fn DeleteAutoObjectVector(v: *mut JS::AutoObjectVector);
+    pub fn CollectServoSizes(rt: *mut JSRuntime, sizes: *mut JS::ServoSizes) -> bool;
+    pub fn CallIdTracer(trc: *mut JSTracer, idp: *mut Heap<jsid>, name: *const ::libc::c_char);
+    pub fn CallValueTracer(trc: *mut JSTracer,
+                           valuep: *mut Heap<JS::Value>,
+                           name: *const ::libc::c_char);
+    pub fn CallObjectTracer(trc: *mut JSTracer,
+                            objp: *mut Heap<*mut JSObject>,
+                            name: *const ::libc::c_char);
+    pub fn CallStringTracer(trc: *mut JSTracer,
+                            strp: *mut Heap<*mut JSString>,
+                            name: *const ::libc::c_char);
+    pub fn CallScriptTracer(trc: *mut JSTracer,
+                            scriptp: *mut Heap<*mut JSScript>,
+                            name: *const ::libc::c_char);
+    pub fn CallFunctionTracer(trc: *mut JSTracer,
+                              funp: *mut Heap<*mut JSFunction>,
+                              name: *const ::libc::c_char);
+    pub fn CallUnbarrieredObjectTracer(trc: *mut JSTracer,
+                                       objp: *mut *mut JSObject,
+                                       name: *const ::libc::c_char);
+    pub fn GetProxyHandlerFamily() -> *const c_void;
+
+    pub fn GetInt8ArrayLengthAndData(obj: *mut JSObject,
+                                     length: *mut u32,
+                                     isSharedMemory: *mut bool,
+                                     data: *mut *mut i8);
+    pub fn GetUint8ArrayLengthAndData(obj: *mut JSObject,
+                                      length: *mut u32,
+                                      isSharedMemory: *mut bool,
+                                      data: *mut *mut u8);
+    pub fn GetUint8ClampedArrayLengthAndData(obj: *mut JSObject,
+                                             length: *mut u32,
+                                             isSharedMemory: *mut bool,
+                                             data: *mut *mut u8);
+    pub fn GetInt16ArrayLengthAndData(obj: *mut JSObject,
+                                      length: *mut u32,
+                                      isSharedMemory: *mut bool,
+                                      data: *mut *mut i16);
+    pub fn GetUint16ArrayLengthAndData(obj: *mut JSObject,
+                                       length: *mut u32,
+                                       isSharedMemory: *mut bool,
+                                       data: *mut *mut u16);
+    pub fn GetInt32ArrayLengthAndData(obj: *mut JSObject,
+                                      length: *mut u32,
+                                      isSharedMemory: *mut bool,
+                                      data: *mut *mut i32);
+    pub fn GetUint32ArrayLengthAndData(obj: *mut JSObject,
+                                       length: *mut u32,
+                                       isSharedMemory: *mut bool,
+                                       data: *mut *mut u32);
+    pub fn GetFloat32ArrayLengthAndData(obj: *mut JSObject,
+                                        length: *mut u32,
+                                        isSharedMemory: *mut bool,
+                                        data: *mut *mut f32);
+    pub fn GetFloat64ArrayLengthAndData(obj: *mut JSObject,
+                                        length: *mut u32,
+                                        isSharedMemory: *mut bool,
+                                        data: *mut *mut f64);
+
+    pub fn NewJSAutoStructuredCloneBuffer(scope: JS::StructuredCloneScope,
+                                          callbacks: *const JSStructuredCloneCallbacks)
+                                          -> *mut JSAutoStructuredCloneBuffer;
+    pub fn DeleteJSAutoStructuredCloneBuffer(buf: *mut JSAutoStructuredCloneBuffer);
+    pub fn GetLengthOfJSStructuredCloneData(data: *mut JSStructuredCloneData) -> usize;
+    pub fn CopyJSStructuredCloneData(src: *mut JSStructuredCloneData, dest: *mut u8);
+    pub fn WriteBytesToJSStructuredCloneData(src: *const u8,
+                                             len: usize,
+                                             dest: *mut JSStructuredCloneData)
+                                             -> bool;
+
+    pub fn IsDebugBuild() -> bool;
+}
+
+#[test]
+fn jsglue_cpp_configured_correctly() {
+    assert_eq!(cfg!(feature = "debugmozjs"), unsafe { IsDebugBuild() });
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/heap.rs
@@ -0,0 +1,172 @@
+use glue;
+use heapsize::HeapSizeOf;
+use jsapi::root::*;
+use rust::GCMethods;
+use std::cell::UnsafeCell;
+use std::ptr;
+
+/// Types that can be traced.
+///
+/// This trait is unsafe; if it is implemented incorrectly, the GC may end up
+/// collecting objects that are still reachable.
+pub unsafe trait Trace {
+    unsafe fn trace(&self, trc: *mut JSTracer);
+}
+
+/**
+ * The Heap<T> class is a heap-stored reference to a JS GC thing. All members of
+ * heap classes that refer to GC things should use Heap<T> (or possibly
+ * TenuredHeap<T>, described below).
+ *
+ * Heap<T> is an abstraction that hides some of the complexity required to
+ * maintain GC invariants for the contained reference. It uses operator
+ * overloading to provide a normal pointer interface, but notifies the GC every
+ * time the value it contains is updated. This is necessary for generational GC,
+ * which keeps track of all pointers into the nursery.
+ *
+ * Heap<T> instances must be traced when their containing object is traced to
+ * keep the pointed-to GC thing alive.
+ *
+ * Heap<T> objects should only be used on the heap. GC references stored on the
+ * C/C++ stack must use Rooted/Handle/MutableHandle instead.
+ *
+ * Type T must be a public GC pointer type.
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct Heap<T: GCMethods + Copy> {
+    ptr: UnsafeCell<T>,
+}
+
+impl<T: GCMethods + Copy> Heap<T> {
+    pub fn new(v: T) -> Heap<T>
+        where Heap<T>: Default
+    {
+        let ptr = Heap::default();
+        ptr.set(v);
+        ptr
+    }
+
+    pub fn set(&self, v: T) {
+        unsafe {
+            let ptr = self.ptr.get();
+            let prev = *ptr;
+            *ptr = v;
+            T::post_barrier(ptr, prev, v);
+        }
+    }
+
+    pub fn get(&self) -> T {
+        unsafe {
+            *self.ptr.get()
+        }
+    }
+
+    pub unsafe fn get_unsafe(&self) -> *mut T {
+        self.ptr.get()
+    }
+
+    pub fn handle(&self) -> JS::Handle<T> {
+        unsafe {
+            JS::Handle::from_marked_location(self.ptr.get() as *const _)
+        }
+    }
+
+    pub fn handle_mut(&self) -> JS::MutableHandle<T> {
+        unsafe {
+            JS::MutableHandle::from_marked_location(self.ptr.get())
+        }
+    }
+}
+
+impl<T: GCMethods + Copy> Clone for Heap<T>
+    where Heap<T>: Default
+{
+    fn clone(&self) -> Self {
+        Heap::new(self.get())
+    }
+}
+
+impl<T: GCMethods + Copy + PartialEq> PartialEq for Heap<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.get() == other.get()
+    }
+}
+
+impl<T> Default for Heap<*mut T>
+    where *mut T: GCMethods + Copy
+{
+    fn default() -> Heap<*mut T> {
+        Heap {
+            ptr: UnsafeCell::new(ptr::null_mut())
+        }
+    }
+}
+
+impl Default for Heap<JS::Value> {
+    fn default() -> Heap<JS::Value> {
+        Heap {
+            ptr: UnsafeCell::new(JS::Value::default())
+        }
+    }
+}
+
+impl<T: GCMethods + Copy> Drop for Heap<T> {
+    fn drop(&mut self) {
+        unsafe {
+            let prev = self.ptr.get();
+            T::post_barrier(prev, *prev, T::initial());
+        }
+    }
+}
+
+// Creates a C string literal `$str`.
+macro_rules! c_str {
+    ($str:expr) => {
+        concat!($str, "\0").as_ptr() as *const ::std::os::raw::c_char
+    }
+}
+
+unsafe impl Trace for Heap<*mut JSFunction> {
+    unsafe fn trace(&self, trc: *mut JSTracer) {
+        glue::CallFunctionTracer(trc, self as *const _ as *mut Self, c_str!("function"));
+    }
+}
+
+unsafe impl Trace for Heap<*mut JSObject> {
+    unsafe fn trace(&self, trc: *mut JSTracer) {
+        glue::CallObjectTracer(trc, self as *const _ as *mut Self, c_str!("object"));
+    }
+}
+
+unsafe impl Trace for Heap<*mut JSScript> {
+    unsafe fn trace(&self, trc: *mut JSTracer) {
+        glue::CallScriptTracer(trc, self as *const _ as *mut Self, c_str!("script"));
+    }
+}
+
+unsafe impl Trace for Heap<*mut JSString> {
+    unsafe fn trace(&self, trc: *mut JSTracer) {
+        glue::CallStringTracer(trc, self as *const _ as *mut Self, c_str!("string"));
+    }
+}
+
+unsafe impl Trace for Heap<JS::Value> {
+    unsafe fn trace(&self, trc: *mut JSTracer) {
+        glue::CallValueTracer(trc, self as *const _ as *mut Self, c_str!("value"));
+    }
+}
+
+unsafe impl Trace for Heap<jsid> {
+    unsafe fn trace(&self, trc: *mut JSTracer) {
+        glue::CallIdTracer(trc, self as *const _ as *mut Self, c_str!("id"));
+    }
+}
+
+// This is measured properly by the heap measurement implemented in
+// SpiderMonkey.
+impl<T: Copy + GCMethods> HeapSizeOf for Heap<T> {
+    fn heap_size_of_children(&self) -> usize {
+        0
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/jsapi.rs
@@ -0,0 +1,5 @@
+#[cfg(feature = "debugmozjs")]
+include!(concat!(env!("OUT_DIR"), "/jsapi_debug.rs"));
+
+#[cfg(not(feature = "debugmozjs"))]
+include!(concat!(env!("OUT_DIR"), "/jsapi.rs"));
new file mode 100644
--- /dev/null
+++ b/js/rust/src/jsglue.cpp
@@ -0,0 +1,940 @@
+/* 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/. */
+
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include "js-config.h"
+
+#ifdef JS_DEBUG
+// A hack for MFBT. Guard objects need this to work.
+#define DEBUG 1
+#endif
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Proxy.h"
+#include "js/Class.h"
+#include "jswrapper.h"
+#include "js/MemoryMetrics.h"
+#include "js/Principals.h"
+#include "assert.h"
+
+struct ProxyTraps {
+    bool (*enter)(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+                  js::BaseProxyHandler::Action action, bool *bp);
+
+    bool (*getOwnPropertyDescriptor)(JSContext *cx, JS::HandleObject proxy,
+                                     JS::HandleId id,
+                                     JS::MutableHandle<JS::PropertyDescriptor> desc);
+    bool (*defineProperty)(JSContext *cx, JS::HandleObject proxy,
+                           JS::HandleId id,
+                           JS::Handle<JS::PropertyDescriptor> desc,
+                           JS::ObjectOpResult &result);
+    bool (*ownPropertyKeys)(JSContext *cx, JS::HandleObject proxy,
+                            JS::AutoIdVector &props);
+    bool (*delete_)(JSContext *cx, JS::HandleObject proxy,
+                    JS::HandleId id, JS::ObjectOpResult &result);
+
+    JSObject* (*enumerate)(JSContext *cx, JS::HandleObject proxy);
+
+    bool (*getPrototypeIfOrdinary)(JSContext *cx, JS::HandleObject proxy,
+                                   bool *isOrdinary, JS::MutableHandleObject protop);
+    // getPrototype
+    // setPrototype
+    // setImmutablePrototype
+
+    bool (*preventExtensions)(JSContext *cx, JS::HandleObject proxy,
+                              JS::ObjectOpResult &result);
+
+    bool (*isExtensible)(JSContext *cx, JS::HandleObject proxy, bool *succeeded);
+
+    bool (*has)(JSContext *cx, JS::HandleObject proxy,
+                JS::HandleId id, bool *bp);
+    bool (*get)(JSContext *cx, JS::HandleObject proxy, JS::HandleValue receiver,
+                JS::HandleId id, JS::MutableHandleValue vp);
+    bool (*set)(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+                JS::HandleValue v, JS::HandleValue receiver,
+                JS::ObjectOpResult &result);
+
+    bool (*call)(JSContext *cx, JS::HandleObject proxy,
+                 const JS::CallArgs &args);
+    bool (*construct)(JSContext *cx, JS::HandleObject proxy,
+                      const JS::CallArgs &args);
+
+    bool (*getPropertyDescriptor)(JSContext *cx, JS::HandleObject proxy,
+                                  JS::HandleId id,
+                                  JS::MutableHandle<JS::PropertyDescriptor> desc);
+    bool (*hasOwn)(JSContext *cx, JS::HandleObject proxy,
+                   JS::HandleId id, bool *bp);
+    bool (*getOwnEnumerablePropertyKeys)(JSContext *cx, JS::HandleObject proxy,
+                                         JS::AutoIdVector &props);
+    bool (*nativeCall)(JSContext *cx, JS::IsAcceptableThis test,
+                       JS::NativeImpl impl, JS::CallArgs args);
+    bool (*hasInstance)(JSContext *cx, JS::HandleObject proxy,
+                        JS::MutableHandleValue v, bool *bp);
+    bool (*objectClassIs)(JS::HandleObject obj, js::ESClass classValue,
+                          JSContext *cx);
+    const char *(*className)(JSContext *cx, JS::HandleObject proxy);
+    JSString* (*fun_toString)(JSContext *cx, JS::HandleObject proxy,
+                              bool isToString);
+    //bool (*regexp_toShared)(JSContext *cx, JS::HandleObject proxy, RegExpGuard *g);
+    bool (*boxedValue_unbox)(JSContext *cx, JS::HandleObject proxy,
+                             JS::MutableHandleValue vp);
+    bool (*defaultValue)(JSContext *cx, JS::HandleObject obj, JSType hint, JS::MutableHandleValue vp);
+    void (*trace)(JSTracer *trc, JSObject *proxy);
+    void (*finalize)(JSFreeOp *fop, JSObject *proxy);
+    void (*objectMoved)(JSObject *proxy, const JSObject *old);
+
+    bool (*isCallable)(JSObject *obj);
+    bool (*isConstructor)(JSObject *obj);
+
+    // watch
+    // unwatch
+    // getElements
+
+    // weakmapKeyDelegate
+    // isScripted
+};
+
+static int HandlerFamily;
+
+#define DEFER_TO_TRAP_OR_BASE_CLASS(_base)                                      \
+                                                                                \
+    /* Standard internal methods. */                                            \
+    virtual JSObject* enumerate(JSContext *cx,                                  \
+                                JS::HandleObject proxy) const override          \
+    {                                                                           \
+        return mTraps.enumerate                                                 \
+               ? mTraps.enumerate(cx, proxy)                                    \
+               : _base::enumerate(cx, proxy);                                   \
+    }                                                                           \
+                                                                                \
+    virtual bool has(JSContext* cx, JS::HandleObject proxy,                     \
+                     JS::HandleId id, bool *bp) const override                  \
+    {                                                                           \
+        return mTraps.has                                                       \
+               ? mTraps.has(cx, proxy, id, bp)                                  \
+               : _base::has(cx, proxy, id, bp);                                 \
+    }                                                                           \
+                                                                                \
+    virtual bool get(JSContext* cx, JS::HandleObject proxy,                     \
+                     JS::HandleValue receiver,                                  \
+                     JS::HandleId id, JS::MutableHandleValue vp) const override \
+    {                                                                           \
+        return mTraps.get                                                       \
+               ? mTraps.get(cx, proxy, receiver, id, vp)                        \
+               : _base::get(cx, proxy, receiver, id, vp);                       \
+    }                                                                           \
+                                                                                \
+    virtual bool set(JSContext* cx, JS::HandleObject proxy,                     \
+                     JS::HandleId id, JS::HandleValue v,                        \
+                     JS::HandleValue receiver,                                  \
+                     JS::ObjectOpResult &result) const override                 \
+    {                                                                           \
+        return mTraps.set                                                       \
+               ? mTraps.set(cx, proxy, id, v, receiver, result)                 \
+               : _base::set(cx, proxy, id, v, receiver, result);                \
+    }                                                                           \
+                                                                                \
+    virtual bool call(JSContext* cx, JS::HandleObject proxy,                    \
+                      const JS::CallArgs &args) const override                  \
+    {                                                                           \
+        return mTraps.call                                                      \
+               ? mTraps.call(cx, proxy, args)                                   \
+               : _base::call(cx, proxy, args);                                  \
+    }                                                                           \
+                                                                                \
+    virtual bool construct(JSContext* cx, JS::HandleObject proxy,               \
+                           const JS::CallArgs &args) const override             \
+    {                                                                           \
+        return mTraps.construct                                                 \
+               ? mTraps.construct(cx, proxy, args)                              \
+               : _base::construct(cx, proxy, args);                             \
+    }                                                                           \
+                                                                                \
+    /* Spidermonkey extensions. */                                              \
+    virtual bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, \
+                        bool* bp) const override                                \
+    {                                                                           \
+        return mTraps.hasOwn                                                    \
+               ? mTraps.hasOwn(cx, proxy, id, bp)                               \
+               : _base::hasOwn(cx, proxy, id, bp);                              \
+    }                                                                           \
+                                                                                \
+    virtual bool getOwnEnumerablePropertyKeys(JSContext* cx,                    \
+                                              JS::HandleObject proxy,           \
+                                              JS::AutoIdVector &props) const override \
+    {                                                                           \
+        return mTraps.getOwnEnumerablePropertyKeys                              \
+               ? mTraps.getOwnEnumerablePropertyKeys(cx, proxy, props)          \
+               : _base::getOwnEnumerablePropertyKeys(cx, proxy, props);         \
+    }                                                                           \
+                                                                                \
+    virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test,           \
+                            JS::NativeImpl impl,                                \
+                            const JS::CallArgs& args) const override            \
+    {                                                                           \
+        return mTraps.nativeCall                                                \
+               ? mTraps.nativeCall(cx, test, impl, args)                        \
+               : _base::nativeCall(cx, test, impl, args);                       \
+    }                                                                           \
+                                                                                \
+    virtual bool hasInstance(JSContext* cx, JS::HandleObject proxy,             \
+                             JS::MutableHandleValue v, bool* bp) const override \
+    {                                                                           \
+        return mTraps.hasInstance                                               \
+               ? mTraps.hasInstance(cx, proxy, v, bp)                           \
+               : _base::hasInstance(cx, proxy, v, bp);                          \
+    }                                                                           \
+                                                                                \
+    virtual const char *className(JSContext *cx, JS::HandleObject proxy) const override\
+    {                                                                           \
+        return mTraps.className                                                 \
+               ? mTraps.className(cx, proxy)                                    \
+               : _base::className(cx, proxy);                                   \
+    }                                                                           \
+                                                                                \
+    virtual JSString* fun_toString(JSContext* cx, JS::HandleObject proxy,       \
+                                   bool isToString) const override              \
+    {                                                                           \
+        return mTraps.fun_toString                                              \
+               ? mTraps.fun_toString(cx, proxy, isToString)                     \
+               : _base::fun_toString(cx, proxy, isToString);                    \
+    }                                                                           \
+                                                                                \
+    virtual bool boxedValue_unbox(JSContext* cx, JS::HandleObject proxy,        \
+                                  JS::MutableHandleValue vp) const override     \
+    {                                                                           \
+        return mTraps.boxedValue_unbox                                          \
+               ? mTraps.boxedValue_unbox(cx, proxy, vp)                         \
+               : _base::boxedValue_unbox(cx, proxy, vp);                        \
+    }                                                                           \
+                                                                                \
+    virtual void trace(JSTracer* trc, JSObject* proxy) const override           \
+    {                                                                           \
+        mTraps.trace                                                            \
+        ? mTraps.trace(trc, proxy)                                              \
+        : _base::trace(trc, proxy);                                             \
+    }                                                                           \
+                                                                                \
+    virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override        \
+    {                                                                           \
+        mTraps.finalize                                                         \
+        ? mTraps.finalize(fop, proxy)                                           \
+        : _base::finalize(fop, proxy);                                          \
+    }                                                                           \
+                                                                                \
+    virtual void objectMoved(JSObject* proxy,                                   \
+                             const JSObject *old) const override                \
+    {                                                                           \
+        mTraps.objectMoved                                                      \
+        ? mTraps.objectMoved(proxy, old)                                        \
+        : _base::objectMoved(proxy, old);                                       \
+    }                                                                           \
+                                                                                \
+    virtual bool isCallable(JSObject* obj) const override                       \
+    {                                                                           \
+        return mTraps.isCallable                                                \
+               ? mTraps.isCallable(obj)                                         \
+               : _base::isCallable(obj);                                        \
+    }                                                                           \
+                                                                                \
+    virtual bool isConstructor(JSObject* obj) const override                    \
+    {                                                                           \
+        return mTraps.isConstructor                                             \
+               ? mTraps.isConstructor(obj)                                      \
+               : _base::isConstructor(obj);                                     \
+    }
+
+class WrapperProxyHandler : public js::Wrapper
+{
+    ProxyTraps mTraps;
+  public:
+    WrapperProxyHandler(const ProxyTraps& aTraps)
+    : js::Wrapper(0), mTraps(aTraps) {}
+
+    virtual bool finalizeInBackground(const JS::Value& priv) const override
+    {
+        return false;
+    }
+
+    DEFER_TO_TRAP_OR_BASE_CLASS(js::Wrapper)
+
+    virtual bool getOwnPropertyDescriptor(JSContext *cx, JS::HandleObject proxy,
+                                          JS::HandleId id,
+                                          JS::MutableHandle<JS::PropertyDescriptor> desc) const override
+    {
+        return mTraps.getOwnPropertyDescriptor
+               ? mTraps.getOwnPropertyDescriptor(cx, proxy, id, desc)
+               : js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc);
+    }
+
+    virtual bool defineProperty(JSContext *cx,
+                                JS::HandleObject proxy, JS::HandleId id,
+                                JS::Handle<JS::PropertyDescriptor> desc,
+                                JS::ObjectOpResult &result) const override
+    {
+        return mTraps.defineProperty
+               ? mTraps.defineProperty(cx, proxy, id, desc, result)
+               : js::Wrapper::defineProperty(cx, proxy, id, desc, result);
+    }
+
+    virtual bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy,
+                                 JS::AutoIdVector &props) const override
+    {
+        return mTraps.ownPropertyKeys
+               ? mTraps.ownPropertyKeys(cx, proxy, props)
+               : js::Wrapper::ownPropertyKeys(cx, proxy, props);
+    }
+
+    virtual bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+                         JS::ObjectOpResult &result) const override
+    {
+        return mTraps.delete_
+               ? mTraps.delete_(cx, proxy, id, result)
+               : js::Wrapper::delete_(cx, proxy, id, result);
+    }
+
+    virtual bool preventExtensions(JSContext *cx, JS::HandleObject proxy,
+                                   JS::ObjectOpResult &result) const override
+    {
+        return mTraps.preventExtensions
+               ? mTraps.preventExtensions(cx, proxy, result)
+               : js::Wrapper::preventExtensions(cx, proxy, result);
+    }
+
+    virtual bool isExtensible(JSContext *cx, JS::HandleObject proxy,
+                              bool *succeeded) const override
+    {
+        return mTraps.isExtensible
+               ? mTraps.isExtensible(cx, proxy, succeeded)
+               : js::Wrapper::isExtensible(cx, proxy, succeeded);
+    }
+
+    virtual bool getPropertyDescriptor(JSContext *cx, JS::HandleObject proxy,
+                                       JS::HandleId id,
+                                       JS::MutableHandle<JS::PropertyDescriptor> desc) const override
+    {
+        return mTraps.getPropertyDescriptor
+               ? mTraps.getPropertyDescriptor(cx, proxy, id, desc)
+               : js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc);
+    }
+};
+
+class RustJSPrincipal : public JSPrincipals
+{
+    const void* origin; //box with origin in it
+    void (*destroyCallback)(JSPrincipals *principal);
+    bool (*writeCallback)(JSContext* cx, JSStructuredCloneWriter* writer);
+
+  public:
+    RustJSPrincipal(const void* origin,
+                     void (*destroy)(JSPrincipals *principal),
+                     bool (*write)(JSContext* cx, JSStructuredCloneWriter* writer))
+    : JSPrincipals() {
+      this->origin = origin;
+      this->destroyCallback = destroy;
+      this->writeCallback = write;
+    }
+
+    virtual const void* getOrigin() {
+      return origin;
+    }
+
+    virtual void destroy() {
+      if(this->destroyCallback)
+        this->destroyCallback(this);
+    }
+
+    bool write(JSContext* cx, JSStructuredCloneWriter* writer) {
+      return this->writeCallback
+             ? this->writeCallback(cx, writer)
+             : false;
+    }
+};
+
+class ForwardingProxyHandler : public js::BaseProxyHandler
+{
+    ProxyTraps mTraps;
+    const void* mExtra;
+  public:
+    ForwardingProxyHandler(const ProxyTraps& aTraps, const void* aExtra)
+    : js::BaseProxyHandler(&HandlerFamily), mTraps(aTraps), mExtra(aExtra) {}
+
+    const void* getExtra() const {
+        return mExtra;
+    }
+
+    virtual bool finalizeInBackground(const JS::Value& priv) const override
+    {
+        return false;
+    }
+
+    DEFER_TO_TRAP_OR_BASE_CLASS(BaseProxyHandler)
+
+    virtual bool getOwnPropertyDescriptor(JSContext *cx, JS::HandleObject proxy,
+                                          JS::HandleId id,
+                                          JS::MutableHandle<JS::PropertyDescriptor> desc) const override
+    {
+        return mTraps.getOwnPropertyDescriptor(cx, proxy, id, desc);
+    }
+
+    virtual bool defineProperty(JSContext *cx,
+                                JS::HandleObject proxy, JS::HandleId id,
+                                JS::Handle<JS::PropertyDescriptor> desc,
+                                JS::ObjectOpResult &result) const override
+    {
+        return mTraps.defineProperty(cx, proxy, id, desc, result);
+    }
+
+    virtual bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy,
+                                 JS::AutoIdVector &props) const override
+    {
+        return mTraps.ownPropertyKeys(cx, proxy, props);
+    }
+
+    virtual bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+                         JS::ObjectOpResult &result) const override
+    {
+        return mTraps.delete_(cx, proxy, id, result);
+    }
+
+    virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy,
+                                        bool* isOrdinary,
+                                        JS::MutableHandleObject protop) const override
+    {
+        return mTraps.getPrototypeIfOrdinary(cx, proxy, isOrdinary, protop);
+    }
+
+    virtual bool preventExtensions(JSContext *cx, JS::HandleObject proxy,
+                                   JS::ObjectOpResult &result) const override
+    {
+        return mTraps.preventExtensions(cx, proxy, result);
+    }
+
+    virtual bool isExtensible(JSContext *cx, JS::HandleObject proxy,
+                              bool *succeeded) const override
+    {
+        return mTraps.isExtensible(cx, proxy, succeeded);
+    }
+
+    virtual bool getPropertyDescriptor(JSContext *cx, JS::HandleObject proxy,
+                                       JS::HandleId id,
+                                       JS::MutableHandle<JS::PropertyDescriptor> desc) const override
+    {
+        return mTraps.getPropertyDescriptor(cx, proxy, id, desc);
+    }
+};
+
+extern "C" {
+
+JSPrincipals*
+CreateRustJSPrincipal(const void* origin,
+                       void (*destroy)(JSPrincipals *principal),
+                       bool (*write)(JSContext* cx, JSStructuredCloneWriter *writer)){
+  return new RustJSPrincipal(origin, destroy, write);
+}
+
+const void*
+GetPrincipalOrigin(JSPrincipals* principal) {
+  return static_cast<RustJSPrincipal*>(principal)->getOrigin();
+}
+
+bool
+InvokeGetOwnPropertyDescriptor(
+        const void *handler,
+        JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
+        JS::MutableHandle<JS::PropertyDescriptor> desc)
+{
+    return static_cast<const ForwardingProxyHandler*>(handler)->
+        getOwnPropertyDescriptor(cx, proxy, id, desc);
+}
+
+bool
+InvokeHasOwn(
+       const void *handler,
+       JSContext *cx, JS::HandleObject proxy,
+       JS::HandleId id, bool *bp)
+{
+    return static_cast<const js::BaseProxyHandler*>(handler)->
+        hasOwn(cx, proxy, id, bp);
+}
+
+JS::Value
+RUST_JS_NumberValue(double d)
+{
+    return JS_NumberValue(d);
+}
+
+const JSJitInfo*
+RUST_FUNCTION_VALUE_TO_JITINFO(JS::Value v)
+{
+    return FUNCTION_VALUE_TO_JITINFO(v);
+}
+
+JS::CallArgs
+CreateCallArgsFromVp(unsigned argc, JS::Value* vp)
+{
+    return JS::CallArgsFromVp(argc, vp);
+}
+
+bool
+CallJitGetterOp(const JSJitInfo* info, JSContext* cx,
+                JS::HandleObject thisObj, void* specializedThis,
+                unsigned argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    return info->getter(cx, thisObj, specializedThis, JSJitGetterCallArgs(args));
+}
+
+bool
+CallJitSetterOp(const JSJitInfo* info, JSContext* cx,
+                JS::HandleObject thisObj, void* specializedThis,
+                unsigned argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    return info->setter(cx, thisObj, specializedThis, JSJitSetterCallArgs(args));
+}
+
+bool
+CallJitMethodOp(const JSJitInfo* info, JSContext* cx,
+                JS::HandleObject thisObj, void* specializedThis,
+                uint32_t argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    return info->method(cx, thisObj, specializedThis, JSJitMethodCallArgs(args));
+}
+
+const void*
+CreateProxyHandler(const ProxyTraps* aTraps, const void* aExtra)
+{
+    return new ForwardingProxyHandler(*aTraps, aExtra);
+}
+
+const void*
+CreateWrapperProxyHandler(const ProxyTraps* aTraps)
+{
+    return new WrapperProxyHandler(*aTraps);
+}
+
+const void*
+GetCrossCompartmentWrapper()
+{
+    return &js::CrossCompartmentWrapper::singleton;
+}
+
+const void*
+GetSecurityWrapper()
+{
+  return &js::CrossCompartmentSecurityWrapper::singleton;
+}
+
+JS::ReadOnlyCompileOptions*
+NewCompileOptions(JSContext* aCx, const char* aFile, unsigned aLine)
+{
+    JS::OwningCompileOptions *opts = new JS::OwningCompileOptions(aCx);
+    opts->setFileAndLine(aCx, aFile, aLine);
+    opts->setVersion(JSVERSION_DEFAULT);
+    return opts;
+}
+
+void
+DeleteCompileOptions(JS::ReadOnlyCompileOptions *aOpts)
+{
+    delete static_cast<JS::OwningCompileOptions *>(aOpts);
+}
+
+JSObject*
+NewProxyObject(JSContext* aCx, const void* aHandler, JS::HandleValue aPriv,
+               JSObject* proto, JSObject* parent, JSObject* call,
+               JSObject* construct)
+{
+    js::ProxyOptions options;
+    return js::NewProxyObject(aCx, (js::BaseProxyHandler*)aHandler, aPriv, proto,
+                              options);
+}
+
+JSObject*
+WrapperNew(JSContext* aCx, JS::HandleObject aObj, const void* aHandler,
+           const JSClass* aClass, bool aSingleton)
+{
+    js::WrapperOptions options;
+    if (aClass) {
+        options.setClass(js::Valueify(aClass));
+    }
+    options.setSingleton(aSingleton);
+    return js::Wrapper::New(aCx, aObj, (const js::Wrapper*)aHandler, options);
+}
+
+void WindowProxyObjectMoved(JSObject*, const JSObject*)
+{
+    abort();
+}
+
+static const js::ClassExtension WindowProxyClassExtension = PROXY_MAKE_EXT(
+    WindowProxyObjectMoved
+);
+
+const js::Class WindowProxyClass = PROXY_CLASS_WITH_EXT(
+    "Proxy",
+    JSCLASS_HAS_RESERVED_SLOTS(1), /* additional class flags */
+    &WindowProxyClassExtension);
+
+const js::Class*
+GetWindowProxyClass()
+{
+    return &WindowProxyClass;
+}
+
+JS::Value
+GetProxyReservedSlot(JSObject* obj, uint32_t slot)
+{
+    return js::GetProxyReservedSlot(obj, slot);
+}
+
+void
+SetProxyReservedSlot(JSObject* obj, uint32_t slot, const JS::Value* val)
+{
+    js::SetProxyReservedSlot(obj, slot, *val);
+}
+
+JSObject*
+NewWindowProxy(JSContext* aCx, JS::HandleObject aObj, const void* aHandler)
+{
+    return WrapperNew(aCx, aObj, aHandler, Jsvalify(&WindowProxyClass), true);
+}
+
+JS::Value
+GetProxyPrivate(JSObject* obj)
+{
+    return js::GetProxyPrivate(obj);
+}
+
+void
+SetProxyPrivate(JSObject* obj, const JS::Value* expando)
+{
+    js::SetProxyPrivate(obj, *expando);
+}
+
+bool
+RUST_JSID_IS_INT(JS::HandleId id)
+{
+    return JSID_IS_INT(id);
+}
+
+jsid
+int_to_jsid(int32_t i)
+{
+    return INT_TO_JSID(i);
+}
+
+int32_t
+RUST_JSID_TO_INT(JS::HandleId id)
+{
+    return JSID_TO_INT(id);
+}
+
+bool
+RUST_JSID_IS_STRING(JS::HandleId id)
+{
+    return JSID_IS_STRING(id);
+}
+
+JSString*
+RUST_JSID_TO_STRING(JS::HandleId id)
+{
+    return JSID_TO_STRING(id);
+}
+
+jsid
+RUST_SYMBOL_TO_JSID(JS::Symbol* sym)
+{
+    return SYMBOL_TO_JSID(sym);
+}
+
+void
+RUST_SET_JITINFO(JSFunction* func, const JSJitInfo* info) {
+    SET_JITINFO(func, info);
+}
+
+jsid
+RUST_INTERNED_STRING_TO_JSID(JSContext* cx, JSString* str) {
+    return INTERNED_STRING_TO_JSID(cx, str);
+}
+
+const JSErrorFormatString*
+RUST_js_GetErrorMessage(void* userRef, uint32_t errorNumber)
+{
+    return js::GetErrorMessage(userRef, errorNumber);
+}
+
+bool
+IsProxyHandlerFamily(JSObject* obj)
+{
+    auto family = js::GetProxyHandler(obj)->family();
+    return family == &HandlerFamily;
+}
+
+const void*
+GetProxyHandlerFamily()
+{
+    return &HandlerFamily;
+}
+
+const void*
+GetProxyHandlerExtra(JSObject* obj)
+{
+    const js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
+    assert(handler->family() == &HandlerFamily);
+    return static_cast<const ForwardingProxyHandler*>(handler)->getExtra();
+}
+
+const void*
+GetProxyHandler(JSObject* obj)
+{
+    const js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
+    assert(handler->family() == &HandlerFamily);
+    return handler;
+}
+
+void
+ReportError(JSContext* aCx, const char* aError)
+{
+#ifdef DEBUG
+    for (const char* p = aError; *p; ++p) {
+        assert(*p != '%');
+    }
+#endif
+    JS_ReportErrorASCII(aCx, "%s", aError);
+}
+
+bool
+IsWrapper(JSObject* obj)
+{
+    return js::IsWrapper(obj);
+}
+
+JSObject*
+UnwrapObject(JSObject* obj, bool stopAtOuter)
+{
+    return js::CheckedUnwrap(obj, stopAtOuter);
+}
+
+JSObject*
+UncheckedUnwrapObject(JSObject* obj, bool stopAtOuter)
+{
+    return js::UncheckedUnwrap(obj, stopAtOuter);
+}
+
+JS::AutoIdVector*
+CreateAutoIdVector(JSContext* cx)
+{
+    return new JS::AutoIdVector(cx);
+}
+
+bool
+AppendToAutoIdVector(JS::AutoIdVector* v, jsid id)
+{
+    return v->append(id);
+}
+
+const jsid*
+SliceAutoIdVector(const JS::AutoIdVector* v, size_t* length)
+{
+    *length = v->length();
+    return v->begin();
+}
+
+void
+DestroyAutoIdVector(JS::AutoIdVector* v)
+{
+    delete v;
+}
+
+JS::AutoObjectVector*
+CreateAutoObjectVector(JSContext* aCx)
+{
+    JS::AutoObjectVector* vec = new JS::AutoObjectVector(aCx);
+    return vec;
+}
+
+bool
+AppendToAutoObjectVector(JS::AutoObjectVector* v, JSObject* obj)
+{
+    return v->append(obj);
+}
+
+void
+DeleteAutoObjectVector(JS::AutoObjectVector* v)
+{
+    delete v;
+}
+
+#if defined(__linux__)
+ #include <malloc.h>
+#elif defined(__APPLE__)
+ #include <malloc/malloc.h>
+#elif defined(__MINGW32__) || defined(__MINGW64__)
+ // nothing needed here
+#elif defined(_MSC_VER)
+ // nothing needed here
+#else
+ #error "unsupported platform"
+#endif
+
+// SpiderMonkey-in-Rust currently uses system malloc, not jemalloc.
+static size_t MallocSizeOf(const void* aPtr)
+{
+#if defined(__linux__)
+    return malloc_usable_size((void*)aPtr);
+#elif defined(__APPLE__)
+    return malloc_size((void*)aPtr);
+#elif defined(__MINGW32__) || defined(__MINGW64__)
+    return _msize((void*)aPtr);
+#elif defined(_MSC_VER)
+    return _msize((void*)aPtr);
+#else
+    #error "unsupported platform"
+#endif
+}
+
+bool
+CollectServoSizes(JSContext* cx, JS::ServoSizes *sizes)
+{
+    mozilla::PodZero(sizes);
+    return JS::AddServoSizeOf(cx, MallocSizeOf,
+                              /* ObjectPrivateVisitor = */ nullptr, sizes);
+}
+
+void
+CallValueTracer(JSTracer* trc, JS::Heap<JS::Value>* valuep, const char* name)
+{
+    JS::TraceEdge(trc, valuep, name);
+}
+
+void
+CallIdTracer(JSTracer* trc, JS::Heap<jsid>* idp, const char* name)
+{
+    JS::TraceEdge(trc, idp, name);
+}
+
+void
+CallObjectTracer(JSTracer* trc, JS::Heap<JSObject*>* objp, const char* name)
+{
+    JS::TraceEdge(trc, objp, name);
+}
+
+void
+CallStringTracer(JSTracer* trc, JS::Heap<JSString*>* strp, const char* name)
+{
+    JS::TraceEdge(trc, strp, name);
+}
+
+void
+CallScriptTracer(JSTracer* trc, JS::Heap<JSScript*>* scriptp, const char* name)
+{
+    JS::TraceEdge(trc, scriptp, name);
+}
+
+void
+CallFunctionTracer(JSTracer* trc, JS::Heap<JSFunction*>* funp, const char* name)
+{
+    JS::TraceEdge(trc, funp, name);
+}
+
+void
+CallUnbarrieredObjectTracer(JSTracer* trc, JSObject** objp, const char* name)
+{
+    js::UnsafeTraceManuallyBarrieredEdge(trc, objp, name);
+}
+
+bool
+IsDebugBuild()
+{
+#ifdef JS_DEBUG
+    return true;
+#else
+    return false;
+#endif
+}
+
+#define JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Type, type)                         \
+    void                                                                       \
+    Get ## Type ## ArrayLengthAndData(JSObject* obj, uint32_t* length,         \
+                                      bool* isSharedMemory, type** data)       \
+    {                                                                          \
+        js::Get ## Type ## ArrayLengthAndData(obj, length, isSharedMemory,     \
+                                              data);                           \
+    }
+
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Int8, int8_t)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint8, uint8_t)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint8Clamped, uint8_t)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Int16, int16_t)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint16, uint16_t)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Int32, int32_t)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Uint32, uint32_t)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Float32, float)
+JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Float64, double)
+
+#undef JS_DEFINE_DATA_AND_LENGTH_ACCESSOR
+
+JSAutoStructuredCloneBuffer*
+NewJSAutoStructuredCloneBuffer(JS::StructuredCloneScope scope,
+                               const JSStructuredCloneCallbacks* callbacks)
+{
+    return js_new<JSAutoStructuredCloneBuffer>(scope, callbacks, nullptr);
+}
+
+JSStructuredCloneData*
+DeleteJSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer* buf)
+{
+    js_delete(buf);
+}
+
+size_t
+GetLengthOfJSStructuredCloneData(JSStructuredCloneData* data)
+{
+    assert(data != nullptr);
+
+    size_t len = 0;
+
+    auto iter = data->Iter();
+    while (!iter.Done()) {
+        size_t len_of_this_segment = iter.RemainingInSegment();
+        len += len_of_this_segment;
+        iter.Advance(*data, len_of_this_segment);
+    }
+
+    return len;
+}
+
+void
+CopyJSStructuredCloneData(JSStructuredCloneData* src, uint8_t* dest)
+{
+    assert(src != nullptr);
+    assert(dest != nullptr);
+
+    size_t bytes_copied = 0;
+
+    auto iter = src->Iter();
+    while (!iter.Done()) {
+        size_t len_of_this_segment = iter.RemainingInSegment();
+        memcpy(dest + bytes_copied, iter.Data(), len_of_this_segment);
+        bytes_copied += len_of_this_segment;
+        iter.Advance(*src, len_of_this_segment);
+    }
+}
+
+bool
+WriteBytesToJSStructuredCloneData(const uint8_t* src, size_t len, JSStructuredCloneData* dest)
+{
+    assert(src != nullptr);
+    assert(dest != nullptr);
+
+    return dest->WriteBytes(reinterpret_cast<const char*>(src), len);
+}
+
+} // extern "C"
new file mode 100644
--- /dev/null
+++ b/js/rust/src/jsval.rs
@@ -0,0 +1,549 @@
+/* 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 jsapi::root::*;
+use libc::c_void;
+use std::mem;
+
+#[cfg(target_pointer_width = "64")]
+const JSVAL_TAG_SHIFT: usize = 47;
+
+#[cfg(target_pointer_width = "64")]
+const JSVAL_TAG_MAX_DOUBLE: u32 = 0x1FFF0u32;
+
+#[cfg(target_pointer_width = "32")]
+const JSVAL_TAG_CLEAR: u32 = 0xFFFFFF80;
+
+#[cfg(target_pointer_width = "64")]
+#[repr(u32)]
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug)]
+enum ValueTag {
+    INT32     = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_INT32 as u32),
+    UNDEFINED = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_UNDEFINED as u32),
+    STRING    = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_STRING as u32),
+    SYMBOL    = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_SYMBOL as u32),
+    BOOLEAN   = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_BOOLEAN as u32),
+    MAGIC     = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_MAGIC as u32),
+    NULL      = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_NULL as u32),
+    OBJECT    = JSVAL_TAG_MAX_DOUBLE | (JSValueType::JSVAL_TYPE_OBJECT as u32),
+}
+
+#[cfg(target_pointer_width = "32")]
+#[repr(u32)]
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug)]
+enum ValueTag {
+    PRIVATE   = 0,
+    INT32     = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_INT32 as u32),
+    UNDEFINED = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_UNDEFINED as u32),
+    STRING    = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_STRING as u32),
+    SYMBOL    = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_SYMBOL as u32),
+    BOOLEAN   = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_BOOLEAN as u32),
+    MAGIC     = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_MAGIC as u32),
+    NULL      = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_NULL as u32),
+    OBJECT    = JSVAL_TAG_CLEAR as u32 | (JSValueType::JSVAL_TYPE_OBJECT as u32),
+}
+
+#[cfg(target_pointer_width = "64")]
+#[repr(u64)]
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug)]
+enum ValueShiftedTag {
+    MAX_DOUBLE = (((JSVAL_TAG_MAX_DOUBLE as u64) << JSVAL_TAG_SHIFT) | 0xFFFFFFFFu64),
+    INT32      = ((ValueTag::INT32 as u64)      << JSVAL_TAG_SHIFT),
+    UNDEFINED  = ((ValueTag::UNDEFINED as u64)  << JSVAL_TAG_SHIFT),
+    STRING     = ((ValueTag::STRING as u64)     << JSVAL_TAG_SHIFT),
+    SYMBOL     = ((ValueTag::SYMBOL as u64)     << JSVAL_TAG_SHIFT),
+    BOOLEAN    = ((ValueTag::BOOLEAN as u64)    << JSVAL_TAG_SHIFT),
+    MAGIC      = ((ValueTag::MAGIC as u64)      << JSVAL_TAG_SHIFT),
+    NULL       = ((ValueTag::NULL as u64)       << JSVAL_TAG_SHIFT),
+    OBJECT     = ((ValueTag::OBJECT as u64)     << JSVAL_TAG_SHIFT),
+}
+
+
+#[cfg(target_pointer_width = "64")]
+const JSVAL_PAYLOAD_MASK: u64 = 0x00007FFFFFFFFFFF;
+
+#[inline(always)]
+fn AsJSVal(val: u64) -> JS::Value {
+    JS::Value {
+        data: JS::Value_layout {
+            asBits: val,
+        }
+    }
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline(always)]
+fn BuildJSVal(tag: ValueTag, payload: u64) -> JS::Value {
+    AsJSVal(((tag as u32 as u64) << JSVAL_TAG_SHIFT) | payload)
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline(always)]
+fn BuildJSVal(tag: ValueTag, payload: u64) -> JS::Value {
+    AsJSVal(((tag as u32 as u64) << 32) | payload)
+}
+
+#[inline(always)]
+pub fn NullValue() -> JS::Value {
+    BuildJSVal(ValueTag::NULL, 0)
+}
+
+#[inline(always)]
+pub fn UndefinedValue() -> JS::Value {
+    BuildJSVal(ValueTag::UNDEFINED, 0)
+}
+
+#[inline(always)]
+pub fn Int32Value(i: i32) -> JS::Value {
+    BuildJSVal(ValueTag::INT32, i as u32 as u64)
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline(always)]
+pub fn DoubleValue(f: f64) -> JS::Value {
+    let bits: u64 = unsafe { mem::transmute(f) };
+    assert!(bits <= ValueShiftedTag::MAX_DOUBLE as u64);
+    AsJSVal(bits)
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline(always)]
+pub fn DoubleValue(f: f64) -> JS::Value {
+    let bits: u64 = unsafe { mem::transmute(f) };
+    let val = AsJSVal(bits);
+    assert!(val.is_double());
+    val
+}
+
+#[inline(always)]
+pub fn UInt32Value(ui: u32) -> JS::Value {
+    if ui > 0x7fffffff {
+        DoubleValue(ui as f64)
+    } else {
+        Int32Value(ui as i32)
+    }
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline(always)]
+pub fn StringValue(s: &JSString) -> JS::Value {
+    let bits = s as *const JSString as usize as u64;
+    assert!((bits >> JSVAL_TAG_SHIFT) == 0);
+    BuildJSVal(ValueTag::STRING, bits)
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline(always)]
+pub fn StringValue(s: &JSString) -> JS::Value {
+    let bits = s as *const JSString as usize as u64;
+    BuildJSVal(ValueTag::STRING, bits)
+}
+
+#[inline(always)]
+pub fn BooleanValue(b: bool) -> JS::Value {
+    BuildJSVal(ValueTag::BOOLEAN, b as u64)
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline(always)]
+pub fn ObjectValue(o: *mut JSObject) -> JS::Value {
+    let bits = o as usize as u64;
+    assert!((bits >> JSVAL_TAG_SHIFT) == 0);
+    BuildJSVal(ValueTag::OBJECT, bits)
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline(always)]
+pub fn ObjectValue(o: *mut JSObject) -> JS::Value {
+    let bits = o as usize as u64;
+    BuildJSVal(ValueTag::OBJECT, bits)
+}
+
+#[inline(always)]
+pub fn ObjectOrNullValue(o: *mut JSObject) -> JS::Value {
+    if o.is_null() {
+        NullValue()
+    } else {
+        ObjectValue(o)
+    }
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline(always)]
+pub fn PrivateValue(o: *const c_void) -> JS::Value {
+    let ptrBits = o as usize as u64;
+    assert!((ptrBits & 1) == 0);
+    AsJSVal(ptrBits >> 1)
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline(always)]
+pub fn PrivateValue(o: *const c_void) -> JS::Value {
+    let ptrBits = o as usize as u64;
+    assert!((ptrBits & 1) == 0);
+    BuildJSVal(ValueTag::PRIVATE, ptrBits)
+}
+
+impl JS::Value {
+    #[inline(always)]
+    unsafe fn asBits(&self) -> u64 {
+        self.data.asBits
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_undefined(&self) -> bool {
+        unsafe {
+            self.asBits() == ValueShiftedTag::UNDEFINED as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_undefined(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) == ValueTag::UNDEFINED as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_null(&self) -> bool {
+        unsafe {
+            self.asBits() == ValueShiftedTag::NULL as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_null(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) == ValueTag::NULL as u64
+        }
+    }
+
+    #[inline(always)]
+    pub fn is_null_or_undefined(&self) -> bool {
+        self.is_null() || self.is_undefined()
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_boolean(&self) -> bool {
+        unsafe {
+            (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::BOOLEAN as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_boolean(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) == ValueTag::BOOLEAN as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_int32(&self) -> bool {
+        unsafe {
+            (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::INT32 as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_int32(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) == ValueTag::INT32 as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_double(&self) -> bool {
+        unsafe {
+            self.asBits() <= ValueShiftedTag::MAX_DOUBLE as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_double(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) <= JSVAL_TAG_CLEAR as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_number(&self) -> bool {
+        const JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_NUMBER_SET: u64 = ValueShiftedTag::UNDEFINED as u64;
+        unsafe {
+            self.asBits() < JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_NUMBER_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_number(&self) -> bool {
+        const JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET: u64 = ValueTag::INT32 as u64;
+        unsafe {
+            (self.asBits() >> 32) <= JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_primitive(&self) -> bool {
+        const JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_PRIMITIVE_SET: u64 = ValueShiftedTag::OBJECT as u64;
+        unsafe {
+            self.asBits() < JSVAL_UPPER_EXCL_SHIFTED_TAG_OF_PRIMITIVE_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_primitive(&self) -> bool {
+        const JSVAL_UPPER_EXCL_TAG_OF_PRIMITIVE_SET: u64 = ValueTag::OBJECT as u64;
+        unsafe {
+            (self.asBits() >> 32) < JSVAL_UPPER_EXCL_TAG_OF_PRIMITIVE_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_string(&self) -> bool {
+        unsafe {
+            (self.asBits() >> JSVAL_TAG_SHIFT) == ValueTag::STRING as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_string(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) == ValueTag::STRING as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_object(&self) -> bool {
+        unsafe {
+            assert!((self.asBits() >> JSVAL_TAG_SHIFT) <= ValueTag::OBJECT as u64);
+            self.asBits() >= ValueShiftedTag::OBJECT as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_object(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) == ValueTag::OBJECT as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_symbol(&self) -> bool {
+        unsafe {
+            self.asBits() == ValueShiftedTag::SYMBOL as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_symbol(&self) -> bool {
+        unsafe {
+            (self.asBits() >> 32) == ValueTag::SYMBOL as u64
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn to_boolean(&self) -> bool {
+        assert!(self.is_boolean());
+        unsafe {
+            (self.asBits() & JSVAL_PAYLOAD_MASK) != 0
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn to_boolean(&self) -> bool {
+        assert!(self.is_boolean());
+        unsafe {
+            (self.asBits() & 0x00000000FFFFFFFF) != 0
+        }
+    }
+
+    #[inline(always)]
+    pub fn to_int32(&self) -> i32 {
+        assert!(self.is_int32());
+        unsafe {
+            (self.asBits() & 0x00000000FFFFFFFF) as i32
+        }
+    }
+
+    #[inline(always)]
+    pub fn to_double(&self) -> f64 {
+        assert!(self.is_double());
+        unsafe { mem::transmute(self.asBits()) }
+    }
+
+    #[inline(always)]
+    pub fn to_number(&self) -> f64 {
+        assert!(self.is_number());
+        if self.is_double() {
+            self.to_double()
+        } else {
+            self.to_int32() as f64
+        }
+    }
+
+    #[inline(always)]
+    pub fn to_object(&self) -> *mut JSObject {
+        assert!(self.is_object());
+        self.to_object_or_null()
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn to_string(&self) -> *mut JSString {
+        assert!(self.is_string());
+        unsafe {
+            let ptrBits = self.asBits() & JSVAL_PAYLOAD_MASK;
+            ptrBits as usize as *mut JSString
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn to_string(&self) -> *mut JSString {
+        assert!(self.is_string());
+        unsafe {
+            let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32;
+            ptrBits as *mut JSString
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_object_or_null(&self) -> bool {
+        const JSVAL_LOWER_INCL_SHIFTED_TAG_OF_OBJ_OR_NULL_SET: u64 = ValueShiftedTag::NULL as u64;
+        unsafe {
+            assert!((self.asBits() >> JSVAL_TAG_SHIFT) <= ValueTag::OBJECT as u64);
+            self.asBits() >= JSVAL_LOWER_INCL_SHIFTED_TAG_OF_OBJ_OR_NULL_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_object_or_null(&self) -> bool {
+        const JSVAL_LOWER_INCL_TAG_OF_OBJ_OR_NULL_SET: u64 = ValueTag::NULL as u64;
+        unsafe {
+            assert!((self.asBits() >> 32) <= ValueTag::OBJECT as u64);
+            (self.asBits() >> 32) >= JSVAL_LOWER_INCL_TAG_OF_OBJ_OR_NULL_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn to_object_or_null(&self) -> *mut JSObject {
+        assert!(self.is_object_or_null());
+        unsafe {
+            let ptrBits = self.asBits() & JSVAL_PAYLOAD_MASK;
+            assert!((ptrBits & 0x7) == 0);
+            ptrBits as usize as *mut JSObject
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn to_object_or_null(&self) -> *mut JSObject {
+        assert!(self.is_object_or_null());
+        unsafe {
+            let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32;
+            ptrBits as *mut JSObject
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn to_private(&self) -> *const c_void {
+        assert!(self.is_double());
+        unsafe {
+            assert!((self.asBits() & 0x8000000000000000u64) == 0);
+            (self.asBits() << 1) as usize as *const c_void
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn to_private(&self) -> *const c_void {
+        unsafe {
+            let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32;
+            ptrBits as *const c_void
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn is_gcthing(&self) -> bool {
+        const JSVAL_LOWER_INCL_SHIFTED_TAG_OF_GCTHING_SET: u64 = ValueShiftedTag::STRING as u64;
+        unsafe {
+            self.asBits() >= JSVAL_LOWER_INCL_SHIFTED_TAG_OF_GCTHING_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn is_gcthing(&self) -> bool {
+        const JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET: u64 = ValueTag::STRING as u64;
+        unsafe {
+            (self.asBits() >> 32) >= JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "64")]
+    pub fn to_gcthing(&self) -> *mut c_void {
+        assert!(self.is_gcthing());
+        unsafe {
+            let ptrBits = self.asBits() & JSVAL_PAYLOAD_MASK;
+            assert!((ptrBits & 0x7) == 0);
+            ptrBits as *mut c_void
+        }
+    }
+
+    #[inline(always)]
+    #[cfg(target_pointer_width = "32")]
+    pub fn to_gcthing(&self) -> *mut c_void {
+        assert!(self.is_gcthing());
+        unsafe {
+            let ptrBits: u32 = (self.asBits() & 0x00000000FFFFFFFF) as u32;
+            ptrBits as *mut c_void
+        }
+    }
+
+    #[inline(always)]
+    pub fn is_markable(&self) -> bool {
+        self.is_gcthing() && !self.is_null()
+    }
+
+    #[inline(always)]
+    pub fn trace_kind(&self) -> JS::TraceKind {
+        assert!(self.is_markable());
+        if self.is_object() {
+            JS::TraceKind::Object
+        } else {
+            JS::TraceKind::String
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/lib.rs
@@ -0,0 +1,54 @@
+/* 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/. */
+
+#![crate_name = "js"]
+#![crate_type = "rlib"]
+
+#![cfg_attr(feature = "nonzero", feature(nonzero))]
+
+#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case, improper_ctypes)]
+
+#[cfg(feature = "nonzero")]
+extern crate core;
+#[macro_use]
+extern crate heapsize;
+#[macro_use]
+extern crate lazy_static;
+extern crate libc;
+#[macro_use]
+extern crate log;
+#[allow(unused_extern_crates)]
+extern crate mozjs_sys;
+extern crate num_traits;
+
+#[macro_use]
+pub mod rust;
+
+pub mod ac;
+pub mod conversions;
+pub mod error;
+pub mod glue;
+pub mod heap;
+pub mod jsval;
+pub mod panic;
+pub mod sc;
+pub mod typedarray;
+
+pub mod jsapi;
+use self::jsapi::root::*;
+
+#[inline(always)]
+pub unsafe fn JS_ARGV(_cx: *mut JSContext, vp: *mut JS::Value) -> *mut JS::Value {
+    vp.offset(2)
+}
+
+known_heap_size!(0, JS::Value);
+
+impl JS::ObjectOpResult {
+    /// Set this ObjectOpResult to true and return true.
+    pub fn succeed(&mut self) -> bool {
+        self.code_ = JS::ObjectOpResult_SpecialCodes::OkCode as usize;
+        true
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/panic.rs
@@ -0,0 +1,33 @@
+/* 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 std::any::Any;
+use std::cell::RefCell;
+use std::panic::{UnwindSafe, catch_unwind, resume_unwind};
+
+thread_local!(static PANIC_RESULT: RefCell<Option<Box<Any + Send>>> = RefCell::new(None));
+
+/// If there is a pending panic, resume unwinding.
+pub fn maybe_resume_unwind() {
+    if let Some(error) = PANIC_RESULT.with(|result| result.borrow_mut().take()) {
+        resume_unwind(error);
+    }
+}
+
+/// Generic wrapper for JS engine callbacks panic-catching
+pub fn wrap_panic<F, R>(function: F, generic_return_type: R) -> R
+    where F: FnOnce() -> R + UnwindSafe
+{
+    let result = catch_unwind(function);
+    match result {
+        Ok(result) => result,
+        Err(error) => {
+            PANIC_RESULT.with(|result| {
+                assert!(result.borrow().is_none());
+                *result.borrow_mut() = Some(error);
+            });
+            generic_return_type
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/rust.rs
@@ -0,0 +1,1120 @@
+/* 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/. */
+
+//! Rust wrappers around the raw JS apis
+
+use ac::AutoCompartment;
+use libc::c_uint;
+use heapsize::HeapSizeOf;
+use std::cell::{Cell, UnsafeCell};
+use std::char;
+use std::ffi;
+use std::ptr;
+use std::slice;
+use std::mem;
+use std::u32;
+use std::default::Default;
+use std::marker;
+use std::ops::{Deref, DerefMut};
+use std::sync::{Once, ONCE_INIT};
+use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
+use std::thread;
+use jsapi::root::*;
+use jsval::{self, UndefinedValue};
+use glue::{CreateAutoObjectVector, CreateCallArgsFromVp, AppendToAutoObjectVector, DeleteAutoObjectVector, IsDebugBuild};
+use glue::{CreateAutoIdVector, SliceAutoIdVector, DestroyAutoIdVector};
+use glue::{NewCompileOptions, DeleteCompileOptions};
+use panic;
+
+const DEFAULT_HEAPSIZE: u32 = 32_u32 * 1024_u32 * 1024_u32;
+
+// From Gecko:
+// Our "default" stack is what we use in configurations where we don't have a compelling reason to
+// do things differently. This is effectively 1MB on 64-bit platforms.
+const STACK_QUOTA: usize = 128 * 8 * 1024;
+
+// From Gecko:
+// The JS engine permits us to set different stack limits for system code,
+// trusted script, and untrusted script. We have tests that ensure that
+// we can always execute 10 "heavy" (eval+with) stack frames deeper in
+// privileged code. Our stack sizes vary greatly in different configurations,
+// so satisfying those tests requires some care. Manual measurements of the
+// number of heavy stack frames achievable gives us the following rough data,
+// ordered by the effective categories in which they are grouped in the
+// JS_SetNativeStackQuota call (which predates this analysis).
+//
+// (NB: These numbers may have drifted recently - see bug 938429)
+// OSX 64-bit Debug: 7MB stack, 636 stack frames => ~11.3k per stack frame
+// OSX64 Opt: 7MB stack, 2440 stack frames => ~3k per stack frame
+//
+// Linux 32-bit Debug: 2MB stack, 426 stack frames => ~4.8k per stack frame
+// Linux 64-bit Debug: 4MB stack, 455 stack frames => ~9.0k per stack frame
+//
+// Windows (Opt+Debug): 900K stack, 235 stack frames => ~3.4k per stack frame
+//
+// Linux 32-bit Opt: 1MB stack, 272 stack frames => ~3.8k per stack frame
+// Linux 64-bit Opt: 2MB stack, 316 stack frames => ~6.5k per stack frame
+//
+// We tune the trusted/untrusted quotas for each configuration to achieve our
+// invariants while attempting to minimize overhead. In contrast, our buffer
+// between system code and trusted script is a very unscientific 10k.
+const SYSTEM_CODE_BUFFER: usize = 10 * 1024;
+
+// Gecko's value on 64-bit.
+const TRUSTED_SCRIPT_BUFFER: usize = 8 * 12800;
+
+trait ToResult {
+    fn to_result(self) -> Result<(), ()>;
+}
+
+impl ToResult for bool {
+    fn to_result(self) -> Result<(), ()> {
+        if self {
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+}
+
+// ___________________________________________________________________________
+// friendly Rustic API to runtimes
+
+thread_local!(static CONTEXT: Cell<*mut JSContext> = Cell::new(ptr::null_mut()));
+
+lazy_static! {
+    static ref OUTSTANDING_RUNTIMES: AtomicUsize = AtomicUsize::new(0);
+    static ref SHUT_DOWN: AtomicBool = AtomicBool::new(false);
+}
+
+/// A wrapper for the `JSContext` structure in SpiderMonkey.
+pub struct Runtime {
+    cx: *mut JSContext,
+}
+
+impl Runtime {
+    /// Get the `JSContext` for this thread.
+    pub fn get() -> *mut JSContext {
+        let cx = CONTEXT.with(|context| {
+            context.get()
+        });
+        assert!(!cx.is_null());
+        cx
+    }
+
+    /// Creates a new `JSContext`.
+    pub fn new() -> Result<Runtime, ()> {
+        if SHUT_DOWN.load(Ordering::SeqCst) {
+            return Err(());
+        }
+
+        OUTSTANDING_RUNTIMES.fetch_add(1, Ordering::SeqCst);
+
+        unsafe {
+            #[derive(Debug)]
+            struct Parent(UnsafeCell<*mut JSContext>);
+            unsafe impl ::std::marker::Sync for Parent {}
+
+            impl Parent {
+                fn set(&self, val: *mut JSContext) {
+                    assert!(self.get().is_null());
+                    self.as_atomic().store(val, Ordering::SeqCst);
+                    assert_eq!(self.get(), val);
+                }
+
+                fn get(&self) -> *mut JSContext {
+                    self.as_atomic().load(Ordering::SeqCst)
+                }
+
+                fn as_atomic(&self) -> &AtomicPtr<JSContext> {
+                    unsafe {
+                        mem::transmute(&self.0)
+                    }
+                }
+            }
+
+            lazy_static! {
+                static ref PARENT: Parent = Parent(UnsafeCell::new(0 as *mut _));
+            }
+            static ONCE: Once = ONCE_INIT;
+
+            ONCE.call_once(|| {
+                // There is a 1:1 relationship between threads and JSContexts,
+                // so we must spawn a new thread for the parent context.
+                let _ = thread::spawn(move || {
+                    let is_debug_mozjs = cfg!(feature = "debugmozjs");
+                    let diagnostic = JS::detail::InitWithFailureDiagnostic(is_debug_mozjs);
+                    if !diagnostic.is_null() {
+                        panic!("JS::detail::InitWithFailureDiagnostic failed: {}",
+                               ffi::CStr::from_ptr(diagnostic).to_string_lossy());
+                    }
+
+                    let context = JS_NewContext(
+                        DEFAULT_HEAPSIZE, ChunkSize as u32, ptr::null_mut());
+                    assert!(!context.is_null());
+                    JS::InitSelfHostedCode(context);
+                    PARENT.set(context);
+
+                    loop {
+                        thread::sleep(::std::time::Duration::new(::std::u64::MAX, 0));
+                    }
+                });
+
+                while PARENT.get().is_null() {
+                    thread::yield_now();
+                }
+            });
+
+            assert_eq!(IsDebugBuild(), cfg!(feature = "debugmozjs"));
+
+            let js_context = JS_NewContext(DEFAULT_HEAPSIZE,
+                                           ChunkSize as u32,
+                                           JS_GetParentRuntime(PARENT.get()));
+            assert!(!js_context.is_null());
+
+            // Unconstrain the runtime's threshold on nominal heap size, to avoid
+            // triggering GC too often if operating continuously near an arbitrary
+            // finite threshold. This leaves the maximum-JS_malloc-bytes threshold
+            // still in effect to cause periodical, and we hope hygienic,
+            // last-ditch GCs from within the GC's allocator.
+            JS_SetGCParameter(
+                js_context, JSGCParamKey::JSGC_MAX_BYTES, u32::MAX);
+
+            JS_SetNativeStackQuota(
+                js_context,
+                STACK_QUOTA,
+                STACK_QUOTA - SYSTEM_CODE_BUFFER,
+                STACK_QUOTA - SYSTEM_CODE_BUFFER - TRUSTED_SCRIPT_BUFFER);
+
+            let opts = JS::ContextOptionsRef(js_context);
+            (*opts).set_baseline_(true);
+            (*opts).set_ion_(true);
+            (*opts).set_nativeRegExp_(true);
+
+            CONTEXT.with(|context| {
+                assert!(context.get().is_null());
+                context.set(js_context);
+            });
+
+            JS::InitSelfHostedCode(js_context);
+
+            JS::SetWarningReporter(js_context, Some(report_warning));
+
+            JS_BeginRequest(js_context);
+
+            Ok(Runtime {
+                cx: js_context,
+            })
+        }
+    }
+
+    /// Returns the underlying `JSContext` object.
+    pub fn cx(&self) -> *mut JSContext {
+        self.cx
+    }
+
+    /// Returns the underlying `JSContext`'s `JSRuntime`.
+    pub fn rt(&self) -> *mut JSRuntime {
+        unsafe {
+            JS_GetRuntime(self.cx)
+        }
+    }
+
+    pub fn evaluate_script(&self, glob: JS::HandleObject, script: &str, filename: &str,
+                           line_num: u32, rval: JS::MutableHandleValue)
+                    -> Result<(),()> {
+        let script_utf16: Vec<u16> = script.encode_utf16().collect();
+        let filename_cstr = ffi::CString::new(filename.as_bytes()).unwrap();
+        debug!("Evaluating script from {} with content {}", filename, script);
+        // SpiderMonkey does not approve of null pointers.
+        let (ptr, len) = if script_utf16.len() == 0 {
+            static empty: &'static [u16] = &[];
+            (empty.as_ptr(), 0)
+        } else {
+            (script_utf16.as_ptr(), script_utf16.len() as c_uint)
+        };
+        assert!(!ptr.is_null());
+        unsafe {
+            let _ac = AutoCompartment::with_obj(self.cx(), glob.get());
+            let options = CompileOptionsWrapper::new(self.cx(), filename_cstr.as_ptr(), line_num);
+
+            if !JS::Evaluate2(self.cx(), options.ptr, ptr as *const u16, len as _, rval) {
+                debug!("...err!");
+                panic::maybe_resume_unwind();
+                Err(())
+            } else {
+                // we could return the script result but then we'd have
+                // to root it and so forth and, really, who cares?
+                debug!("...ok!");
+                Ok(())
+            }
+        }
+    }
+}
+
+impl Drop for Runtime {
+    fn drop(&mut self) {
+        unsafe {
+            JS_EndRequest(self.cx);
+            JS_DestroyContext(self.cx);
+
+            CONTEXT.with(|context| {
+                assert_eq!(context.get(), self.cx);
+                context.set(ptr::null_mut());
+            });
+
+            if OUTSTANDING_RUNTIMES.fetch_sub(1, Ordering::SeqCst) == 1 {
+                SHUT_DOWN.store(true, Ordering::SeqCst);
+                JS_ShutDown();
+            }
+        }
+    }
+}
+
+// This is measured through `glue::CollectServoSizes`.
+impl HeapSizeOf for Runtime {
+    fn heap_size_of_children(&self) -> usize {
+        0
+    }
+}
+
+// ___________________________________________________________________________
+// Rooting API for standard JS things
+
+pub trait RootKind {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind;
+}
+
+impl RootKind for *mut JSObject {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::Object }
+}
+
+impl RootKind for *mut JSFlatString {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::String }
+}
+
+impl RootKind for *mut JSFunction {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::Object }
+}
+
+impl RootKind for *mut JSString {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::String }
+}
+
+impl RootKind for *mut JS::Symbol {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::Symbol }
+}
+
+impl RootKind for *mut JSScript {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::Script }
+}
+
+impl RootKind for jsid {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::Id }
+}
+
+impl RootKind for JS::Value {
+    #[inline(always)]
+    fn rootKind() -> JS::RootKind { JS::RootKind::Value }
+}
+
+impl<T> JS::Rooted<T> {
+    pub fn new_unrooted() -> JS::Rooted<T>
+        where T: GCMethods,
+    {
+        JS::Rooted {
+            stack: ptr::null_mut(),
+            prev: ptr::null_mut(),
+            ptr: unsafe { T::initial() },
+            _phantom_0: marker::PhantomData,
+        }
+    }
+
+    unsafe fn get_rooting_context(cx: *mut JSContext) -> *mut JS::RootingContext {
+        mem::transmute(cx)
+    }
+
+    unsafe fn get_root_stack(cx: *mut JSContext)
+                             -> *mut *mut JS::Rooted<*mut ::std::os::raw::c_void>
+        where T: RootKind
+    {
+        let kind = T::rootKind() as usize;
+        let rooting_cx = Self::get_rooting_context(cx);
+        &mut rooting_cx.as_mut().unwrap().stackRoots_[kind] as *mut _ as *mut _
+    }
+
+    pub unsafe fn register_with_root_lists(&mut self, cx: *mut JSContext)
+        where T: RootKind
+    {
+        self.stack = Self::get_root_stack(cx);
+        let stack = self.stack.as_mut().unwrap();
+        self.prev = *stack as *mut _;
+
+        *stack = self as *mut _ as usize as _;
+    }
+
+    pub unsafe fn remove_from_root_stack(&mut self) {
+        assert!(*self.stack == self as *mut _ as usize as _);
+        *self.stack = self.prev;
+    }
+}
+
+/// Rust API for keeping a JS::Rooted value in the context's root stack.
+/// Example usage: `rooted!(in(cx) let x = UndefinedValue());`.
+/// `RootedGuard::new` also works, but the macro is preferred.
+pub struct RootedGuard<'a, T: 'a + RootKind + GCMethods> {
+    root: &'a mut JS::Rooted<T>
+}
+
+impl<'a, T: 'a + RootKind + GCMethods> RootedGuard<'a, T> {
+    pub fn new(cx: *mut JSContext, root: &'a mut JS::Rooted<T>, initial: T) -> Self {
+        root.ptr = initial;
+        unsafe {
+            root.register_with_root_lists(cx);
+        }
+        RootedGuard {
+            root: root
+        }
+    }
+
+    pub fn handle(&self) -> JS::Handle<T> {
+        unsafe {
+            JS::Handle::from_marked_location(&self.root.ptr)
+        }
+    }
+
+    pub fn handle_mut(&mut self) -> JS::MutableHandle<T> {
+        unsafe {
+            JS::MutableHandle::from_marked_location(&mut self.root.ptr)
+        }
+    }
+
+    pub fn get(&self) -> T where T: Copy {
+        self.root.ptr
+    }
+
+    pub fn set(&mut self, v: T) {
+        self.root.ptr = v;
+    }
+}
+
+impl<'a, T: 'a + RootKind + GCMethods> Deref for RootedGuard<'a, T> {
+    type Target = T;
+    fn deref(&self) -> &T {
+        &self.root.ptr
+    }
+}
+
+impl<'a, T: 'a + RootKind + GCMethods> DerefMut for RootedGuard<'a, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        &mut self.root.ptr
+    }
+}
+
+impl<'a, T: 'a + RootKind + GCMethods> Drop for RootedGuard<'a, T> {
+    fn drop(&mut self) {
+        unsafe {
+            self.root.ptr = T::initial();
+            self.root.remove_from_root_stack();
+        }
+    }
+}
+
+#[macro_export]
+macro_rules! rooted {
+    (in($cx:expr) let $name:ident = $init:expr) => {
+        let mut __root = $crate::jsapi::JS::Rooted::new_unrooted();
+        let $name = $crate::rust::RootedGuard::new($cx, &mut __root, $init);
+    };
+    (in($cx:expr) let mut $name:ident = $init:expr) => {
+        let mut __root = $crate::jsapi::JS::Rooted::new_unrooted();
+        let mut $name = $crate::rust::RootedGuard::new($cx, &mut __root, $init);
+    }
+}
+
+impl<T> JS::Handle<T> {
+    pub fn get(&self) -> T
+        where T: Copy
+    {
+        unsafe { *self.ptr }
+    }
+
+    pub unsafe fn from_marked_location(ptr: *const T) -> JS::Handle<T> {
+        JS::Handle {
+            ptr: mem::transmute(ptr),
+            _phantom_0: marker::PhantomData,
+        }
+    }
+}
+
+impl<T> Deref for JS::Handle<T> {
+    type Target = T;
+
+    fn deref<'a>(&'a self) -> &'a T {
+        unsafe { &*self.ptr }
+    }
+}
+
+impl<T> JS::MutableHandle<T> {
+    pub unsafe fn from_marked_location(ptr: *mut T) -> JS::MutableHandle<T> {
+        JS::MutableHandle {
+            ptr: ptr,
+            _phantom_0: marker::PhantomData,
+        }
+    }
+
+    pub fn handle(&self) -> JS::Handle<T> {
+        unsafe {
+            JS::Handle::from_marked_location(self.ptr as *const _)
+        }
+    }
+
+    pub fn get(&self) -> T
+        where T: Copy
+    {
+        unsafe { *self.ptr }
+    }
+
+    pub fn set(&self, v: T)
+        where T: Copy
+    {
+        unsafe { *self.ptr = v }
+    }
+}
+
+impl<T> Deref for JS::MutableHandle<T> {
+    type Target = T;
+
+    fn deref<'a>(&'a self) -> &'a T {
+        unsafe { &*self.ptr }
+    }
+}
+
+impl<T> DerefMut for JS::MutableHandle<T> {
+    fn deref_mut<'a>(&'a mut self) -> &'a mut T {
+        unsafe { &mut *self.ptr }
+    }
+}
+
+impl JS::HandleValue {
+    pub fn null() -> JS::HandleValue {
+        unsafe {
+            JS::NullHandleValue
+        }
+    }
+
+    pub fn undefined() -> JS::HandleValue {
+        unsafe {
+            JS::UndefinedHandleValue
+        }
+    }
+}
+
+impl JS::HandleValueArray {
+    pub fn new() -> JS::HandleValueArray {
+        JS::HandleValueArray {
+            length_: 0,
+            elements_: ptr::null(),
+        }
+    }
+
+    pub unsafe fn from_rooted_slice(values: &[JS::Value]) -> JS::HandleValueArray {
+        JS::HandleValueArray {
+            length_: values.len(),
+            elements_: values.as_ptr()
+        }
+    }
+}
+
+const ConstNullValue: *mut JSObject = 0 as *mut JSObject;
+
+impl JS::HandleObject {
+    pub fn null() -> JS::HandleObject {
+        unsafe {
+            JS::HandleObject::from_marked_location(&ConstNullValue)
+        }
+    }
+}
+
+impl Default for jsid {
+    fn default() -> jsid {
+        unsafe {
+            JSID_VOID
+        }
+    }
+}
+
+impl Default for JS::Value {
+    fn default() -> JS::Value { jsval::UndefinedValue() }
+}
+
+impl Default for JS::CompartmentOptions {
+    fn default() -> Self { unsafe { ::std::mem::zeroed() } }
+}
+
+const ChunkShift: usize = 20;
+const ChunkSize: usize = 1 << ChunkShift;
+
+#[cfg(target_pointer_width = "32")]
+const ChunkLocationOffset: usize = ChunkSize - 2 * 4 - 8;
+
+pub trait GCMethods {
+    unsafe fn initial() -> Self;
+    unsafe fn post_barrier(v: *mut Self, prev: Self, next: Self);
+}
+
+impl GCMethods for jsid {
+    unsafe fn initial() -> jsid { JSID_VOID }
+    unsafe fn post_barrier(_: *mut jsid, _: jsid, _: jsid) {}
+}
+
+impl GCMethods for *mut JSObject {
+    unsafe fn initial() -> *mut JSObject { ptr::null_mut() }
+    unsafe fn post_barrier(v: *mut *mut JSObject,
+                           prev: *mut JSObject, next: *mut JSObject) {
+        JS::HeapObjectPostBarrier(v, prev, next);
+    }
+}
+
+impl GCMethods for *mut JSString {
+    unsafe fn initial() -> *mut JSString { ptr::null_mut() }
+    unsafe fn post_barrier(_: *mut *mut JSString, _: *mut JSString, _: *mut JSString) {}
+}
+
+impl GCMethods for *mut JSScript {
+    unsafe fn initial() -> *mut JSScript { ptr::null_mut() }
+    unsafe fn post_barrier(_: *mut *mut JSScript, _: *mut JSScript, _: *mut JSScript) { }
+}
+
+impl GCMethods for *mut JSFunction {
+    unsafe fn initial() -> *mut JSFunction { ptr::null_mut() }
+    unsafe fn post_barrier(v: *mut *mut JSFunction,
+                           prev: *mut JSFunction, next: *mut JSFunction) {
+        JS::HeapObjectPostBarrier(mem::transmute(v),
+                                  mem::transmute(prev),
+                                  mem::transmute(next));
+    }
+}
+
+impl GCMethods for JS::Value {
+    unsafe fn initial() -> JS::Value { UndefinedValue() }
+    unsafe fn post_barrier(v: *mut JS::Value, prev: JS::Value, next: JS::Value) {
+        JS::HeapValuePostBarrier(v, &prev, &next);
+    }
+}
+
+// ___________________________________________________________________________
+// Implementations for various things in jsapi.rs
+
+impl Drop for JSAutoCompartment {
+    fn drop(&mut self) {
+        unsafe { JS_LeaveCompartment(self.cx_, self.oldCompartment_); }
+    }
+}
+
+impl JSJitMethodCallArgs {
+    #[inline]
+    pub fn get(&self, i: u32) -> JS::HandleValue {
+        unsafe {
+            if i < self._base.argc_ {
+                JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize))
+            } else {
+                JS::UndefinedHandleValue
+            }
+        }
+    }
+
+    #[inline]
+    pub fn index(&self, i: u32) -> JS::HandleValue {
+        assert!(i < self._base.argc_);
+        unsafe {
+            JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize))
+        }
+    }
+
+    #[inline]
+    pub fn index_mut(&self, i: u32) -> JS::MutableHandleValue {
+        assert!(i < self._base.argc_);
+        unsafe {
+            JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(i as isize))
+        }
+    }
+
+    #[inline]
+    pub fn rval(&self) -> JS::MutableHandleValue {
+        unsafe {
+            JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(-2))
+        }
+    }
+}
+
+// XXX need to hack up bindgen to convert this better so we don't have
+//     to duplicate so much code here
+impl JS::CallArgs {
+    #[inline]
+    pub unsafe fn from_vp(vp: *mut JS::Value, argc: u32) -> JS::CallArgs {
+        CreateCallArgsFromVp(argc, vp)
+    }
+
+    #[inline]
+    pub fn index(&self, i: u32) -> JS::HandleValue {
+        assert!(i < self._base.argc_);
+        unsafe {
+            JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize))
+        }
+    }
+
+    #[inline]
+    pub fn index_mut(&self, i: u32) -> JS::MutableHandleValue {
+        assert!(i < self._base.argc_);
+        unsafe {
+            JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(i as isize))
+        }
+    }
+
+    #[inline]
+    pub fn get(&self, i: u32) -> JS::HandleValue {
+        unsafe {
+            if i < self._base.argc_ {
+                JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize))
+            } else {
+                JS::UndefinedHandleValue
+            }
+        }
+    }
+
+    #[inline]
+    pub fn rval(&self) -> JS::MutableHandleValue {
+        unsafe {
+            JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(-2))
+        }
+    }
+
+    #[inline]
+    pub fn thisv(&self) -> JS::HandleValue {
+        unsafe {
+            JS::HandleValue::from_marked_location(self._base.argv_.offset(-1))
+        }
+    }
+
+    #[inline]
+    pub fn calleev(&self) -> JS::HandleValue {
+        unsafe {
+            JS::HandleValue::from_marked_location(self._base.argv_.offset(-2))
+        }
+    }
+
+    #[inline]
+    pub fn callee(&self) -> *mut JSObject {
+        self.calleev().to_object()
+    }
+
+    #[inline]
+    pub fn new_target(&self) -> JS::MutableHandleValue {
+        assert!(self._base.constructing_());
+        unsafe {
+            JS::MutableHandleValue::from_marked_location(
+                self._base.argv_.offset(self._base.argc_ as isize))
+        }
+    }
+}
+
+impl JSJitGetterCallArgs {
+    #[inline]
+    pub fn rval(&self) -> JS::MutableHandleValue {
+        self._base
+    }
+}
+
+impl JSJitSetterCallArgs {
+    #[inline]
+    pub fn get(&self, i: u32) -> JS::HandleValue {
+        assert!(i == 0);
+        self._base.handle()
+    }
+}
+
+// ___________________________________________________________________________
+// Wrappers around things in jsglue.cpp
+
+pub struct AutoObjectVectorWrapper {
+    pub ptr: *mut JS::AutoObjectVector
+}
+
+impl AutoObjectVectorWrapper {
+    pub fn new(cx: *mut JSContext) -> AutoObjectVectorWrapper {
+        AutoObjectVectorWrapper {
+            ptr: unsafe {
+                 CreateAutoObjectVector(cx)
+            }
+        }
+    }
+
+    pub fn append(&self, obj: *mut JSObject) -> bool {
+        unsafe {
+            AppendToAutoObjectVector(self.ptr, obj)
+        }
+    }
+}
+
+impl Drop for AutoObjectVectorWrapper {
+    fn drop(&mut self) {
+        unsafe { DeleteAutoObjectVector(self.ptr) }
+    }
+}
+
+pub struct CompileOptionsWrapper {
+    pub ptr: *mut JS::ReadOnlyCompileOptions
+}
+
+impl CompileOptionsWrapper {
+    pub fn new(cx: *mut JSContext, file: *const ::libc::c_char, line: c_uint) -> CompileOptionsWrapper {
+        CompileOptionsWrapper {
+            ptr: unsafe { NewCompileOptions(cx, file, line) }
+        }
+    }
+}
+
+impl Drop for CompileOptionsWrapper {
+    fn drop(&mut self) {
+        unsafe { DeleteCompileOptions(self.ptr) }
+    }
+}
+
+// ___________________________________________________________________________
+// Fast inline converters
+
+#[inline]
+pub unsafe fn ToBoolean(v: JS::HandleValue) -> bool {
+    let val = *v.ptr;
+
+    if val.is_boolean() {
+        return val.to_boolean();
+    }
+
+    if val.is_int32() {
+        return val.to_int32() != 0;
+    }
+
+    if val.is_null_or_undefined() {
+        return false;
+    }
+
+    if val.is_double() {
+        let d = val.to_double();
+        return !d.is_nan() && d != 0f64;
+    }
+
+    if val.is_symbol() {
+        return true;
+    }
+
+    js::ToBooleanSlow(v)
+}
+
+#[inline]
+pub unsafe fn ToNumber(cx: *mut JSContext, v: JS::HandleValue) -> Result<f64, ()> {
+    let val = *v.ptr;
+    if val.is_number() {
+        return Ok(val.to_number());
+    }
+
+    let mut out = Default::default();
+    if js::ToNumberSlow(cx, v, &mut out) {
+        Ok(out)
+    } else {
+        Err(())
+    }
+}
+
+#[inline]
+unsafe fn convert_from_int32<T: Default + Copy>(
+    cx: *mut JSContext,
+    v: JS::HandleValue,
+    conv_fn: unsafe extern "C" fn(*mut JSContext, JS::HandleValue, *mut T) -> bool)
+        -> Result<T, ()> {
+
+    let val = *v.ptr;
+    if val.is_int32() {
+        let intval: i64 = val.to_int32() as i64;
+        // TODO: do something better here that works on big endian
+        let intval = *(&intval as *const i64 as *const T);
+        return Ok(intval);
+    }
+
+    let mut out = Default::default();
+    if conv_fn(cx, v, &mut out) {
+        Ok(out)
+    } else {
+        Err(())
+    }
+}
+
+#[inline]
+pub unsafe fn ToInt32(cx: *mut JSContext, v: JS::HandleValue) -> Result<i32, ()> {
+    convert_from_int32::<i32>(cx, v, js::ToInt32Slow)
+}
+
+#[inline]
+pub unsafe fn ToUint32(cx: *mut JSContext, v: JS::HandleValue) -> Result<u32, ()> {
+    convert_from_int32::<u32>(cx, v, js::ToUint32Slow)
+}
+
+#[inline]
+pub unsafe fn ToUint16(cx: *mut JSContext, v: JS::HandleValue) -> Result<u16, ()> {
+    convert_from_int32::<u16>(cx, v, js::ToUint16Slow)
+}
+
+#[inline]
+pub unsafe fn ToInt64(cx: *mut JSContext, v: JS::HandleValue) -> Result<i64, ()> {
+    convert_from_int32::<i64>(cx, v, js::ToInt64Slow)
+}
+
+#[inline]
+pub unsafe fn ToUint64(cx: *mut JSContext, v: JS::HandleValue) -> Result<u64, ()> {
+    convert_from_int32::<u64>(cx, v, js::ToUint64Slow)
+}
+
+#[inline]
+pub unsafe fn ToString(cx: *mut JSContext, v: JS::HandleValue) -> *mut JSString {
+    let val = *v.ptr;
+    if val.is_string() {
+        return val.to_string();
+    }
+
+    js::ToStringSlow(cx, v)
+}
+
+pub unsafe extern fn report_warning(_cx: *mut JSContext, report: *mut JSErrorReport) {
+    fn latin1_to_string(bytes: &[u8]) -> String {
+        bytes.iter().map(|c| char::from_u32(*c as u32).unwrap()).collect()
+    }
+
+    let fnptr = (*report)._base.filename;
+    let fname = if !fnptr.is_null() {
+        let c_str = ffi::CStr::from_ptr(fnptr);
+        latin1_to_string(c_str.to_bytes())
+    } else {
+        "none".to_string()
+    };
+
+    let lineno = (*report)._base.lineno;
+    let column = (*report)._base.column;
+
+    let msg_ptr = (*report)._base.message_.data_ as *const u16;
+    let msg_len = (0usize..).find(|&i| *msg_ptr.offset(i as isize) == 0).unwrap();
+    let msg_slice = slice::from_raw_parts(msg_ptr, msg_len);
+    let msg = String::from_utf16_lossy(msg_slice);
+
+    warn!("Warning at {}:{}:{}: {}\n", fname, lineno, column, msg);
+}
+
+impl JSNativeWrapper {
+    fn is_zeroed(&self) -> bool {
+        let JSNativeWrapper { op, info } = *self;
+        op.is_none() && info.is_null()
+    }
+}
+
+pub struct IdVector(*mut JS::AutoIdVector);
+
+impl IdVector {
+    pub unsafe fn new(cx: *mut JSContext) -> IdVector {
+        let vector = CreateAutoIdVector(cx);
+        assert!(!vector.is_null());
+        IdVector(vector)
+    }
+
+    pub fn get(&self) -> *mut JS::AutoIdVector {
+        self.0
+    }
+}
+
+impl Drop for IdVector {
+    fn drop(&mut self) {
+        unsafe {
+            DestroyAutoIdVector(self.0)
+        }
+    }
+}
+
+impl Deref for IdVector {
+    type Target = [jsid];
+
+    fn deref(&self) -> &[jsid] {
+        unsafe {
+            let mut length = 0;
+            let pointer = SliceAutoIdVector(self.0 as *const _, &mut length);
+            slice::from_raw_parts(pointer, length)
+        }
+    }
+}
+
+/// Defines methods on `obj`. The last entry of `methods` must contain zeroed
+/// memory.
+///
+/// # Failures
+///
+/// Returns `Err` on JSAPI failure.
+///
+/// # Panics
+///
+/// Panics if the last entry of `methods` does not contain zeroed memory.
+///
+/// # Safety
+///
+/// - `cx` must be valid.
+/// - This function calls into unaudited C++ code.
+pub unsafe fn define_methods(cx: *mut JSContext, obj: JS::HandleObject,
+                             methods: &'static [JSFunctionSpec])
+                             -> Result<(), ()> {
+    assert!({
+        match methods.last() {
+            Some(&JSFunctionSpec { name, call, nargs, flags, selfHostedName }) => {
+                name.is_null() && call.is_zeroed() && nargs == 0 && flags == 0 &&
+                selfHostedName.is_null()
+            },
+            None => false,
+        }
+    });
+
+    JS_DefineFunctions(cx, obj, methods.as_ptr()).to_result()
+}
+
+/// Defines attributes on `obj`. The last entry of `properties` must contain
+/// zeroed memory.
+///
+/// # Failures
+///
+/// Returns `Err` on JSAPI failure.
+///
+/// # Panics
+///
+/// Panics if the last entry of `properties` does not contain zeroed memory.
+///
+/// # Safety
+///
+/// - `cx` must be valid.
+/// - This function calls into unaudited C++ code.
+pub unsafe fn define_properties(cx: *mut JSContext, obj: JS::HandleObject,
+                                properties: &'static [JSPropertySpec])
+                                -> Result<(), ()> {
+    assert!(!properties.is_empty());
+    assert!({
+        let spec = properties.last().unwrap();
+        let slice = slice::from_raw_parts(spec as *const _ as *const u8,
+                                          mem::size_of::<JSPropertySpec>());
+        slice.iter().all(|byte| *byte == 0)
+    });
+
+    JS_DefineProperties(cx, obj, properties.as_ptr()).to_result()
+}
+
+static SIMPLE_GLOBAL_CLASS_OPS: JSClassOps = JSClassOps {
+    addProperty: None,
+    delProperty: None,
+    enumerate: Some(JS_EnumerateStandardClasses),
+    newEnumerate: None,
+    resolve: Some(JS_ResolveStandardClass),
+    mayResolve: Some(JS_MayResolveStandardClass),
+    finalize: None,
+    call: None,
+    hasInstance: None,
+    construct: None,
+    trace: Some(JS_GlobalObjectTraceHook),
+};
+
+/// This is a simple `JSClass` for global objects, primarily intended for tests.
+pub static SIMPLE_GLOBAL_CLASS: JSClass = JSClass {
+    name: b"Global\0" as *const u8 as *const _,
+    flags: (JSCLASS_IS_GLOBAL | ((JSCLASS_GLOBAL_SLOT_COUNT & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT)) as u32,
+    cOps: &SIMPLE_GLOBAL_CLASS_OPS as *const JSClassOps,
+    reserved: [0 as *mut _; 3]
+};
+
+#[inline]
+unsafe fn get_object_group(obj: *mut JSObject) -> *mut js::shadow::ObjectGroup {
+    assert!(!obj.is_null());
+    let obj = obj as *mut js::shadow::Object;
+    (*obj).group
+}
+
+#[inline]
+pub unsafe fn get_object_class(obj: *mut JSObject) -> *const JSClass {
+    (*get_object_group(obj)).clasp as *const _
+}
+
+#[inline]
+pub unsafe fn get_object_compartment(obj: *mut JSObject) -> *mut JSCompartment {
+    (*get_object_group(obj)).compartment
+}
+
+#[inline]
+pub fn is_dom_class(class: &JSClass) -> bool {
+    class.flags & JSCLASS_IS_DOMJSCLASS != 0
+}
+
+#[inline]
+pub unsafe fn is_dom_object(obj: *mut JSObject) -> bool {
+    is_dom_class(&*get_object_class(obj))
+}
+
+#[inline]
+pub unsafe fn is_global(obj: *mut JSObject) -> bool {
+    (*get_object_class(obj)).flags & JSCLASS_IS_GLOBAL != 0
+}
+
+#[inline]
+pub unsafe fn is_window(obj: *mut JSObject) -> bool {
+    is_global(obj) && js::detail::IsWindowSlow(obj)
+}
+
+#[inline]
+pub unsafe fn try_to_outerize(rval: JS::MutableHandleValue) {
+    let obj = rval.to_object();
+    if is_window(obj) {
+        let obj = js::detail::ToWindowProxyIfWindowSlow(obj);
+        assert!(!obj.is_null());
+        rval.set(jsval::ObjectValue(&mut *obj));
+    }
+}
+
+#[inline]
+pub unsafe fn maybe_wrap_object_value(cx: *mut JSContext, rval: JS::MutableHandleValue) {
+    assert!(rval.is_object());
+
+    // There used to be inline checks if this out of line call was necessary or
+    // not here, but JSAPI no longer exposes a way to get a JSContext's
+    // compartment, and additionally JSContext is under a bunch of churn in
+    // JSAPI in general right now.
+
+    assert!(JS_WrapValue(cx, rval));
+}
+
+#[inline]
+pub unsafe fn maybe_wrap_object_or_null_value(
+        cx: *mut JSContext,
+        rval: JS::MutableHandleValue) {
+    assert!(rval.is_object_or_null());
+    if !rval.is_null() {
+        maybe_wrap_object_value(cx, rval);
+    }
+}
+
+#[inline]
+pub unsafe fn maybe_wrap_value(cx: *mut JSContext, rval: JS::MutableHandleValue) {
+    if rval.is_string() {
+        assert!(JS_WrapValue(cx, rval));
+    } else if rval.is_object() {
+        maybe_wrap_object_value(cx, rval);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/sc.rs
@@ -0,0 +1,102 @@
+/* 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/. */
+
+//! Nicer, Rust-y APIs for structured cloning.
+
+use glue;
+use jsapi;
+use rust::Runtime;
+use std::ptr;
+
+/// An RAII owned buffer for structured cloning into and out of.
+pub struct StructuredCloneBuffer {
+    raw: *mut jsapi::JSAutoStructuredCloneBuffer,
+}
+
+impl StructuredCloneBuffer {
+    /// Construct a new `StructuredCloneBuffer`.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the underlying JSAPI calls fail.
+    pub fn new(scope: jsapi::JS::StructuredCloneScope,
+               callbacks: &jsapi::JSStructuredCloneCallbacks)
+               -> StructuredCloneBuffer {
+        let raw = unsafe {
+            glue::NewJSAutoStructuredCloneBuffer(scope, callbacks)
+        };
+        assert!(!raw.is_null());
+        StructuredCloneBuffer {
+            raw: raw,
+        }
+    }
+
+    /// Get the raw `*mut JSStructuredCloneData` owned by this buffer.
+    pub fn data(&self) -> *mut jsapi::JSStructuredCloneData {
+        unsafe {
+            &mut (*self.raw).data_
+        }
+    }
+
+    /// Copy this buffer's data into a vec.
+    pub fn copy_to_vec(&self) -> Vec<u8> {
+        let len = unsafe {
+            glue::GetLengthOfJSStructuredCloneData(self.data())
+        };
+        let mut vec = Vec::with_capacity(len);
+        unsafe {
+            glue::CopyJSStructuredCloneData(self.data(), vec.as_mut_ptr());
+        }
+        vec
+    }
+
+    /// Read a JS value out of this buffer.
+    pub fn read(&mut self,
+                vp: jsapi::JS::MutableHandleValue,
+                callbacks: &jsapi::JSStructuredCloneCallbacks)
+                -> Result<(), ()> {
+        if unsafe {
+            (*self.raw).read(Runtime::get(), vp, callbacks, ptr::null_mut())
+        } {
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+
+    /// Write a JS value into this buffer.
+    pub fn write(&mut self,
+                 v: jsapi::JS::HandleValue,
+                 callbacks: &jsapi::JSStructuredCloneCallbacks)
+                 -> Result<(), ()> {
+        if unsafe {
+            (*self.raw).write(Runtime::get(), v, callbacks, ptr::null_mut())
+        } {
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+
+    /// Copy the given slice into this buffer.
+    pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), ()> {
+        let len = bytes.len();
+        let src = bytes.as_ptr();
+        if unsafe {
+            glue::WriteBytesToJSStructuredCloneData(src, len, self.data())
+        } {
+            Ok(())
+        } else {
+            Err(())
+        }
+    }
+}
+
+impl Drop for StructuredCloneBuffer {
+    fn drop(&mut self) {
+        unsafe {
+            glue::DeleteJSAutoStructuredCloneBuffer(self.raw);
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/src/typedarray.rs
@@ -0,0 +1,301 @@
+/* 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/. */
+
+//! High-level, safe bindings for JS typed array APIs. Allows creating new
+//! typed arrays or wrapping existing JS reflectors, and prevents reinterpreting
+//! existing buffers as different types except in well-defined cases.
+
+use glue::GetFloat32ArrayLengthAndData;
+use glue::GetFloat64ArrayLengthAndData;
+use glue::GetInt16ArrayLengthAndData;
+use glue::GetInt32ArrayLengthAndData;
+use glue::GetInt8ArrayLengthAndData;
+use glue::GetUint16ArrayLengthAndData;
+use glue::GetUint32ArrayLengthAndData;
+use glue::GetUint8ArrayLengthAndData;
+use glue::GetUint8ClampedArrayLengthAndData;
+use jsapi::*;
+use jsapi::js::*;
+use jsapi::JS::*;
+use rust::RootedGuard;
+use std::ptr;
+use std::slice;
+
+pub enum CreateWith<'a, T: 'a> {
+    Length(u32),
+    Slice(&'a [T]),
+}
+
+/// A rooted typed array.
+pub struct TypedArray<'a, T: 'a + TypedArrayElement> {
+    object: RootedGuard<'a, *mut JSObject>,
+    computed: Option<(*mut T::Element, u32)>,
+}
+
+impl<'a, T: TypedArrayElement> TypedArray<'a, T> {
+    /// Create a typed array representation that wraps an existing JS reflector.
+    /// This operation will fail if attempted on a JS object that does not match
+    /// the expected typed array details.
+    pub fn from(cx: *mut JSContext,
+                root: &'a mut Rooted<*mut JSObject>,
+                object: *mut JSObject)
+                -> Result<Self, ()> {
+        if object.is_null() {
+            return Err(());
+        }
+        unsafe {
+            let mut guard = RootedGuard::new(cx, root, object);
+            let unwrapped = T::unwrap_array(*guard);
+            if unwrapped.is_null() {
+                return Err(());
+            }
+
+            *guard = unwrapped;
+            Ok(TypedArray {
+                object: guard,
+                computed: None,
+            })
+        }
+    }
+
+    fn data(&mut self) -> (*mut T::Element, u32) {
+        if let Some(data) = self.computed {
+            return data;
+        }
+
+        let data = unsafe { T::length_and_data(*self.object) };
+        self.computed = Some(data);
+        data
+    }
+
+    /// # Unsafety
+    ///
+    /// The returned slice can be invalidated if the underlying typed array
+    /// is neutered.
+    pub unsafe fn as_slice(&mut self) -> &[T::Element] {
+        let (pointer, length) = self.data();
+        slice::from_raw_parts(pointer as *const T::Element, length as usize)
+    }
+
+    /// # Unsafety
+    ///
+    /// The returned slice can be invalidated if the underlying typed array
+    /// is neutered.
+    ///
+    /// The underlying `JSObject` can be aliased, which can lead to
+    /// Undefined Behavior due to mutable aliasing.
+    pub unsafe fn as_mut_slice(&mut self) -> &mut [T::Element] {
+        let (pointer, length) = self.data();
+        slice::from_raw_parts_mut(pointer, length as usize)
+    }
+}
+
+impl<'a, T: TypedArrayElementCreator + TypedArrayElement> TypedArray<'a, T> {
+    /// Create a new JS typed array, optionally providing initial data that will
+    /// be copied into the newly-allocated buffer. Returns the new JS reflector.
+    pub unsafe fn create(cx: *mut JSContext,
+                         with: CreateWith<T::Element>,
+                         result: MutableHandleObject)
+                         -> Result<(), ()> {
+        let length = match with {
+            CreateWith::Length(len) => len,
+            CreateWith::Slice(slice) => slice.len() as u32,
+        };
+
+        result.set(T::create_new(cx, length));
+        if result.get().is_null() {
+            return Err(());
+        }
+
+        if let CreateWith::Slice(data) = with {
+            TypedArray::<T>::update_raw(data, result.handle());
+        }
+
+        Ok(())
+    }
+
+    ///  Update an existed JS typed array
+    pub unsafe fn update(&mut self, data: &[T::Element]) {
+        TypedArray::<T>::update_raw(data, self.object.handle());
+    }
+
+    unsafe fn update_raw(data: &[T::Element], result: HandleObject) {
+        let (buf, length) = T::length_and_data(result.get());
+        assert!(data.len() <= length as usize);
+        ptr::copy_nonoverlapping(data.as_ptr(), buf, data.len());
+    }
+}
+
+/// Internal trait used to associate an element type with an underlying representation
+/// and various functions required to manipulate typed arrays of that element type.
+pub trait TypedArrayElement {
+    /// Underlying primitive representation of this element type.
+    type Element;
+    /// Unwrap a typed array JS reflector for this element type.
+    unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject;
+    /// Retrieve the length and data of a typed array's buffer for this element type.
+    unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32);
+}
+
+/// Internal trait for creating new typed arrays.
+pub trait TypedArrayElementCreator: TypedArrayElement {
+    /// Create a new typed array.
+    unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject;
+    /// Get the data.
+    unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element;
+}
+
+macro_rules! typed_array_element {
+    ($t: ident,
+     $element: ty,
+     $unwrap: ident,
+     $length_and_data: ident) => (
+        /// A kind of typed array.
+        pub struct $t;
+
+        impl TypedArrayElement for $t {
+            type Element = $element;
+            unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject {
+                $unwrap(obj)
+            }
+
+            unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32) {
+                let mut len = 0;
+                let mut shared = false;
+                let mut data = ptr::null_mut();
+                $length_and_data(obj, &mut len, &mut shared, &mut data);
+                assert!(!shared);
+                (data, len)
+            }
+        }
+    );
+
+    ($t: ident,
+     $element: ty,
+     $unwrap: ident,
+     $length_and_data: ident,
+     $create_new: ident,
+     $get_data: ident) => (
+        typed_array_element!($t, $element, $unwrap, $length_and_data);
+
+        impl TypedArrayElementCreator for $t {
+            unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject {
+                $create_new(cx, length)
+            }
+
+            unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element {
+                let mut shared = false;
+                let data = $get_data(obj, &mut shared, ptr::null_mut());
+                assert!(!shared);
+                data
+            }
+        }
+    );
+}
+
+typed_array_element!(Uint8,
+                     u8,
+                     UnwrapUint8Array,
+                     GetUint8ArrayLengthAndData,
+                     JS_NewUint8Array,
+                     JS_GetUint8ArrayData);
+typed_array_element!(Uint16,
+                     u16,
+                     UnwrapUint16Array,
+                     GetUint16ArrayLengthAndData,
+                     JS_NewUint16Array,
+                     JS_GetUint16ArrayData);
+typed_array_element!(Uint32,
+                     u32,
+                     UnwrapUint32Array,
+                     GetUint32ArrayLengthAndData,
+                     JS_NewUint32Array,
+                     JS_GetUint32ArrayData);
+typed_array_element!(Int8,
+                     i8,
+                     UnwrapInt8Array,
+                     GetInt8ArrayLengthAndData,
+                     JS_NewInt8Array,
+                     JS_GetInt8ArrayData);
+typed_array_element!(Int16,
+                     i16,
+                     UnwrapInt16Array,
+                     GetInt16ArrayLengthAndData,
+                     JS_NewInt16Array,
+                     JS_GetInt16ArrayData);
+typed_array_element!(Int32,
+                     i32,
+                     UnwrapInt32Array,
+                     GetInt32ArrayLengthAndData,
+                     JS_NewInt32Array,
+                     JS_GetInt32ArrayData);
+typed_array_element!(Float32,
+                     f32,
+                     UnwrapFloat32Array,
+                     GetFloat32ArrayLengthAndData,
+                     JS_NewFloat32Array,
+                     JS_GetFloat32ArrayData);
+typed_array_element!(Float64,
+                     f64,
+                     UnwrapFloat64Array,
+                     GetFloat64ArrayLengthAndData,
+                     JS_NewFloat64Array,
+                     JS_GetFloat64ArrayData);
+typed_array_element!(ClampedU8,
+                     u8,
+                     UnwrapUint8ClampedArray,
+                     GetUint8ClampedArrayLengthAndData,
+                     JS_NewUint8ClampedArray,
+                     JS_GetUint8ClampedArrayData);
+typed_array_element!(ArrayBufferU8,
+                     u8,
+                     UnwrapArrayBuffer,
+                     GetArrayBufferLengthAndData,
+                     JS_NewArrayBuffer,
+                     JS_GetArrayBufferData);
+typed_array_element!(ArrayBufferViewU8,
+                     u8,
+                     UnwrapArrayBufferView,
+                     GetArrayBufferViewLengthAndData);
+
+/// The Uint8ClampedArray type.
+pub type Uint8ClampedArray<'a> = TypedArray<'a, ClampedU8>;
+/// The Uint8Array type.
+pub type Uint8Array<'a> = TypedArray<'a, Uint8>;
+/// The Int8Array type.
+pub type Int8Array<'a> = TypedArray<'a, Int8>;
+/// The Uint16Array type.
+pub type Uint16Array<'a> = TypedArray<'a, Uint16>;
+/// The Int16Array type.
+pub type Int16Array<'a> = TypedArray<'a, Int16>;
+/// The Uint32Array type.
+pub type Uint32Array<'a> = TypedArray<'a, Uint32>;
+/// The Int32Array type.
+pub type Int32Array<'a> = TypedArray<'a, Int32>;
+/// The Float32Array type.
+pub type Float32Array<'a> = TypedArray<'a, Float32>;
+/// The Float64Array type.
+pub type Float64Array<'a> = TypedArray<'a, Float64>;
+/// The ArrayBuffer type.
+pub type ArrayBuffer<'a> = TypedArray<'a, ArrayBufferU8>;
+/// The ArrayBufferView type
+pub type ArrayBufferView<'a> = TypedArray<'a, ArrayBufferViewU8>;
+
+impl<'a> ArrayBufferView<'a> {
+    pub fn get_array_type(&self) -> Scalar::Type {
+        unsafe { JS_GetArrayBufferViewType(self.object.get()) }
+    }
+}
+
+#[macro_export]
+macro_rules! typedarray {
+    (in($cx:expr) let $name:ident : $ty:ident = $init:expr) => {
+        let mut __root = $crate::jsapi::JS::Rooted::new_unrooted();
+        let $name = $crate::typedarray::$ty::from($cx, &mut __root, $init);
+    };
+    (in($cx:expr) let mut $name:ident : $ty:ident = $init:expr) => {
+        let mut __root = $crate::jsapi::JS::Rooted::new_unrooted();
+        let mut $name = $crate::typedarray::$ty::from($cx, &mut __root, $init);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/callback.rs
@@ -0,0 +1,64 @@
+/* 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/. */
+
+#[macro_use]
+extern crate js;
+extern crate libc;
+
+use js::ac::AutoCompartment;
+use js::jsapi::root::JS::CallArgs;
+use js::jsapi::root::JS::CompartmentOptions;
+use js::jsapi::root::JSContext;
+use js::jsapi::root::JS_DefineFunction;
+use js::jsapi::root::JS_EncodeStringToUTF8;
+use js::jsapi::root::JS_NewGlobalObject;
+use js::jsapi::root::JS_ReportErrorASCII;
+use js::jsapi::root::JS::OnNewGlobalHookOption;
+use js::jsapi::root::JS::Value;
+use js::jsval::UndefinedValue;
+use js::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
+
+use std::ffi::CStr;
+use std::ptr;
+use std::str;
+
+#[test]
+fn callback() {
+    let runtime = Runtime::new().unwrap();
+    let context = runtime.cx();
+    let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
+    let c_option = CompartmentOptions::default();
+
+    unsafe {
+        let global = JS_NewGlobalObject(context, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(), h_option, &c_option);
+        rooted!(in(context) let global_root = global);
+        let global = global_root.handle();
+        let _ac = AutoCompartment::with_obj(context, global.get());
+        let function = JS_DefineFunction(context, global, b"puts\0".as_ptr() as *const libc::c_char,
+                                         Some(puts), 1, 0);
+        assert!(!function.is_null());
+        let javascript = "puts('Test Iñtërnâtiônàlizætiøn ┬─┬ノ( º _ ºノ) ');";
+        rooted!(in(context) let mut rval = UndefinedValue());
+        let _ = runtime.evaluate_script(global, javascript, "test.js", 0, rval.handle_mut());
+    }
+}
+
+unsafe extern "C" fn puts(context: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
+    let args = CallArgs::from_vp(vp, argc);
+
+    if args._base.argc_ != 1 {
+        JS_ReportErrorASCII(context, b"puts() requires exactly 1 argument\0".as_ptr() as *const libc::c_char);
+        return false;
+    }
+
+    let arg = args.get(0);
+    let js = js::rust::ToString(context, arg);
+    rooted!(in(context) let message_root = js);
+    let message = JS_EncodeStringToUTF8(context, message_root.handle());
+    let message = CStr::from_ptr(message);
+    println!("{}", str::from_utf8(message.to_bytes()).unwrap());
+
+    args.rval().set(UndefinedValue());
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/enumerate.rs
@@ -0,0 +1,56 @@
+/* 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/. */
+
+#[macro_use]
+extern crate js;
+
+use js::glue::RUST_JSID_IS_STRING;
+use js::glue::RUST_JSID_TO_STRING;
+use js::jsapi::root::JS::CompartmentOptions;
+use js::jsapi::root::js::GetPropertyKeys;
+use js::jsapi::root::JSITER_OWNONLY;
+use js::jsapi::root::JS_NewGlobalObject;
+use js::jsapi::root::JS_StringEqualsAscii;
+use js::jsapi::root::JS::OnNewGlobalHookOption;
+use js::jsval::UndefinedValue;
+use js::rust::IdVector;
+use js::rust::Runtime;
+use js::rust::SIMPLE_GLOBAL_CLASS;
+use std::ptr;
+
+#[test]
+fn enumerate() {
+    let rt = Runtime::new().unwrap();
+    let cx = rt.cx();
+
+    unsafe {
+        rooted!(in(cx) let global =
+            JS_NewGlobalObject(cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
+                               OnNewGlobalHookOption::FireOnNewGlobalHook,
+                               &CompartmentOptions::default())
+        );
+
+        rooted!(in(cx) let mut rval = UndefinedValue());
+        assert!(rt.evaluate_script(global.handle(), "({ 'a': 7 })",
+                                   "test", 1, rval.handle_mut()).is_ok());
+        assert!(rval.is_object());
+
+        rooted!(in(cx) let object = rval.to_object());
+        let ids = IdVector::new(cx);
+        assert!(GetPropertyKeys(cx, object.handle(), JSITER_OWNONLY, ids.get()));
+
+        assert_eq!(ids.len(), 1);
+        rooted!(in(cx) let id = ids[0]);
+
+        assert!(RUST_JSID_IS_STRING(id.handle()));
+        rooted!(in(cx) let id = RUST_JSID_TO_STRING(id.handle()));
+
+        let mut matches = false;
+        assert!(JS_StringEqualsAscii(cx,
+                                     id.get(),
+                                     b"a\0" as *const _ as *const _,
+                                     &mut matches));
+        assert!(matches);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/evaluate.rs
@@ -0,0 +1,32 @@
+/* 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/. */
+
+#[macro_use]
+extern crate js;
+
+use js::jsapi::root::JS::CompartmentOptions;
+use js::jsapi::root::JS_NewGlobalObject;
+use js::jsapi::root::JS::OnNewGlobalHookOption;
+use js::jsval::UndefinedValue;
+use js::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
+
+use std::ptr;
+
+#[test]
+fn evaluate() {
+    let rt = Runtime::new().unwrap();
+    let cx = rt.cx();
+
+    unsafe {
+
+        rooted!(in(cx) let global =
+            JS_NewGlobalObject(cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
+                               OnNewGlobalHookOption::FireOnNewGlobalHook,
+                               &CompartmentOptions::default())
+        );
+        rooted!(in(cx) let mut rval = UndefinedValue());
+        assert!(rt.evaluate_script(global.handle(), "1 + 1",
+                                   "test", 1, rval.handle_mut()).is_ok());
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/panic.rs
@@ -0,0 +1,43 @@
+/* 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/. */
+
+#[macro_use]
+extern crate js;
+
+use js::jsapi::*;
+use js::jsval::UndefinedValue;
+use js::panic::wrap_panic;
+use js::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
+use std::ptr;
+use std::str;
+
+#[test]
+#[should_panic]
+fn panic() {
+    let runtime = Runtime::new().unwrap();
+    let context = runtime.cx();
+    let h_option = JS::OnNewGlobalHookOption::FireOnNewGlobalHook;
+    let c_option = JS::CompartmentOptions::default();
+
+    unsafe {
+        let global = JS_NewGlobalObject(context, &SIMPLE_GLOBAL_CLASS,
+                                        ptr::null_mut(), h_option, &c_option);
+        rooted!(in(context) let global_root = global);
+        let global = global_root.handle();
+        let _ac = js::ac::AutoCompartment::with_obj(context, global.get());
+        let function = JS_DefineFunction(context, global,
+                                         b"test\0".as_ptr() as *const _,
+                                         Some(test), 0, 0);
+        assert!(!function.is_null());
+        rooted!(in(context) let mut rval = UndefinedValue());
+        let _ = runtime.evaluate_script(global, "test();", "test.js", 0,
+                                        rval.handle_mut());
+    }
+}
+
+unsafe extern "C" fn test(_cx: *mut JSContext, _argc: u32, _vp: *mut JS::Value) -> bool {
+    wrap_panic(|| {
+        panic!()
+    }, false)
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/rooting.rs
@@ -0,0 +1,83 @@
+/* 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/. */
+
+#![cfg(feature = "debugmozjs")]
+
+#[macro_use]
+extern crate js;
+#[macro_use]
+extern crate lazy_static;
+extern crate libc;
+
+use js::jsapi::*;
+use js::rust::{Runtime, SIMPLE_GLOBAL_CLASS, define_methods};
+use std::ptr;
+
+#[test]
+fn rooting() {
+    unsafe {
+        let runtime = Runtime::new().unwrap();
+        JS_SetGCZeal(runtime.cx(), 2, 1);
+
+        let cx = runtime.cx();
+        let h_option = JS::OnNewGlobalHookOption::FireOnNewGlobalHook;
+        let c_option = JS::CompartmentOptions::default();
+
+        rooted!(in(cx) let global = JS_NewGlobalObject(cx,
+                                                       &SIMPLE_GLOBAL_CLASS,
+                                                       ptr::null_mut(),
+                                                       h_option,
+                                                       &c_option));
+        let _ac = js::ac::AutoCompartment::with_obj(cx, global.get());
+        rooted!(in(cx) let prototype_proto = JS_GetObjectPrototype(cx, global.handle()));
+        rooted!(in(cx) let proto = JS_NewObjectWithUniqueType(cx,
+                                                              &CLASS as *const _,
+                                                              prototype_proto.handle()));
+        define_methods(cx, proto.handle(), &METHODS[..]).unwrap();
+    }
+}
+
+unsafe extern "C" fn generic_method(_: *mut JSContext, _: u32, _: *mut JS::Value) -> bool {
+    true
+}
+
+lazy_static! {
+    static ref METHODS: [JSFunctionSpec; 4] = [
+        JSFunctionSpec {
+            name: b"addEventListener\0" as *const u8 as *const libc::c_char,
+            call: JSNativeWrapper { op: Some(generic_method), info: ptr::null() },
+            nargs: 2,
+            flags: JSPROP_ENUMERATE as u16,
+            selfHostedName: 0 as *const libc::c_char
+        },
+        JSFunctionSpec {
+            name: b"removeEventListener\0" as *const u8 as *const libc::c_char,
+            call: JSNativeWrapper { op: Some(generic_method), info: ptr::null() },
+            nargs: 2,
+            flags: JSPROP_ENUMERATE as u16,
+            selfHostedName: 0 as *const libc::c_char
+        },
+        JSFunctionSpec {
+            name: b"dispatchEvent\0" as *const u8 as *const libc::c_char,
+            call: JSNativeWrapper { op: Some(generic_method), info: ptr::null() },
+            nargs: 1,
+            flags: JSPROP_ENUMERATE as u16,
+            selfHostedName: 0 as *const libc::c_char
+        },
+        JSFunctionSpec {
+            name: ptr::null(),
+            call: JSNativeWrapper { op: None, info: ptr::null() },
+            nargs: 0,
+            flags: 0,
+            selfHostedName: ptr::null()
+        }
+    ];
+}
+
+static CLASS: JSClass = JSClass {
+    name: b"EventTargetPrototype\0" as *const u8 as *const libc::c_char,
+    flags: 0,
+    cOps: 0 as *const _,
+    reserved: [0 as *mut _; 3]
+};
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/runtime.rs
@@ -0,0 +1,57 @@
+/* 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/. */
+
+#[macro_use]
+extern crate js;
+extern crate libc;
+
+use js::jsapi::*;
+use js::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
+use std::ptr;
+
+#[test]
+fn runtime() {
+    unsafe {
+        let runtime = Runtime::new().unwrap();
+
+        let cx = runtime.cx();
+        let h_option = JS::OnNewGlobalHookOption::FireOnNewGlobalHook;
+        let c_option = JS::CompartmentOptions::default();
+
+        rooted!(in(cx) let global = JS_NewGlobalObject(cx,
+                                                       &SIMPLE_GLOBAL_CLASS,
+                                                       ptr::null_mut(),
+                                                       h_option,
+                                                       &c_option));
+        let _ac = js::ac::AutoCompartment::with_obj(cx, global.get());
+        rooted!(in(cx) let _object = JS_NewObject(cx, &CLASS as *const _));
+    }
+
+    assert!(Runtime::new().is_err());
+}
+
+unsafe extern fn finalize(_fop: *mut JSFreeOp, _object: *mut JSObject) {
+    assert!(!Runtime::get().is_null());
+}
+
+static CLASS_OPS: JSClassOps = JSClassOps {
+    addProperty: None,
+    delProperty: None,
+    enumerate: None,
+    newEnumerate: None,
+    resolve: None,
+    mayResolve: None,
+    finalize: Some(finalize),
+    call: None,
+    hasInstance: None,
+    construct: None,
+    trace: None,
+};
+
+static CLASS: JSClass = JSClass {
+    name: b"EventTargetPrototype\0" as *const u8 as *const libc::c_char,
+    flags: 0 | JSCLASS_FOREGROUND_FINALIZE,
+    cOps: &CLASS_OPS as *const JSClassOps,
+    reserved: [0 as *mut _; 3]
+};
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/stack_limit.rs
@@ -0,0 +1,32 @@
+/* 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/. */
+
+#[macro_use]
+extern crate js;
+
+use js::jsapi::root::JS::CompartmentOptions;
+use js::jsapi::root::JS_NewGlobalObject;
+use js::jsapi::root::JS::OnNewGlobalHookOption;
+use js::jsval::UndefinedValue;
+use js::rust::{Runtime, SIMPLE_GLOBAL_CLASS};
+
+use std::ptr;
+
+#[test]
+fn stack_limit() {
+    let rt = Runtime::new().unwrap();
+    let cx = rt.cx();
+
+    unsafe {
+        let h_option = OnNewGlobalHookOption::FireOnNewGlobalHook;
+        let c_option = CompartmentOptions::default();
+        let global = JS_NewGlobalObject(cx, &SIMPLE_GLOBAL_CLASS,
+                                        ptr::null_mut(), h_option, &c_option);
+        rooted!(in(cx) let global_root = global);
+        let global = global_root.handle();
+        rooted!(in(cx) let mut rval = UndefinedValue());
+        assert!(rt.evaluate_script(global, "function f() { f.apply() } f()",
+                                   "test", 1, rval.handle_mut()).is_err());
+    }
+}
new file mode 100644
--- /dev/null
+++ b/js/rust/tests/typedarray.rs
@@ -0,0 +1,91 @@
+/* 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/. */
+
+#[macro_use]
+extern crate js;
+
+use js::jsapi::*;
+use js::jsval::UndefinedValue;
+use js::rust::Runtime as Runtime_;
+use js::rust::SIMPLE_GLOBAL_CLASS;
+use js::typedarray::{CreateWith, Uint32Array};
+use std::ptr;
+
+#[test]
+fn typedarray() {
+    let rt = Runtime_::