Merge m-c to birch.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 29 Apr 2013 08:24:42 -0400
changeset 130172 c8b361ae0c70dc7a700a06093024d97375ed108e
parent 130171 9ae254158eae833f9ecbcbb127558559e08641bf (current diff)
parent 130164 05533d50f2f7fa60667b89829bd779d7263df7ca (diff)
child 130173 1be592ac3c8d2c1288921f21c6af37331d538248
push id24602
push userryanvm@gmail.com
push dateMon, 29 Apr 2013 15:24:29 +0000
treeherdermozilla-central@06f4af96580e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to birch.
content/svg/content/test/test_pointer-events.xhtml
toolkit/mozapps/update/test/unit/test_0160_appInUse_xp_unix_complete.js
toolkit/mozapps/update/test/unit/test_0160_appInUse_xp_win_complete.js
toolkit/mozapps/update/test_svc/unit/test_0160_appInUse_xp_win_complete_svc.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -266,19 +266,20 @@ pref("media.preload.auto", 2);    // pre
 pref("media.cache_size", 4096);    // 4MB media cache
 
 // The default number of decoded video frames that are enqueued in
 // MediaDecoderReader's mVideoQueue.
 pref("media.video-queue.default-size", 3);
 
 // optimize images' memory usage
 pref("image.mem.decodeondraw", true);
-pref("content.image.allow_locking", true);
-pref("image.mem.min_discard_timeout_ms", 10000);
-pref("image.mem.max_decoded_image_kb", 5120); /* 5MB */
+pref("content.image.allow_locking", false); /* don't allow image locking */
+pref("image.mem.min_discard_timeout_ms", 86400000); /* 24h, we rely on the out of memory hook */
+pref("image.mem.max_decoded_image_kb", 30000); /* 30MB seems reasonable */
+pref("image.onload.decode.limit", 24); /* don't decode more than 24 images eagerly */
 
 // XXX this isn't a good check for "are touch events supported", but
 // we don't really have a better one at the moment.
 #ifdef MOZ_WIDGET_GONK
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1366143901000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1366828228000">
   <emItems>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i41" id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
                         <versionRange  minVersion="0.1" maxVersion="4.3.1.00" severity="1">
                     </versionRange>
@@ -197,16 +197,20 @@
       <emItem  blockID="i127" id="plugin@youtubeplayer.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i77" id="{fa277cfc-1d75-4949-a1f9-4ac8e41b2dfd}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i338" id="{1FD91A9C-410C-4090-BBCC-55D3450EF433}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i59" id="ghostviewer@youtube2.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i222" id="dealcabby@jetpack">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
@@ -282,16 +286,20 @@
       <emItem  blockID="i16" id="{27182e60-b5f3-411c-b545-b44205977502}">
                         <versionRange  minVersion="1.0" maxVersion="1.0">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i92" id="play5@vide04flash.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i109" id="{392e123b-b691-4a5e-b52f-c4c1027e749c}">
+                        <versionRange  minVersion="0" maxVersion="*">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i286" id="{58bd07eb-0ee0-4df0-8121-dc9b693373df}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i1" id="mozilla_cc@internetdownloadmanager.com">
                         <versionRange  minVersion="2.1" maxVersion="3.3">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="3.0a1" maxVersion="*" />
@@ -308,18 +316,18 @@
                     </versionRange>
                   </emItem>
       <emItem  blockID="i13" id="{E8E88AB0-7182-11DF-904E-6045E0D72085}">
                         </emItem>
       <emItem  blockID="i83" id="flash@adobee.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
-      <emItem  blockID="i109" id="{392e123b-b691-4a5e-b52f-c4c1027e749c}">
-                        <versionRange  minVersion="0" maxVersion="*">
+      <emItem  blockID="i218" id="ffxtlbr@claro.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i76" id="crossriderapp3924@crossrider.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i262" id="{167d9323-f7cc-48f5-948a-6f012831a69f}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
@@ -359,16 +367,20 @@
                     </versionRange>
                   </emItem>
       <emItem  blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i47" id="youtube@youtube2.com">
                         </emItem>
+      <emItem  blockID="i336" id="CortonExt@ext.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i103" id="kdrgun@gmail.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i320" id="torntv@torntv.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
@@ -440,20 +452,16 @@
       <emItem  blockID="i220" id="pricepeep@getpricepeep.com">
                         <versionRange  minVersion="0" maxVersion="2.1.0.19.99" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i226" id="{462be121-2b54-4218-bf00-b9bf8135b23f}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
-      <emItem  blockID="i218" id="ffxtlbr@claro.com">
-                        <versionRange  minVersion="0" maxVersion="*" severity="1">
-                    </versionRange>
-                  </emItem>
       <emItem  blockID="i70" id="psid-vhvxQHMZBOzUZA@jetpack">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i306" id="{ADFA33FD-16F5-4355-8504-DF4D664CFE10}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -10,17 +10,17 @@
 
 <!DOCTYPE window [
   <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
   %newTabDTD;
 ]>
 
 <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-            disablefastfind="true" title="&newtab.pageTitle;">
+            title="&newtab.pageTitle;">
 
   <div id="newtab-scrollbox">
 
     <div id="newtab-vertical-margin">
       <div id="newtab-margin-top">
         <div id="newtab-undo-container" undo-disabled="true">
           <xul:label id="newtab-undo-label"
                      value="&newtab.undo.removedLabel;" />
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1119,24 +1119,22 @@
                   title = browser.currentURI.spec;
                 }
               }
 
               if (title && !isBlankPageURL(title)) {
                 // At this point, we now have a URI.
                 // Let's try to unescape it using a character set
                 // in case the URI is not ASCII.
-                if (!gMultiProcessBrowser) {
-                  try {
-                    var characterSet = browser.contentDocument.characterSet;
-                    const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
-                                                   .getService(Components.interfaces.nsITextToSubURI);
-                    title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
-                  } catch(ex) { /* Do nothing. */ }
-                }
+                try {
+                  var characterSet = browser.characterSet;
+                  const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
+                                                 .getService(Components.interfaces.nsITextToSubURI);
+                  title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
+                } catch(ex) { /* Do nothing. */ }
 
                 crop = "center";
 
               } else // Still no title?  Fall back to our untitled string.
                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
             }
 
             if (aTab.label == title &&
--- a/browser/branding/aurora/branding.nsi
+++ b/browser/branding/aurora/branding.nsi
@@ -9,17 +9,17 @@
 # BrandFullNameInternal is used for some registry and file system values
 # instead of BrandFullName and typically should not be modified.
 !define BrandFullNameInternal "Aurora"
 !define CompanyName           "mozilla.org"
 !define URLInfoAbout          "http://www.mozilla.org"
 !define URLUpdateInfo         "http://www.mozilla.org/projects/firefox"
 
 !define URLStubDownload "http://download.mozilla.org/?product=firefox-aurora-latest&os=win&lang=${AB_CD}"
-!define URLManualDownload "https://www.mozilla.org/firefox/installer-help/?channel=aurora"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=aurora&installer_lang=${AB_CD}"
 !define Channel "aurora"
 
 # The installer's certificate name and issuer expected by the stub installer
 !define CertNameDownload   "Mozilla Corporation"
 !define CertIssuerDownload "Thawte Code Signing CA - G2"
 
 # Dialog units are used so the UI displays correctly with the system's DPI
 # settings.
--- a/browser/branding/nightly/branding.nsi
+++ b/browser/branding/nightly/branding.nsi
@@ -9,17 +9,17 @@
 # BrandFullNameInternal is used for some registry and file system values
 # instead of BrandFullName and typically should not be modified.
 !define BrandFullNameInternal "Nightly"
 !define CompanyName           "mozilla.org"
 !define URLInfoAbout          "http://www.mozilla.org"
 !define URLUpdateInfo         "http://www.mozilla.org/projects/firefox"
 
 !define URLStubDownload "http://download.mozilla.org/?product=firefox-nightly-latest&os=win&lang=${AB_CD}"
-!define URLManualDownload "https://www.mozilla.org/firefox/installer-help/?channel=nightly"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=nightly&installer_lang=${AB_CD}"
 !define Channel "nightly"
 
 # The installer's certificate name and issuer expected by the stub installer
 !define CertNameDownload   "Mozilla Corporation"
 !define CertIssuerDownload "Thawte Code Signing CA - G2"
 
 # Dialog units are used so the UI displays correctly with the system's DPI
 # settings.
--- a/browser/branding/official/branding.nsi
+++ b/browser/branding/official/branding.nsi
@@ -13,17 +13,17 @@
 !define URLInfoAbout          "http://www.mozilla.com/${AB_CD}/"
 !define URLUpdateInfo         "http://www.mozilla.com/${AB_CD}/firefox/"
 
 ; The OFFICIAL define is a workaround to support different urls for Release and
 ; Beta since they share the same branding when building with other branches that
 ; set the update channel to beta.
 !define OFFICIAL
 !define URLStubDownload "http://download.mozilla.org/?product=firefox-latest&os=win&lang=${AB_CD}"
-!define URLManualDownload "https://www.mozilla.org/firefox/installer-help/?channel=release"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=release&installer_lang=${AB_CD}"
 !define Channel "release"
 
 # The installer's certificate name and issuer expected by the stub installer
 !define CertNameDownload   "Mozilla Corporation"
 !define CertIssuerDownload "Thawte Code Signing CA - G2"
 
 # Dialog units are used so the UI displays correctly with the system's DPI
 # settings.
--- a/browser/branding/unofficial/branding.nsi
+++ b/browser/branding/unofficial/branding.nsi
@@ -9,17 +9,17 @@
 # BrandFullNameInternal is used for some registry and file system values
 # instead of BrandFullName and typically should not be modified.
 !define BrandFullNameInternal "Mozilla Developer Preview"
 !define CompanyName           "mozilla.org"
 !define URLInfoAbout          "http://www.mozilla.org"
 !define URLUpdateInfo         "http://www.mozilla.org/projects/firefox"
 
 !define URLStubDownload "http://download.mozilla.org/?product=firefox-latest&os=win&lang=${AB_CD}"
-!define URLManualDownload "https://www.mozilla.org/firefox/installer-help/?channel=release"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=release&installer_lang=${AB_CD}"
 !define Channel "unofficial"
 
 # The installer's certificate name and issuer expected by the stub installer
 !define CertNameDownload   "Mozilla Corporation"
 !define CertIssuerDownload "Thawte Code Signing CA - G2"
 
 # Dialog units are used so the UI displays correctly with the system's DPI
 # settings.
--- a/browser/components/feeds/src/FeedWriter.js
+++ b/browser/components/feeds/src/FeedWriter.js
@@ -39,17 +39,17 @@ function makeURI(aURLSpec, aCharset) {
             getService(Ci.nsIIOService);
   try {
     return ios.newURI(aURLSpec, aCharset, null);
   } catch (ex) { }
 
   return null;
 }
 
-const XML_NS = "http://www.w3.org/XML/1998/namespace"
+const XML_NS = "http://www.w3.org/XML/1998/namespace";
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
 const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
 const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
 const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
 
 const PREF_SELECTED_APP = "browser.feeds.handlers.application";
@@ -164,21 +164,25 @@ FeedWriter.prototype = {
     }
     catch (e) {
     }
     return "";
   },
 
   _setContentText: function FW__setContentText(id, text) {
     this._contentSandbox.element = this._document.getElementById(id);
-    this._contentSandbox.textNode = this._document.createTextNode(text);
+    this._contentSandbox.textNode = text.createDocumentFragment(this._contentSandbox.element);
     var codeStr =
       "while (element.hasChildNodes()) " +
       "  element.removeChild(element.firstChild);" +
       "element.appendChild(textNode);";
+    if (text.base) {
+      this._contentSandbox.spec = text.base.spec;
+      codeStr += "element.setAttributeNS('" + XML_NS + "', 'base', spec);";
+    }
     Cu.evalInSandbox(codeStr, this._contentSandbox);
     this._contentSandbox.element = null;
     this._contentSandbox.textNode = null;
   },
 
   /**
    * Safely sets the href attribute on an anchor tag, providing the URI 
    * specified can be loaded according to rules. 
@@ -356,26 +360,26 @@ FeedWriter.prototype = {
   /**
    * Writes the feed title into the preview document.
    * @param   container
    *          The feed container
    */
   _setTitleText: function FW__setTitleText(container) {
     if (container.title) {
       var title = container.title.plainText();
-      this._setContentText(TITLE_ID, title);
+      this._setContentText(TITLE_ID, container.title);
       this._contentSandbox.document = this._document;
       this._contentSandbox.title = title;
       var codeStr = "document.title = title;"
       Cu.evalInSandbox(codeStr, this._contentSandbox);
     }
 
     var feed = container.QueryInterface(Ci.nsIFeed);
     if (feed && feed.subtitle)
-      this._setContentText(SUBTITLE_ID, container.subtitle.plainText());
+      this._setContentText(SUBTITLE_ID, container.subtitle);
   },
 
   /**
    * Writes the title image into the preview document if one is present.
    * @param   container
    *          The feed container
    */
   _setTitleImage: function FW__setTitleImage(container) {
@@ -434,17 +438,21 @@ FeedWriter.prototype = {
       entry.QueryInterface(Ci.nsIFeedContainer);
 
       var entryContainer = this._document.createElementNS(HTML_NS, "div");
       entryContainer.className = "entry";
 
       // If the entry has a title, make it a link
       if (entry.title) {
         var a = this._document.createElementNS(HTML_NS, "a");
-        a.appendChild(this._document.createTextNode(entry.title.plainText()));
+        var span = this._document.createElementNS(HTML_NS, "span");
+        a.appendChild(span);
+        if (entry.title.base)
+          span.setAttributeNS(XML_NS, "base", entry.title.base.spec);
+        span.appendChild(entry.title.createDocumentFragment(a));
 
         // Entries are not required to have links, so entry.link can be null.
         if (entry.link)
           this._safeSetURIAttribute(a, "href", entry.link.spec);
 
         var title = this._document.createElementNS(HTML_NS, "h3");
         title.appendChild(a);
 
--- a/browser/components/feeds/test/Makefile.in
+++ b/browser/components/feeds/test/Makefile.in
@@ -13,14 +13,16 @@ include $(DEPTH)/config/autoconf.mk
 XPCSHELL_TESTS	= unit
 
 MOCHITEST_FILES =	bug408328-data.xml \
 		bug368464-data.xml \
 		test_bug494328.html \
 		bug494328-data.xml \
 		test_bug589543.html \
 		bug589543-data.xml \
+		test_bug436801.html \
+		bug436801-data.xml \
 		test_registerHandler.html \
 		valid-feed.xml \
 		valid-unsniffable-feed.xml \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/bug436801-data.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:base="http://www.example.com/">
+
+  <title type="xhtml" xml:base="/foo/bar/">
+    <div xmlns="http://www.w3.org/1999/xhtml">Example of a <em>special</em> feed (<img height="20px" src="baz.png" alt="base test sprite"/>)</div>
+  </title>
+
+  <subtitle type="html" xml:base="/foo/bar/">
+    <![CDATA[
+      With a <em>special</em> subtitle (<img height="20px" src="baz.png" alt="base test sprite"/>)
+    ]]>
+  </subtitle>
+
+  <link href="http://example.org/"/>
+
+  <updated>2010-09-02T18:30:02Z</updated>
+
+  <author>
+    <name>John Doe</name>
+  </author>
+
+  <id>urn:uuid:22906062-ecbd-46e2-b6a7-3039506a398f</id>
+
+  <entry>
+    <title type="xhtml" xml:base="/foo/bar/">
+      <div xmlns="http://www.w3.org/1999/xhtml">Some <abbr title="Extensible Hyper-text Mark-up Language">XHTML</abbr> examples (<img height="20px" src="baz.png" alt="base test sprite"/>)</div>
+    </title>
+    <id>urn:uuid:b48083a7-71a7-4c9c-8515-b7c0d22955e7</id>
+    <updated>2010-09-02T18:30:02Z</updated>
+    <summary>Some text.</summary>
+  </entry>
+
+  <entry>
+    <title type="html" xml:base="/foo/bar/">
+      <![CDATA[
+        Some <abbr title="Hyper-text Mark-up Language">HTML</abbr> examples (<img height="20px" src="baz.png" alt="base test sprite"/>)
+      ]]>
+    </title>
+    <id>urn:uuid:1424967a-280a-414d-b0ab-8b11c4ac1bb7</id>
+    <updated>2010-09-02T18:30:02Z</updated>
+    <summary>Some text.</summary>
+  </entry>
+
+</feed>
new file mode 100644
--- /dev/null
+++ b/browser/components/feeds/test/test_bug436801.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=436801
+-->
+<head>
+  <title>Test feed preview subscribe UI</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436801">Mozilla Bug 436801</a>
+<p id="display"><iframe id="testFrame" src="bug436801-data.xml"></iframe></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function () {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var doc = $("testFrame").contentDocument;
+
+  checkNode(doc.getElementById("feedTitleText"), [
+    "ELEMENT", "h1", { "xml:base": "http://www.example.com/foo/bar/" }, [
+      ["TEXT", "Example of a "],
+      ["ELEMENT", "em", [
+        ["TEXT", "special"],
+      ]],
+      ["TEXT", " feed ("],
+      ["ELEMENT", "img", { "src": "baz.png" }],
+      ["TEXT", ")"],
+    ]
+  ]);
+
+  checkNode(doc.getElementById("feedSubtitleText"), [
+    "ELEMENT", "h2", { "xml:base": "http://www.example.com/foo/bar/" }, [
+      ["TEXT", "With a "],
+      ["ELEMENT", "em", [
+        ["TEXT", "special"],
+      ]],
+      ["TEXT", " subtitle ("],
+      ["ELEMENT", "img", { "src": "baz.png" }],
+      ["TEXT", ")"],
+    ]
+  ]);
+
+  checkNode(doc.querySelector(".entry").firstChild.firstChild.firstChild, [
+    "ELEMENT", "span", { "xml:base": "http://www.example.com/foo/bar/" }, [
+      ["TEXT", "Some "],
+      ["ELEMENT", "abbr", { title: "Extensible Hyper-text Mark-up Language" }, [
+        ["TEXT", "XHTML"],
+      ]],
+      ["TEXT", " examples ("],
+      ["ELEMENT", "img", { "src": "baz.png" }],
+      ["TEXT", ")"],
+    ]
+  ]);
+
+  checkNode(doc.querySelectorAll(".entry")[1].firstChild.firstChild.firstChild, [
+    "ELEMENT", "span", { "xml:base": "http://www.example.com/foo/bar/" }, [
+      ["TEXT", "Some "],
+      ["ELEMENT", "abbr", { title: "Hyper-text Mark-up Language" }, [
+        ["TEXT", "HTML"],
+      ]],
+      ["TEXT", " examples ("],
+      ["ELEMENT", "img", { "src": "baz.png" }],
+      ["TEXT", ")"],
+    ]
+  ]);
+});
+
+addLoadEvent(SimpleTest.finish);
+
+function checkNode(node, schema) {
+  var typeName = schema.shift() + "_NODE";
+  var type = Node[typeName];
+  is(node.nodeType, type, "Node should be expected type " + typeName);
+  if (type == Node.TEXT_NODE) {
+    var text = schema.shift();
+    is(node.data, text, "Text should match");
+    return;
+  }
+  // type == Node.ELEMENT_NODE
+  var tag = schema.shift();
+  is(node.localName, tag, "Element should have expected tag");
+  while (schema.length) {
+    var val = schema.shift();
+    if (Array.isArray(val))
+      var childSchema = val;
+    else
+      var attrSchema = val;
+  }
+  if (attrSchema) {
+    var nsTable = {
+      xml: "http://www.w3.org/XML/1998/namespace",
+    };
+    for (var name in attrSchema) {
+      var [ns, nsName] = name.split(":");
+      var val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
+                node.getAttribute(name);
+      is(val, attrSchema[name], "Attribute " + name + " should match");
+    }
+  }
+  if (childSchema) {
+    var numChildren = node.childNodes.length;
+    is(childSchema.length, numChildren,
+       "Element should have expected number of children");
+    for (var i = 0; i < numChildren; i++)
+      checkNode(node.childNodes[i], childSchema[i]);
+  }
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -740,16 +740,34 @@ CssLogic.isContentStylesheet = function 
   if (aSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) {
     return CssLogic.isContentStylesheet(aSheet.parentStyleSheet);
   }
 
   return false;
 };
 
 /**
+ * Get a source for a stylesheet, taking into account embedded stylesheets
+ * for which we need to use document.defaultView.location.href rather than
+ * sheet.href
+ *
+ * @param {CSSStyleSheet} aSheet the DOM object for the style sheet.
+ * @return {string} the address of the stylesheet.
+ */
+CssLogic.href = function CssLogic_href(aSheet)
+{
+  let href = aSheet.href;
+  if (!href) {
+    href = aSheet.ownerNode.ownerDocument.location;
+  }
+
+  return href;
+};
+
+/**
  * Return a shortened version of a style sheet's source.
  *
  * @param {CSSStyleSheet} aSheet the DOM object for the style sheet.
  */
 CssLogic.shortSource = function CssLogic_shortSource(aSheet)
 {
   // Use a string like "inline" if there is no source href
   if (!aSheet || !aSheet.href) {
@@ -922,31 +940,27 @@ CssSheet.prototype = {
   {
     if (this._mediaMatches === null) {
       this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet);
     }
     return this._mediaMatches;
   },
 
   /**
-   * Get a source for a stylesheet, taking into account embedded stylesheets
-   * for which we need to use document.defaultView.location.href rather than
-   * sheet.href
+   * Get a source for a stylesheet, using CssLogic.href
    *
    * @return {string} the address of the stylesheet.
    */
   get href()
   {
-    if (!this._href) {
-      this._href = this.domSheet.href;
-      if (!this._href) {
-        this._href = this.domSheet.ownerNode.ownerDocument.location;
-      }
+    if (this._href) {
+      return this._href;
     }
 
+    this._href = CssLogic.href(this.domSheet);
     return this._href;
   },
 
   /**
    * Create a shorthand version of the href of a stylesheet.
    *
    * @return {string} the shorthand source of the stylesheet.
    */
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -19,16 +19,22 @@ const XUL_NS = "http://www.mozilla.org/k
  */
 
 // Used to split on css line separators
 const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g;
 
 // Used to parse a single property line.
 const CSS_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/;
 
+// Used to parse an external resource from a property value
+const CSS_RESOURCE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
+
+const IOService = Components.classes["@mozilla.org/network/io-service;1"]
+                  .getService(Components.interfaces.nsIIOService);
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/CssLogic.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/InplaceEditor.jsm");
 
 this.EXPORTED_SYMBOLS = ["CssRuleView",
                          "_ElementStyle"];
 
@@ -1316,16 +1322,22 @@ RuleEditor.prototype = {
  * @constructor
  */
 function TextPropertyEditor(aRuleEditor, aProperty)
 {
   this.doc = aRuleEditor.doc;
   this.prop = aProperty;
   this.prop.editor = this;
 
+  let sheet = this.prop.rule.sheet;
+  let href = sheet ? CssLogic.href(sheet) : null;
+  if (href) {
+    this.sheetURI = IOService.newURI(href, null, null);
+  }
+
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onValueDone = this._onValueDone.bind(this);
 
   this._create();
   this.update();
@@ -1432,16 +1444,46 @@ TextPropertyEditor.prototype = {
       done: this._onValueDone,
       validate: this._validate.bind(this),
       warning: this.warning,
       advanceChars: ';'
     });
   },
 
   /**
+   * Resolve a URI based on the rule stylesheet
+   * @param {string} relativePath the path to resolve
+   * @return {string} the resolved path.
+   */
+  resolveURI: function(relativePath)
+  {
+    if (this.sheetURI) {
+      relativePath = this.sheetURI.resolve(relativePath);
+    }
+    return relativePath;
+  },
+
+  /**
+   * Check the property value to find an external resource (if any).
+   * @return {string} the URI in the property value, or null if there is no match.
+   */
+  getResourceURI: function()
+  {
+    let val = this.prop.value;
+    let uriMatch = CSS_RESOURCE_RE.exec(val);
+    let uri = null;
+
+    if (uriMatch && uriMatch[1]) {
+      uri = uriMatch[1];
+    }
+
+    return uri;
+  },
+
+  /**
    * Populate the span based on changes to the TextProperty.
    */
   update: function TextPropertyEditor_update()
   {
     if (this.prop.enabled) {
       this.enable.style.removeProperty("visibility");
       this.enable.setAttribute("checked", "");
     } else {
@@ -1459,17 +1501,42 @@ TextPropertyEditor.prototype = {
     this.nameSpan.textContent = name;
 
     // Combine the property's value and priority into one string for
     // the value.
     let val = this.prop.value;
     if (this.prop.priority) {
       val += " !" + this.prop.priority;
     }
-    this.valueSpan.textContent = val;
+
+    // Treat URLs differently than other properties.
+    // Allow the user to click a link to the resource and open it.
+    let resourceURI = this.getResourceURI();
+    if (resourceURI) {
+      this.valueSpan.textContent = "";
+
+      appendText(this.valueSpan, val.split(resourceURI)[0]);
+
+      let a = createChild(this.valueSpan, "a",  {
+        target: "_blank",
+        class: "theme-link",
+        textContent: resourceURI,
+        href: this.resolveURI(resourceURI)
+      });
+
+      a.addEventListener("click", function(aEvent) {
+        // Clicks within the link shouldn't trigger editing.
+        aEvent.stopPropagation();
+      }, false);
+
+      appendText(this.valueSpan, val.split(resourceURI)[1]);
+    } else {
+      this.valueSpan.textContent = val;
+    }
+
     this.warning.hidden = this._validate();
 
     let store = this.prop.rule.elementStyle.store;
     let propDirty = store.userProperties.contains(this.prop.rule.style, name);
     if (propDirty) {
       this.element.setAttribute("dirty", "");
     } else {
       this.element.removeAttribute("dirty");
--- a/browser/devtools/styleinspector/ruleview.css
+++ b/browser/devtools/styleinspector/ruleview.css
@@ -23,12 +23,16 @@
   cursor: text;
 }
 
 .ruleview-propertycontainer {
   cursor: text;
   padding-right: 15px;
 }
 
+.ruleview-propertycontainer a {
+  cursor: pointer;
+}
+
 .ruleview-computedlist:not(.styleinspector-open),
 .ruleview-warning[hidden] {
   display: none;
 }
--- a/browser/devtools/styleinspector/test/Makefile.in
+++ b/browser/devtools/styleinspector/test/Makefile.in
@@ -32,24 +32,29 @@ MOCHITEST_BROWSER_FILES = \
   browser_ruleview_update.js \
   browser_bug705707_is_content_stylesheet.js \
   browser_bug722196_property_view_media_queries.js \
   browser_bug722196_rule_view_media_queries.js \
   browser_bug_592743_specificity.js \
   browser_bug722691_rule_view_increment.js \
   browser_computedview_734259_style_editor_link.js \
   browser_computedview_copy.js\
+  browser_styleinspector_bug_677930_urls_clickable.js \
   head.js \
   $(NULL)
 
 MOCHITEST_BROWSER_FILES += \
   browser_bug683672.html \
   browser_bug705707_is_content_stylesheet.html \
   browser_bug705707_is_content_stylesheet_imported.css \
   browser_bug705707_is_content_stylesheet_imported2.css \
   browser_bug705707_is_content_stylesheet_linked.css \
   browser_bug705707_is_content_stylesheet_script.css \
   browser_bug705707_is_content_stylesheet.xul \
   browser_bug705707_is_content_stylesheet_xul.css \
   browser_bug722196_identify_media_queries.html \
+  browser_styleinspector_bug_677930_urls_clickable.html \
+  browser_styleinspector_bug_677930_urls_clickable \
+  browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css \
+  test-image.png \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.html
@@ -0,0 +1,21 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+
+    <link href="./browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css" rel="stylesheet" type="text/css">
+
+  </head>
+  <body>
+
+    <div class="relative">Background image with relative path (loaded from external css)</div>
+
+    <div class="absolute">Background image with absolute path (loaded from external css)</div>
+
+    <div class="base64">Background image with base64 url (loaded from external css)</div>
+
+    <div class="inline" style="background: url(test-image.png);">Background image with relative path (loaded from style attribute)</div>';
+
+    <div class="noimage">No background image :(</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable.js
@@ -0,0 +1,97 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests to make sure that URLs are clickable in the rule view
+
+let doc;
+let computedView;
+let inspector;
+
+const BASE_URL = "http://example.com/browser/browser/" +
+                 "devtools/styleinspector/test/";
+const TEST_URI = BASE_URL +
+                 "browser_styleinspector_bug_677930_urls_clickable.html";
+const TEST_IMAGE = BASE_URL + "test-image.png";
+const BASE_64_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+
+function createDocument()
+{
+  doc.title = "Style Inspector URL Clickable test";
+
+  openInspector(function(aInspector) {
+    inspector = aInspector;
+    executeSoon(selectNode);
+  });
+}
+
+
+function selectNode(aInspector)
+{
+  let sidebar = inspector.sidebar;
+  let iframe = sidebar._tabbox.querySelector(".iframe-ruleview");
+  let contentDoc = iframe.contentWindow.document;
+
+  let relative = doc.querySelector(".relative");
+  let absolute = doc.querySelector(".absolute");
+  let inline = doc.querySelector(".inline");
+  let base64 = doc.querySelector(".base64");
+  let noimage = doc.querySelector(".noimage");
+
+  ok(relative, "captain, we have the relative div");
+  ok(absolute, "captain, we have the absolute div");
+  ok(inline, "captain, we have the inline div");
+  ok(base64, "captain, we have the base64 div");
+  ok(noimage, "captain, we have the noimage div");
+
+  inspector.selection.setNode(relative);
+  is(inspector.selection.node, relative, "selection matches the relative element");
+  let relativeLink = contentDoc.querySelector(".ruleview-propertycontainer a");
+  ok (relativeLink, "Link exists for relative node");
+  ok (relativeLink.getAttribute("href"), TEST_IMAGE);
+
+  inspector.selection.setNode(absolute);
+  is(inspector.selection.node, absolute, "selection matches the absolute element");
+  let absoluteLink = contentDoc.querySelector(".ruleview-propertycontainer a");
+  ok (absoluteLink, "Link exists for absolute node");
+  ok (absoluteLink.getAttribute("href"), TEST_IMAGE);
+
+  inspector.selection.setNode(inline);
+  is(inspector.selection.node, inline, "selection matches the inline element");
+  let inlineLink = contentDoc.querySelector(".ruleview-propertycontainer a");
+  ok (inlineLink, "Link exists for inline node");
+  ok (inlineLink.getAttribute("href"), TEST_IMAGE);
+
+  inspector.selection.setNode(base64);
+  is(inspector.selection.node, base64, "selection matches the base64 element");
+  let base64Link = contentDoc.querySelector(".ruleview-propertycontainer a");
+  ok (base64Link, "Link exists for base64 node");
+  ok (base64Link.getAttribute("href"), BASE_64_URL);
+
+  inspector.selection.setNode(noimage);
+  is(inspector.selection.node, noimage, "selection matches the inline element");
+  let noimageLink = contentDoc.querySelector(".ruleview-propertycontainer a");
+  ok (!noimageLink, "There is no link for the node with no background image");
+
+  finishUp();
+}
+
+function finishUp()
+{
+  doc = computedView = inspector = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = TEST_URI;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_677930_urls_clickable/browser_styleinspector_bug_677930_urls_clickable.css
@@ -0,0 +1,9 @@
+.relative {
+    background-image: url(../test-image.png);
+}
+.absolute {
+    background: url("http://example.com/browser/browser/devtools/styleinspector/test/test-image.png");
+}
+.base64 {
+    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==');
+}
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..769c636340e11f9d2a0b7eb6a84d574dd9563f0c
GIT binary patch
literal 580
zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)=5M`RCwBA
z{Qv(y10?_;fS8au@87>?@9gaSt*os4-<6T^zln*-e<1(Y>eZ{a-@A8D7MlS80mJ}u
z0SKQtbH>fs*!aH-1H=C_K)ecw?*efe5W7I}s#U9Y!PLVrKmdV>nKNfT1{(H16si$K
zmjm&CV+al6D=8`c7X*o+82}JK4A3z64>JJB_`e(E3S)?2<zN>X{|^lf1sei(+1<Me
zFarPr2&}oIqvIC?)OL^o?-&p^!wh-{#k-;2f_VoZfS{%bLi}zFF<>UtAY{W<LBj?n
zz6$CsfB=HI;P*^44eyZn#$YcBg1rF~2L~`P&;bI75#$0;v@zVf$It;Z4d`qJS0Dzu
zh@l)BQx!mb7Roj*FK2kaXAgtR*|Q9LfP8=e0=od{pY0$UI-sVXf!f>w#jt?wfcn3@
zy!=0d5|Evi_8%aC7~Z{m#}1AKK|~<_M{=g1px}QcP=ErR57Iaj7$e5OumVLr$n^jL
z1djz!sJcLHd57c@W2lWFi_p^m2m=HVoB>kgf@C|$pqa*ykjAAMgaHBwo)>`0c=vmt
z+g3yQpuk*x7A(#H^u|wInF%0(FiZq_r2`t3pnwGh6fWCA7$ATcDb3CR0R{jJCzQv)
SYsoAC0000<MNUMnLSTYrIq9PS
--- a/browser/installer/windows/nsis/stub.nsi
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -216,17 +216,17 @@ Var ControlRightPX
 ; The OFFICIAL define is a workaround to support different urls for Release and
 ; Beta since they share the same branding when building with other branches that
 ; set the update channel to beta.
 !ifdef OFFICIAL
 !ifdef BETA_UPDATE_CHANNEL
 !undef URLStubDownload
 !define URLStubDownload "http://download.mozilla.org/?product=firefox-beta-latest&os=win&lang=${AB_CD}"
 !undef URLManualDownload
-!define URLManualDownload "https://www.mozilla.org/firefox/installer-help/?channel=beta"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=beta&installer_lang=${AB_CD}"
 !undef Channel
 !define Channel "beta"
 !endif
 !endif
 
 !include "common.nsh"
 
 !insertmacro ElevateUAC
--- a/browser/metro/base/content/history.js
+++ b/browser/metro/base/content/history.js
@@ -1,19 +1,19 @@
 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
 /* 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';
 
 
-// TODO <jwilde>: Observe changes in history with nsINavHistoryObserver
 function HistoryView(aSet) {
   this._set = aSet;
   this._set.controller = this;
+  this._inBatch = false;
 
   let history = Cc["@mozilla.org/browser/nav-history-service;1"].
                 getService(Ci.nsINavHistoryService);
   history.addObserver(this, false);
 }
 
 HistoryView.prototype = {
   _set:null,
@@ -37,52 +37,89 @@ HistoryView.prototype = {
     rootNode.containerOpen = true;
     let childCount = rootNode.childCount;
 
     for (let i = 0; i < childCount; i++) {
       let node = rootNode.getChild(i);
       let uri = node.uri;
       let title = node.title || uri;
 
-      let item = this._set.appendItem(title, uri);
-      item.setAttribute("iconURI", node.icon);
+      this.addItemToSet(uri, title, node.icon);
     }
 
     rootNode.containerOpen = false;
   },
 
   destruct: function destruct() {
   },
 
-  // nsINavHistoryObserver
+  // nsINavHistoryObserver & helpers
+
+  addItemToSet: function addItemToSet(uri, title, icon) {
+    let item = this._set.appendItem(title, uri, this._inBatch);
+    item.setAttribute("iconURI", icon);
+  },
+
+  // TODO rebase/merge Alert: bug 831916 's patch merges in,
+  // this can be replaced with the updated calls to populateGrid()
+  refreshAndRepopulate: function() {
+    this._set.clearAll();
+    this.populateGrid();
+  },
 
   onBeginUpdateBatch: function() {
+    // Avoid heavy grid redraws while a batch is in process
+    this._inBatch = true;
   },
 
   onEndUpdateBatch: function() {
+    this._inBatch = false;
+    this.refreshAndRepopulate();
   },
 
   onVisit: function(aURI, aVisitID, aTime, aSessionID,
                     aReferringID, aTransitionType) {
+    if (!this._inBatch) {
+      this.refreshAndRepopulate();
+    }
   },
 
   onTitleChanged: function(aURI, aPageTitle) {
+    let changedItems = this._set.getItemsByUrl(aURI.spec);
+    for (let item of changedItems) {
+      item.setAttribute("label", aPageTitle);
+    }
   },
 
   onDeleteURI: function(aURI) {
+    for (let item of this._set.getItemsByUrl(aURI.spec)) {
+      this._set.removeItem(item, this._inBatch);
+    }
   },
 
   onClearHistory: function() {
     this._set.clearAll();
   },
 
   onPageChanged: function(aURI, aWhat, aValue) {
+    if (aWhat ==  Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
+      let changedItems = this._set.getItemsByUrl(aURI.spec);
+      for (let item of changedItems) {
+        let currIcon = item.getAttribute("iconURI");
+        if (currIcon != aValue) {
+          item.setAttribute("iconURI", aValue);
+        }
+      }
+    }
   },
 
-  onPageExpired: function(aURI, aVisitTime, aWholeEntry) {
+  onDeleteVisits: function (aURI, aVisitTime, aGUID, aReason, aTransitionType) {
+    if ((aReason ==  Ci.nsINavHistoryObserver.REASON_DELETED) && !this._inBatch) {
+      this.refreshAndRepopulate();
+    }
   },
 
   QueryInterface: function(iid) {
     if (iid.equals(Components.interfaces.nsINavHistoryObserver) ||
         iid.equals(Components.interfaces.nsISupports)) {
       return this;
     }
     throw Cr.NS_ERROR_NO_INTERFACE;
@@ -120,9 +157,8 @@ let HistoryPanelView = {
     this._view = new HistoryView(this._grid);
     this._view.populateGrid();
   },
 
   uninit: function uninit() {
     this._view.destruct();
   }
 };
-
--- a/content/base/public/Element.h
+++ b/content/base/public/Element.h
@@ -12,39 +12,30 @@
 
 #ifndef mozilla_dom_Element_h__
 #define mozilla_dom_Element_h__
 
 #include "mozilla/dom/FragmentOrElement.h" // for base class
 #include "nsChangeHint.h"                  // for enum
 #include "nsEventStates.h"                 // for member
 #include "mozilla/dom/DirectionalityUtils.h"
-#include "nsCOMPtr.h"
-#include "nsAutoPtr.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMDocumentFragment.h"
 #include "nsILinkHandler.h"
 #include "nsNodeUtils.h"
 #include "nsAttrAndChildArray.h"
 #include "mozFlushType.h"
 #include "nsDOMAttributeMap.h"
-#include "nsIWeakReference.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsIDocument.h"
-#include "nsIDOMNodeSelector.h"
 #include "nsIDOMXPathNSResolver.h"
 #include "nsPresContext.h"
 #include "nsDOMClassInfoID.h" // DOMCI_DATA
-#include "nsIDOMTouchEvent.h"
 #include "nsIInlineEventHandlers.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/Attributes.h"
 #include "nsContentUtils.h"
-#include "nsINodeList.h"
-#include "mozilla/ErrorResult.h"
 #include "nsIScrollableFrame.h"
 #include "mozilla/dom/Attr.h"
 #include "nsISMILAttr.h"
 #include "nsClientRect.h"
 #include "nsEvent.h"
 #include "nsAttrValue.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsIHTMLCollection.h"
--- a/content/base/public/FragmentOrElement.h
+++ b/content/base/public/FragmentOrElement.h
@@ -8,17 +8,16 @@
  * provides an implementation of nsIDOMNode, implements nsIContent, provides
  * utility methods for subclasses, and so forth.
  */
 
 #ifndef FragmentOrElement_h___
 #define FragmentOrElement_h___
 
 #include "nsAttrAndChildArray.h"          // member
-#include "nsCOMPtr.h"                     // member
 #include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_*
 #include "nsIContent.h"                   // base class
 #include "nsIDOMTouchEvent.h"             // base class (nsITouchEventReceiver)
 #include "nsIDOMXPathNSResolver.h"        // base class
 #include "nsIInlineEventHandlers.h"       // base class
 #include "nsINodeList.h"                  // base class
 #include "nsIWeakReference.h"             // base class
 #include "nsNodeUtils.h"                  // class member nsNodeUtils::CloneNodeImpl
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -1,19 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsIContent_h___
 #define nsIContent_h___
 
 #include "nsCaseTreatment.h" // for enum, cannot be forward-declared
-#include "nsCOMPtr.h"        // for already_AddRefed in constructor
-#include "nsIDocument.h"     // for use in inline function (IsInHTMLDocument)
-#include "nsINode.h"         // for base class
+#include "nsIDocument.h"
 
 // Forward declarations
 class nsAString;
 class nsIAtom;
 class nsIURI;
 class nsRuleWalker;
 class nsAttrValue;
 class nsAttrName;
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -1,21 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsINode_h___
 #define nsINode_h___
 
-#include "mozilla/ErrorResult.h"
 #include "mozilla/Likely.h"
 #include "nsCOMPtr.h"               // for member, local
 #include "nsGkAtoms.h"              // for nsGkAtoms::baseURIProperty
-#include "nsIDOMEventTarget.h"      // for base class
 #include "nsIDOMNode.h"
 #include "nsIDOMNodeSelector.h"     // base class
 #include "nsINodeInfo.h"            // member (in nsCOMPtr)
 #include "nsIVariant.h"             // for use in GetUserData()
 #include "nsNodeInfoManager.h"      // for use in NodePrincipal()
 #include "nsPropertyTable.h"        // for typedefs
 #include "nsTObserverArray.h"       // for member
 #include "nsWindowMemoryReporter.h" // for NS_DECL_SIZEOF_EXCLUDING_THIS
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1861,24 +1861,31 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   if (tmp->mCSSLoader) {
     tmp->mCSSLoader->UnlinkCachedSheets();
   }
 
   tmp->mInUnlinkOrDeletion = false;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
+static bool sPrefsInitialized = false;
+static int32_t sOnloadDecodeLimit = 0;
 
 nsresult
 nsDocument::Init()
 {
   if (mCSSLoader || mStyleImageLoader || mNodeInfoManager || mScriptLoader) {
     return NS_ERROR_ALREADY_INITIALIZED;
   }
 
+  if (!sPrefsInitialized) {
+    sPrefsInitialized = true;
+    Preferences::AddIntVarCache(&sOnloadDecodeLimit, "image.onload.decode.limit", 0);
+  }
+
   mIdentifierMap.Init();
   mStyledLinks.Init();
   mRadioGroups.Init();
   mCustomPrototypes.Init();
 
   // If after creation the owner js global is not set for a document
   // we use the default compartment for this document, instead of creating
   // wrapper in some random compartment when the document is exposed to js
@@ -8914,19 +8921,21 @@ nsDocument::AddImage(imgIRequest* aImage
 
   // Put the image in the hashtable, with the proper count.
   mImageTracker.Put(aImage, oldCount + 1);
 
   nsresult rv = NS_OK;
 
   // If this is the first insertion and we're locking images, lock this image
   // too.
-  if (oldCount == 0 && mLockingImages) {
-    rv = aImage->LockImage();
-    if (NS_SUCCEEDED(rv))
+  if (oldCount == 0) {
+    if (mLockingImages)
+      rv = aImage->LockImage();
+    if (NS_SUCCEEDED(rv) && (!sOnloadDecodeLimit ||
+                             mImageTracker.Count() < sOnloadDecodeLimit))
       rv = aImage->StartDecoding();
   }
 
   // If this is the first insertion and we're animating images, request
   // that this image be animated too.
   if (oldCount == 0 && mAnimatingImages) {
     nsresult rv2 = aImage->IncrementAnimationConsumers();
     rv = NS_SUCCEEDED(rv) ? rv2 : rv;
--- a/content/canvas/src/Makefile.in
+++ b/content/canvas/src/Makefile.in
@@ -7,19 +7,17 @@ DEPTH		= @DEPTH@
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 LIBRARY_NAME	= gkconcvs_s
 LIBXUL_LIBRARY  = 1
-ifndef _MSC_VER
 FAIL_ON_WARNINGS = 1
-endif # !_MSC_VER
 
 CPPSRCS	= \
 	CanvasImageCache.cpp \
 	CanvasRenderingContext2D.cpp \
 	CanvasUtils.cpp \
 	DocumentRendererParent.cpp \
 	DocumentRendererChild.cpp \
 	ImageData.cpp \
--- a/content/canvas/src/WebGLTexelConversions.cpp
+++ b/content/canvas/src/WebGLTexelConversions.cpp
@@ -354,17 +354,17 @@ WebGLContext::ConvertImage(size_t width,
         }
         return;
     }
 
     uint8_t* dstStart = dst;
     ptrdiff_t signedDstStride = dstStride;
     if (mPixelStoreFlipY) {
         dstStart = dst + (height - 1) * dstStride;
-        signedDstStride = -dstStride;
+        signedDstStride = -signedDstStride;
     }
 
     WebGLImageConverter converter(width, height, src, dstStart, srcStride, signedDstStride);
 
     const WebGLTexelPremultiplicationOp premultiplicationOp
         = FormatsRequireNoPremultiplicationOp     ? NoPremultiplicationOp
         : (!srcPremultiplied && dstPremultiplied) ? Premultiply
         : (srcPremultiplied && !dstPremultiplied) ? Unpremultiply
--- a/content/events/public/EventTarget.h
+++ b/content/events/public/EventTarget.h
@@ -6,17 +6,16 @@
 #ifndef mozilla_dom_EventTarget_h_
 #define mozilla_dom_EventTarget_h_
 
 #include "nsIDOMEventTarget.h"
 #include "nsWrapperCache.h"
 #include "nsIDOMEventListener.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Nullable.h"
-#include "nsIDOMEvent.h"
 class nsDOMEvent;
 
 namespace mozilla {
 namespace dom {
 
 class EventListener;
 
 // IID for the dom::EventTarget interface
--- a/content/html/content/src/HTMLPropertiesCollection.cpp
+++ b/content/html/content/src/HTMLPropertiesCollection.cpp
@@ -440,17 +440,17 @@ NS_INTERFACE_TABLE_HEAD(PropertyNodeList
 NS_INTERFACE_MAP_END
 
 void
 PropertyNodeList::GetValues(JSContext* aCx, nsTArray<JS::Value >& aResult,
                             ErrorResult& aError)
 {
   EnsureFresh();
 
-  JSObject* wrapper = GetWrapper();
+  JS::RootedObject wrapper(aCx, GetWrapper());
   JSAutoCompartment ac(aCx, wrapper);
   uint32_t length = mElements.Length();
   for (uint32_t i = 0; i < length; ++i) {
     JS::Value v = mElements.ElementAt(i)->GetItemValue(aCx, wrapper, aError);
     if (aError.Failed()) {
       return;
     }
     aResult.AppendElement(v);
--- a/content/media/webaudio/ScriptProcessorNode.cpp
+++ b/content/media/webaudio/ScriptProcessorNode.cpp
@@ -229,17 +229,124 @@ private:
   {
     for (unsigned i = 0; i < mInputChannels.Length(); ++i) {
       if (!mInputChannels[i]) {
         mInputChannels[i] = new float[mBufferSize];
       }
     }
   }
 
-  void SendBuffersToMainThread(AudioNodeStream* aStream);
+  void SendBuffersToMainThread(AudioNodeStream* aStream)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    // we now have a full input buffer ready to be sent to the main thread.
+    TrackTicks playbackTick = mSource->GetCurrentPosition();
+    // Add the duration of the current sample
+    playbackTick += WEBAUDIO_BLOCK_SIZE;
+    // Add the delay caused by the main thread
+    playbackTick += mSharedBuffers->DelaySoFar();
+    // Compute the playback time in the coordinate system of the destination
+    double playbackTime =
+      WebAudioUtils::StreamPositionToDestinationTime(playbackTick,
+                                                     mSource,
+                                                     mDestination);
+
+    class Command : public nsRunnable
+    {
+    public:
+      Command(AudioNodeStream* aStream,
+              InputChannels& aInputChannels,
+              double aPlaybackTime,
+              bool aNullInput)
+        : mStream(aStream)
+        , mPlaybackTime(aPlaybackTime)
+        , mNullInput(aNullInput)
+      {
+        mInputChannels.SetLength(aInputChannels.Length());
+        if (!aNullInput) {
+          for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
+            mInputChannels[i] = aInputChannels[i].forget();
+          }
+        }
+      }
+
+      NS_IMETHODIMP Run()
+      {
+        // If it's not safe to run scripts right now, schedule this to run later
+        if (!nsContentUtils::IsSafeToRunScript()) {
+          nsContentUtils::AddScriptRunner(this);
+          return NS_OK;
+        }
+
+        nsRefPtr<ScriptProcessorNode> node;
+        {
+          // No need to keep holding the lock for the whole duration of this
+          // function, since we're holding a strong reference to it, so if
+          // we can obtain the reference, we will hold the node alive in
+          // this function.
+          MutexAutoLock lock(mStream->Engine()->NodeMutex());
+          node = static_cast<ScriptProcessorNode*>(mStream->Engine()->Node());
+        }
+        if (!node) {
+          return NS_OK;
+        }
+
+        AutoPushJSContext cx(node->Context()->GetJSContext());
+        if (cx) {
+          JSAutoRequest ar(cx);
+
+          // Create the input buffer
+          nsRefPtr<AudioBuffer> inputBuffer;
+          if (!mNullInput) {
+            inputBuffer = new AudioBuffer(node->Context(),
+                                          node->BufferSize(),
+                                          node->Context()->SampleRate());
+            if (!inputBuffer->InitializeBuffers(mInputChannels.Length(), cx)) {
+              return NS_OK;
+            }
+            // Put the channel data inside it
+            for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
+              inputBuffer->SetRawChannelContents(cx, i, mInputChannels[i]);
+            }
+          }
+
+          // Ask content to produce data in the output buffer
+          // Note that we always avoid creating the output buffer here, and we try to
+          // avoid creating the input buffer as well.  The AudioProcessingEvent class
+          // knows how to lazily create them if needed once the script tries to access
+          // them.  Otherwise, we may be able to get away without creating them!
+          nsRefPtr<AudioProcessingEvent> event = new AudioProcessingEvent(node, nullptr, nullptr);
+          event->InitEvent(inputBuffer,
+                           mInputChannels.Length(),
+                           mPlaybackTime);
+          node->DispatchTrustedEvent(event);
+
+          // Steal the output buffers
+          nsRefPtr<ThreadSharedFloatArrayBufferList> output;
+          if (event->HasOutputBuffer()) {
+            output = event->OutputBuffer()->GetThreadSharedChannelsForRate(cx);
+          }
+
+          // Append it to our output buffer queue
+          node->GetSharedBuffers()->FinishProducingOutputBuffer(output, node->BufferSize());
+        }
+        return NS_OK;
+      }
+    private:
+      nsRefPtr<AudioNodeStream> mStream;
+      InputChannels mInputChannels;
+      double mPlaybackTime;
+      bool mNullInput;
+    };
+
+    NS_DispatchToMainThread(new Command(aStream, mInputChannels,
+                                        playbackTime,
+                                        !mSeenNonSilenceInput));
+  }
 
   friend class ScriptProcessorNode;
 
   SharedBuffers* mSharedBuffers;
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   InputChannels mInputChannels;
   const uint32_t mBufferSize;
@@ -278,121 +385,11 @@ ScriptProcessorNode::~ScriptProcessorNod
 }
 
 JSObject*
 ScriptProcessorNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return ScriptProcessorNodeBinding::Wrap(aCx, aScope, this);
 }
 
-class DispatchAudioProcessEventCommand : public nsRunnable
-{
-public:
-  DispatchAudioProcessEventCommand(AudioNodeStream* aStream,
-                                   ScriptProcessorNodeEngine::InputChannels& aInputChannels,
-                                   double aPlaybackTime,
-                                   bool aNullInput)
-    : mStream(aStream)
-    , mPlaybackTime(aPlaybackTime)
-    , mNullInput(aNullInput)
-  {
-    mInputChannels.SetLength(aInputChannels.Length());
-    if (!aNullInput) {
-      for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
-        mInputChannels[i] = aInputChannels[i].forget();
-      }
-    }
-  }
-
-  NS_IMETHODIMP Run()
-  {
-    // If it's not safe to run scripts right now, schedule this to run later
-    if (!nsContentUtils::IsSafeToRunScript()) {
-      nsContentUtils::AddScriptRunner(this);
-      return NS_OK;
-    }
-
-    nsRefPtr<ScriptProcessorNode> node;
-    {
-      // No need to keep holding the lock for the whole duration of this
-      // function, since we're holding a strong reference to it, so if
-      // we can obtain the reference, we will hold the node alive in
-      // this function.
-      MutexAutoLock lock(mStream->Engine()->NodeMutex());
-      node = static_cast<ScriptProcessorNode*>(mStream->Engine()->Node());
-    }
-    if (!node) {
-      return NS_OK;
-    }
-
-    AutoPushJSContext cx(node->Context()->GetJSContext());
-    if (cx) {
-      JSAutoRequest ar(cx);
-
-      // Create the input buffer
-      nsRefPtr<AudioBuffer> inputBuffer;
-      if (!mNullInput) {
-        inputBuffer = new AudioBuffer(node->Context(),
-                                      node->BufferSize(),
-                                      node->Context()->SampleRate());
-        if (!inputBuffer->InitializeBuffers(mInputChannels.Length(), cx)) {
-          return NS_OK;
-        }
-        // Put the channel data inside it
-        for (uint32_t i = 0; i < mInputChannels.Length(); ++i) {
-          inputBuffer->SetRawChannelContents(cx, i, mInputChannels[i]);
-        }
-      }
-
-      // Ask content to produce data in the output buffer
-      // Note that we always avoid creating the output buffer here, and we try to
-      // avoid creating the input buffer as well.  The AudioProcessingEvent class
-      // knows how to lazily create them if needed once the script tries to access
-      // them.  Otherwise, we may be able to get away without creating them!
-      nsRefPtr<AudioProcessingEvent> event = new AudioProcessingEvent(node, nullptr, nullptr);
-      event->InitEvent(inputBuffer,
-                       mInputChannels.Length(),
-                       mPlaybackTime);
-      node->DispatchTrustedEvent(event);
-
-      // Steal the output buffers
-      nsRefPtr<ThreadSharedFloatArrayBufferList> output;
-      if (event->HasOutputBuffer()) {
-        output = event->OutputBuffer()->GetThreadSharedChannelsForRate(cx);
-      }
-
-      // Append it to our output buffer queue
-      node->GetSharedBuffers()->FinishProducingOutputBuffer(output, node->BufferSize());
-    }
-    return NS_OK;
-  }
-private:
-  nsRefPtr<AudioNodeStream> mStream;
-  ScriptProcessorNodeEngine::InputChannels mInputChannels;
-  double mPlaybackTime;
-  bool mNullInput;
-};
-
-void
-ScriptProcessorNodeEngine::SendBuffersToMainThread(AudioNodeStream* aStream)
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-
-  // we now have a full input buffer ready to be sent to the main thread.
-  TrackTicks playbackTick = mSource->GetCurrentPosition();
-  // Add the duration of the current sample
-  playbackTick += WEBAUDIO_BLOCK_SIZE;
-  // Add the delay caused by the main thread
-  playbackTick += mSharedBuffers->DelaySoFar();
-  // Compute the playback time in the coordinate system of the destination
-  double playbackTime =
-    WebAudioUtils::StreamPositionToDestinationTime(playbackTick,
-                                                   mSource,
-                                                   mDestination);
-
-  NS_DispatchToMainThread(new DispatchAudioProcessEventCommand(aStream, mInputChannels,
-                                                               playbackTime,
-                                                               !mSeenNonSilenceInput));
-}
-
 }
 }
 
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -43,17 +43,19 @@ MOCHITEST_FILES = \
 		test_getSubStringLength.xhtml \
 		getSubStringLength-helper.svg \
 		test_hasFeature.xhtml \
 		$(filter disabled-for-intermittent-failures--bug-701060, test_lang.xhtml) \
 		test_nonAnimStrings.xhtml \
 		test_pathAnimInterpolation.xhtml \
 		test_pathSeg.xhtml \
 		test_pointAtLength.xhtml \
-		test_pointer-events.xhtml \
+		test_pointer-events-1a.xhtml \
+		test_pointer-events-1b.xhtml \
+		pointer-events.js \
 		test_pointer-events-2.xhtml \
 		test_pointer-events-3.xhtml \
 		test_pointer-events-4.xhtml \
 		test_scientific.html \
 		scientific-helper.svg \
 		test_selectSubString.xhtml \
 		test_selectSubString2.xhtml \
 		selectSubString-helper.svg \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/pointer-events.js
@@ -0,0 +1,328 @@
+SimpleTest.waitForExplicitFinish();
+
+var pointer_events_values = [
+  'auto',
+  'visiblePainted',
+  'visibleFill',
+  'visibleStroke',
+  'visible',
+  'painted',
+  'fill',
+  'stroke',
+  'all',
+  'none'
+];
+
+var paint_values = [
+  'blue',
+  'transparent',
+  'none'
+];
+
+var opacity_values = [
+  '1',
+  '0.5',
+  '0'
+];
+
+var visibility_values = [
+  'visible',
+  'hidden',
+  'collapse'
+];
+
+/**
+ * List of attributes and various values for which we want to test permutations
+ * when hit testing a pointer event that is over an element's fill area,
+ * stroke area, or both (where they overlap).
+ *
+ * We're using an array of objects so that we have control over the order in
+ * which permutations are tested.
+ *
+ * TODO: test the effect of clipping, masking, filters, markers, etc.
+ */
+var hit_test_inputs = {
+  fill: [
+    { name: 'pointer-events',  values: pointer_events_values },
+    { name: 'fill',            values: paint_values },
+    { name: 'fill-opacity',    values: opacity_values },
+    { name: 'opacity',         values: opacity_values },
+    { name: 'visibility',      values: visibility_values }
+  ],
+  stroke: [
+    { name: 'pointer-events',  values: pointer_events_values },
+    { name: 'stroke',          values: paint_values },
+    { name: 'stroke-opacity',  values: opacity_values },
+    { name: 'opacity',         values: opacity_values },
+    { name: 'visibility',      values: visibility_values }
+  ],
+  both: [
+    { name: 'pointer-events',  values: pointer_events_values },
+    { name: 'fill',            values: paint_values },
+    { name: 'fill-opacity',    values: opacity_values },
+    { name: 'stroke',          values: paint_values },
+    { name: 'stroke-opacity',  values: opacity_values },
+    { name: 'opacity',         values: opacity_values },
+    { name: 'visibility',      values: visibility_values }
+  ]
+}
+
+/**
+ * The following object contains a list of 'pointer-events' property values,
+ * each with an object detailing the conditions under which the fill and stroke
+ * of a graphical object will intercept pointer events for the given value. If
+ * the object contains a 'fill-intercepts-iff' property then the fill is
+ * expected to intercept pointer events for that value of 'pointer-events' if
+ * and only if the conditions listed in the 'fill-intercepts-iff' object are
+ * met. If there are no conditions in the 'fill-intercepts-iff' object then the
+ * fill should always intercept pointer events. However, if the
+ * 'fill-intercepts-iff' property is not set at all then it indicates that the
+ * fill should never intercept pointer events. The same rules apply for
+ * 'stroke-intercepts-iff'.
+ *
+ * If an attribute name in the conditions list is followed by the "!"
+ * character then the requirement for a hit is that its value is NOT any
+ * of the values listed in the given array.
+ */
+var hit_conditions = {
+  auto: {
+    'fill-intercepts-iff': {
+      'visibility': ['visible'],
+      'fill!': ['none']
+    },
+    'stroke-intercepts-iff': {
+      'visibility': ['visible'],
+      'stroke!': ['none']
+    }
+  },
+  visiblePainted: {
+    'fill-intercepts-iff': {
+      'visibility': ['visible'],
+      'fill!': ['none']
+    },
+    'stroke-intercepts-iff': {
+      'visibility': ['visible'],
+      'stroke!': ['none']
+    }
+  },
+  visibleFill: {
+    'fill-intercepts-iff': {
+      visibility: ['visible']
+    }
+    // stroke never intercepts pointer events
+  },
+  visibleStroke: {
+    // fill never intercepts pointer events
+    'stroke-intercepts-iff': {
+      visibility: ['visible']
+    }
+  },
+  visible: {
+    'fill-intercepts-iff': {
+      visibility: ['visible']
+    },
+    'stroke-intercepts-iff': {
+      visibility: ['visible']
+    }
+  },
+  painted: {
+    'fill-intercepts-iff': {
+      'fill!': ['none']
+    },
+    'stroke-intercepts-iff': {
+      'stroke!': ['none']
+    }
+  },
+  fill: {
+    'fill-intercepts-iff': {
+      // fill always intercepts pointer events
+    }
+    // stroke never intercepts pointer events
+  },
+  stroke: {
+    // fill never intercepts pointer events
+    'stroke-intercepts-iff': {
+      // stroke always intercepts pointer events
+    }
+  },
+  all: {
+    'fill-intercepts-iff': {
+      // fill always intercepts pointer events
+    },
+    'stroke-intercepts-iff': {
+      // stroke always intercepts pointer events
+    }
+  },
+  none: {
+    // neither fill nor stroke intercept pointer events
+  }
+}
+
+// bit flags
+var POINT_OVER_FILL   = 0x1;
+var POINT_OVER_STROKE = 0x2;
+
+/**
+ * Examine the element's attribute values and, based on the area(s) of the
+ * element that the pointer event is over (fill and/or stroke areas), return
+ * true if the element is expected to intercept the event, otherwise false.
+ */
+function hit_expected(element, over /* bit flags indicating which area(s) of the element the pointer is over */)
+{
+  function expect_hit(target){
+    var intercepts_iff =
+      hit_conditions[element.getAttribute('pointer-events')][target + '-intercepts-iff'];
+
+    if (!intercepts_iff) {
+      return false; // never intercepts events
+    }
+
+    for (var attr in intercepts_iff) {
+      var vals = intercepts_iff[attr];  // must get this before we adjust 'attr'
+      var invert = false;
+      if (attr.substr(-1) == '!') {
+        invert = true;
+        attr = attr.substr(0, attr.length-1);
+      }
+      var match = vals.indexOf(element.getAttribute(attr)) > -1;
+      if (invert) {
+        match = !match;
+      }
+      if (!match) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  return (over & POINT_OVER_FILL) != 0   && expect_hit('fill') ||
+         (over & POINT_OVER_STROKE) != 0 && expect_hit('stroke');
+}
+
+function for_all_permutations(inputs, callback)
+{
+  var current_permutation = arguments[2] || {};
+  var index = arguments[3] || 0;
+
+  if (index < inputs.length) {
+    var name = inputs[index].name;
+    var values = inputs[index].values;
+    for (var i = 0; i < values.length; ++i) {
+      current_permutation[name] = values[i];
+      for_all_permutations(inputs, callback, current_permutation, index+1);
+    }
+    return;
+  }
+
+  callback(current_permutation);
+}
+
+function make_log_msg(over, tag, attributes)
+{
+  var target;
+  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
+    target = 'fill and stroke';
+  } else if (over == POINT_OVER_FILL) {
+    target = 'fill';
+  } else if (over == POINT_OVER_STROKE) {
+    target = 'stroke';
+  } else {
+    throw "unexpected bit combination in 'over'";
+  }
+  var msg = 'Check if events are intercepted at a point over the '+target+' on <'+tag+'> for';
+  for (var attr in attributes) {
+    msg += ' '+attr+'='+attributes[attr];
+  }
+  return msg;
+}
+
+var dx, dy; // offset of <svg> element from pointer coordinates origin
+
+function test_element(id, x, y, over /* bit flags indicating which area(s) of the element the pointer is over */)
+{
+  var element = document.getElementById(id);
+  var tag = element.tagName;
+
+  function test_permutation(attributes) {
+    for (var attr in attributes) {
+      element.setAttribute(attr, attributes[attr]);
+    }
+    var hits = document.elementFromPoint(dx + x, dy + y) == element;
+    var msg = make_log_msg(over, tag, attributes);
+
+    is(hits, hit_expected(element, over), msg);
+  }
+
+  var inputs;
+  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
+    inputs = hit_test_inputs['both'];
+  } else if (over == POINT_OVER_FILL) {
+    inputs = hit_test_inputs['fill'];
+  } else if (over == POINT_OVER_STROKE) {
+    inputs = hit_test_inputs['stroke'];
+  } else {
+    throw "unexpected bit combination in 'over'";
+  }
+
+  for_all_permutations(inputs, test_permutation);
+
+  // To reduce the chance of bogus results in subsequent tests:
+  element.setAttribute('fill', 'none');
+  element.setAttribute('stroke', 'none');
+}
+
+function run_tests(subtest)
+{
+  var div = document.getElementById("div");
+  dx = div.offsetLeft;
+  dy = div.offsetTop;
+
+  // Run the test with only a subset of pointer-events values, to avoid
+  // running over the mochitest time limit.  The subtest argument indicates
+  // whether to use the first half of the pointer-events values (0)
+  // or the second half (1).
+  var partition = Math.floor(pointer_events_values.length / 2);
+  switch (subtest) {
+    case 0:
+      pointer_events_values.splice(partition);
+      break;
+    case 1:
+      pointer_events_values.splice(0, partition);
+      break;
+    case 2:
+      throw "unexpected subtest number";
+  }
+
+  test_element('rect', 30, 30, POINT_OVER_FILL);
+  test_element('rect', 5, 5, POINT_OVER_STROKE);
+
+  // The SVG 1.1 spec essentially says that, for text, hit testing is done
+  // against the character cells of the text, and not the fill and stroke as
+  // you might expect for a normal graphics element like <path>. See the
+  // paragraph starting "For text elements..." in this section:
+  //
+  //   http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
+  //
+  // This requirement essentially means that for the purposes of hit testing
+  // the fill and stroke areas are the same area - the character cell. (At
+  // least until we support having any fill or stroke that lies outside the
+  // character cells intercept events like Opera does - see below.) Thus, for
+  // text, when a pointer event is over a character cell it is essentially over
+  // both the fill and stroke at the same time. That's the reason we pass both
+  // the POINT_OVER_FILL and POINT_OVER_STROKE bits in test_element's 'over'
+  // argument below. It's also the reason why we only test one point in the
+  // text rather than having separate tests for fill and stroke.
+  //
+  // For hit testing of text, Opera essentially treats fill and stroke like it
+  // would on any normal element, but it adds the character cells of glyhs to
+  // both the glyphs' fill AND stroke. I think this is what we should do too.
+  // It's compatible with the letter of the SVG 1.1 rules, and it allows any
+  // parts of a glyph that are outside the glyph's character cells to also
+  // intercept events in the normal way. When we make that change we'll be able
+  // to add separate fill and stroke tests for text below.
+
+  test_element('text', 210, 30, POINT_OVER_FILL | POINT_OVER_STROKE);
+
+  SimpleTest.finish();
+}
rename from content/svg/content/test/test_pointer-events.xhtml
rename to content/svg/content/test/test_pointer-events-1a.xhtml
--- a/content/svg/content/test/test_pointer-events.xhtml
+++ b/content/svg/content/test/test_pointer-events-1a.xhtml
@@ -2,335 +2,18 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=619959
 -->
 <head>
   <title>Test 'pointer-events' handling</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="run_tests()">
-<script class="testbody" type="text/javascript">
-<![CDATA[
-
-SimpleTest.waitForExplicitFinish();
-
-var pointer_events_values = [
-  'auto',
-  'visiblePainted',
-  'visibleFill',
-  'visibleStroke',
-  'visible',
-  'painted',
-  'fill',
-  'stroke',
-  'all',
-  'none'
-];
-
-var paint_values = [
-  'blue',
-  'transparent',
-  'none'
-];
-
-var opacity_values = [
-  '1',
-  '0.5',
-  '0'
-];
-
-var visibility_values = [
-  'visible',
-  'hidden',
-  'collapse'
-];
-
-/**
- * List of attributes and various values for which we want to test permutations
- * when hit testing a pointer event that is over an element's fill area,
- * stroke area, or both (where they overlap).
- *
- * We're using an array of objects so that we have control over the order in
- * which permutations are tested.
- *
- * TODO: test the effect of clipping, masking, filters, markers, etc.
- */
-var hit_test_inputs = {
-  fill: [
-    { name: 'pointer-events',  values: pointer_events_values },
-    { name: 'fill',            values: paint_values },
-    { name: 'fill-opacity',    values: opacity_values },
-    { name: 'opacity',         values: opacity_values },
-    { name: 'visibility',      values: visibility_values }
-  ],
-  stroke: [
-    { name: 'pointer-events',  values: pointer_events_values },
-    { name: 'stroke',          values: paint_values },
-    { name: 'stroke-opacity',  values: opacity_values },
-    { name: 'opacity',         values: opacity_values },
-    { name: 'visibility',      values: visibility_values }
-  ],
-  both: [
-    { name: 'pointer-events',  values: pointer_events_values },
-    { name: 'fill',            values: paint_values },
-    { name: 'fill-opacity',    values: opacity_values },
-    { name: 'stroke',          values: paint_values },
-    { name: 'stroke-opacity',  values: opacity_values },
-    { name: 'opacity',         values: opacity_values },
-    { name: 'visibility',      values: visibility_values }
-  ]
-}
-
-/**
- * The following object contains a list of 'pointer-events' property values,
- * each with an object detailing the conditions under which the fill and stroke
- * of a graphical object will intercept pointer events for the given value. If
- * the object contains a 'fill-intercepts-iff' property then the fill is
- * expected to intercept pointer events for that value of 'pointer-events' if
- * and only if the conditions listed in the 'fill-intercepts-iff' object are
- * met. If there are no conditions in the 'fill-intercepts-iff' object then the
- * fill should always intercept pointer events. However, if the
- * 'fill-intercepts-iff' property is not set at all then it indicates that the
- * fill should never intercept pointer events. The same rules apply for
- * 'stroke-intercepts-iff'.
- *
- * If an attribute name in the conditions list is followed by the "!"
- * character then the requirement for a hit is that its value is NOT any
- * of the values listed in the given array.
- */
-var hit_conditions = {
-  auto: {
-    'fill-intercepts-iff': {
-      'visibility': ['visible'],
-      'fill!': ['none']
-    },
-    'stroke-intercepts-iff': {
-      'visibility': ['visible'],
-      'stroke!': ['none']
-    }
-  },
-  visiblePainted: {
-    'fill-intercepts-iff': {
-      'visibility': ['visible'],
-      'fill!': ['none']
-    },
-    'stroke-intercepts-iff': {
-      'visibility': ['visible'],
-      'stroke!': ['none']
-    }
-  },
-  visibleFill: {
-    'fill-intercepts-iff': {
-      visibility: ['visible']
-    }
-    // stroke never intercepts pointer events
-  },
-  visibleStroke: {
-    // fill never intercepts pointer events
-    'stroke-intercepts-iff': {
-      visibility: ['visible']
-    }
-  },
-  visible: {
-    'fill-intercepts-iff': {
-      visibility: ['visible']
-    },
-    'stroke-intercepts-iff': {
-      visibility: ['visible']
-    }
-  },
-  painted: {
-    'fill-intercepts-iff': {
-      'fill!': ['none']
-    },
-    'stroke-intercepts-iff': {
-      'stroke!': ['none']
-    }
-  },
-  fill: {
-    'fill-intercepts-iff': {
-      // fill always intercepts pointer events
-    }
-    // stroke never intercepts pointer events
-  },
-  stroke: {
-    // fill never intercepts pointer events
-    'stroke-intercepts-iff': {
-      // stroke always intercepts pointer events
-    }
-  },
-  all: {
-    'fill-intercepts-iff': {
-      // fill always intercepts pointer events
-    },
-    'stroke-intercepts-iff': {
-      // stroke always intercepts pointer events
-    }
-  },
-  none: {
-    // neither fill nor stroke intercept pointer events
-  }
-}
-
-// bit flags
-var POINT_OVER_FILL   = 0x1;
-var POINT_OVER_STROKE = 0x2;
-
-/**
- * Examine the element's attribute values and, based on the area(s) of the
- * element that the pointer event is over (fill and/or stroke areas), return
- * true if the element is expected to intercept the event, otherwise false.
- */
-function hit_expected(element, over /* bit flags indicating which area(s) of the element the pointer is over */)
-{
-  function expect_hit(target){
-    var intercepts_iff =
-      hit_conditions[element.getAttribute('pointer-events')][target + '-intercepts-iff'];
-
-    if (!intercepts_iff) {
-      return false; // never intercepts events
-    }
-
-    for (var attr in intercepts_iff) {
-      var vals = intercepts_iff[attr];  // must get this before we adjust 'attr'
-      var invert = false;
-      if (attr.substr(-1) == '!') {
-        invert = true;
-        attr = attr.substr(0, attr.length-1);
-      }
-      var match = vals.indexOf(element.getAttribute(attr)) > -1;
-      if (invert) {
-        match = !match;
-      }
-      if (!match) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  return (over & POINT_OVER_FILL) != 0   && expect_hit('fill') ||
-         (over & POINT_OVER_STROKE) != 0 && expect_hit('stroke');
-}
-
-function for_all_permutations(inputs, callback)
-{
-  var current_permutation = arguments[2] || {};
-  var index = arguments[3] || 0;
-
-  if (index < inputs.length) {
-    var name = inputs[index].name;
-    var values = inputs[index].values;
-    for (var i = 0; i < values.length; ++i) {
-      current_permutation[name] = values[i];
-      for_all_permutations(inputs, callback, current_permutation, index+1);
-    }
-    return;
-  }
-
-  callback(current_permutation);
-}
-
-function make_log_msg(over, tag, attributes)
-{
-  var target;
-  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
-    target = 'fill and stroke';
-  } else if (over == POINT_OVER_FILL) {
-    target = 'fill';
-  } else if (over == POINT_OVER_STROKE) {
-    target = 'stroke';
-  } else {
-    throw "unexpected bit combination in 'over'";
-  }
-  var msg = 'Check if events are intercepted at a point over the '+target+' on <'+tag+'> for';
-  for (var attr in attributes) {
-    msg += ' '+attr+'='+attributes[attr];
-  }
-  return msg;
-}
-
-var dx, dy; // offset of <svg> element from pointer coordinates origin
-
-function test_element(id, x, y, over /* bit flags indicating which area(s) of the element the pointer is over */)
-{
-  var element = document.getElementById(id);
-  var tag = element.tagName;
-
-  function test_permutation(attributes) {
-    for (var attr in attributes) {
-      element.setAttribute(attr, attributes[attr]);
-    }
-    var hits = document.elementFromPoint(dx + x, dy + y) == element;
-    var msg = make_log_msg(over, tag, attributes);
-
-    is(hits, hit_expected(element, over), msg);
-  }
-
-  var inputs;
-  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
-    inputs = hit_test_inputs['both'];
-  } else if (over == POINT_OVER_FILL) {
-    inputs = hit_test_inputs['fill'];
-  } else if (over == POINT_OVER_STROKE) {
-    inputs = hit_test_inputs['stroke'];
-  } else {
-    throw "unexpected bit combination in 'over'";
-  }
-
-  for_all_permutations(inputs, test_permutation);
-
-  // To reduce the chance of bogus results in subsequent tests:
-  element.setAttribute('fill', 'none');
-  element.setAttribute('stroke', 'none');
-}
-
-function run_tests()
-{
-  var div = document.getElementById("div");
-  dx = div.offsetLeft;
-  dy = div.offsetTop;
-
-  test_element('rect', 30, 30, POINT_OVER_FILL);
-  test_element('rect', 5, 5, POINT_OVER_STROKE);
-
-  // The SVG 1.1 spec essentially says that, for text, hit testing is done
-  // against the character cells of the text, and not the fill and stroke as
-  // you might expect for a normal graphics element like <path>. See the
-  // paragraph starting "For text elements..." in this section:
-  //
-  //   http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
-  //
-  // This requirement essentially means that for the purposes of hit testing
-  // the fill and stroke areas are the same area - the character cell. (At
-  // least until we support having any fill or stroke that lies outside the
-  // character cells intercept events like Opera does - see below.) Thus, for
-  // text, when a pointer event is over a character cell it is essentially over
-  // both the fill and stroke at the same time. That's the reason we pass both
-  // the POINT_OVER_FILL and POINT_OVER_STROKE bits in test_element's 'over'
-  // argument below. It's also the reason why we only test one point in the
-  // text rather than having separate tests for fill and stroke.
-  //
-  // For hit testing of text, Opera essentially treats fill and stroke like it
-  // would on any normal element, but it adds the character cells of glyhs to
-  // both the glyphs' fill AND stroke. I think this is what we should do too.
-  // It's compatible with the letter of the SVG 1.1 rules, and it allows any
-  // parts of a glyph that are outside the glyph's character cells to also
-  // intercept events in the normal way. When we make that change we'll be able
-  // to add separate fill and stroke tests for text below.
-
-  test_element('text', 210, 30, POINT_OVER_FILL | POINT_OVER_STROKE);
-
-  SimpleTest.finish();
-}
-
-]]>
-</script>
+<body onload="run_tests(0)">
+<script class="testbody" src="pointer-events.js"></script>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619959">Mozilla Bug 619959</a>
 <p id="display"></p>
 <div id="content">
 
   <div width="100%" height="1" id="div"></div>
 
   <svg xmlns="http://www.w3.org/2000/svg" id="svg">
     <rect id="rect" x="10" y="10" width="40" height="40" stroke-width="20"/>
copy from content/svg/content/test/test_pointer-events.xhtml
copy to content/svg/content/test/test_pointer-events-1b.xhtml
--- a/content/svg/content/test/test_pointer-events.xhtml
+++ b/content/svg/content/test/test_pointer-events-1b.xhtml
@@ -2,335 +2,18 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=619959
 -->
 <head>
   <title>Test 'pointer-events' handling</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="run_tests()">
-<script class="testbody" type="text/javascript">
-<![CDATA[
-
-SimpleTest.waitForExplicitFinish();
-
-var pointer_events_values = [
-  'auto',
-  'visiblePainted',
-  'visibleFill',
-  'visibleStroke',
-  'visible',
-  'painted',
-  'fill',
-  'stroke',
-  'all',
-  'none'
-];
-
-var paint_values = [
-  'blue',
-  'transparent',
-  'none'
-];
-
-var opacity_values = [
-  '1',
-  '0.5',
-  '0'
-];
-
-var visibility_values = [
-  'visible',
-  'hidden',
-  'collapse'
-];
-
-/**
- * List of attributes and various values for which we want to test permutations
- * when hit testing a pointer event that is over an element's fill area,
- * stroke area, or both (where they overlap).
- *
- * We're using an array of objects so that we have control over the order in
- * which permutations are tested.
- *
- * TODO: test the effect of clipping, masking, filters, markers, etc.
- */
-var hit_test_inputs = {
-  fill: [
-    { name: 'pointer-events',  values: pointer_events_values },
-    { name: 'fill',            values: paint_values },
-    { name: 'fill-opacity',    values: opacity_values },
-    { name: 'opacity',         values: opacity_values },
-    { name: 'visibility',      values: visibility_values }
-  ],
-  stroke: [
-    { name: 'pointer-events',  values: pointer_events_values },
-    { name: 'stroke',          values: paint_values },
-    { name: 'stroke-opacity',  values: opacity_values },
-    { name: 'opacity',         values: opacity_values },
-    { name: 'visibility',      values: visibility_values }
-  ],
-  both: [
-    { name: 'pointer-events',  values: pointer_events_values },
-    { name: 'fill',            values: paint_values },
-    { name: 'fill-opacity',    values: opacity_values },
-    { name: 'stroke',          values: paint_values },
-    { name: 'stroke-opacity',  values: opacity_values },
-    { name: 'opacity',         values: opacity_values },
-    { name: 'visibility',      values: visibility_values }
-  ]
-}
-
-/**
- * The following object contains a list of 'pointer-events' property values,
- * each with an object detailing the conditions under which the fill and stroke
- * of a graphical object will intercept pointer events for the given value. If
- * the object contains a 'fill-intercepts-iff' property then the fill is
- * expected to intercept pointer events for that value of 'pointer-events' if
- * and only if the conditions listed in the 'fill-intercepts-iff' object are
- * met. If there are no conditions in the 'fill-intercepts-iff' object then the
- * fill should always intercept pointer events. However, if the
- * 'fill-intercepts-iff' property is not set at all then it indicates that the
- * fill should never intercept pointer events. The same rules apply for
- * 'stroke-intercepts-iff'.
- *
- * If an attribute name in the conditions list is followed by the "!"
- * character then the requirement for a hit is that its value is NOT any
- * of the values listed in the given array.
- */
-var hit_conditions = {
-  auto: {
-    'fill-intercepts-iff': {
-      'visibility': ['visible'],
-      'fill!': ['none']
-    },
-    'stroke-intercepts-iff': {
-      'visibility': ['visible'],
-      'stroke!': ['none']
-    }
-  },
-  visiblePainted: {
-    'fill-intercepts-iff': {
-      'visibility': ['visible'],
-      'fill!': ['none']
-    },
-    'stroke-intercepts-iff': {
-      'visibility': ['visible'],
-      'stroke!': ['none']
-    }
-  },
-  visibleFill: {
-    'fill-intercepts-iff': {
-      visibility: ['visible']
-    }
-    // stroke never intercepts pointer events
-  },
-  visibleStroke: {
-    // fill never intercepts pointer events
-    'stroke-intercepts-iff': {
-      visibility: ['visible']
-    }
-  },
-  visible: {
-    'fill-intercepts-iff': {
-      visibility: ['visible']
-    },
-    'stroke-intercepts-iff': {
-      visibility: ['visible']
-    }
-  },
-  painted: {
-    'fill-intercepts-iff': {
-      'fill!': ['none']
-    },
-    'stroke-intercepts-iff': {
-      'stroke!': ['none']
-    }
-  },
-  fill: {
-    'fill-intercepts-iff': {
-      // fill always intercepts pointer events
-    }
-    // stroke never intercepts pointer events
-  },
-  stroke: {
-    // fill never intercepts pointer events
-    'stroke-intercepts-iff': {
-      // stroke always intercepts pointer events
-    }
-  },
-  all: {
-    'fill-intercepts-iff': {
-      // fill always intercepts pointer events
-    },
-    'stroke-intercepts-iff': {
-      // stroke always intercepts pointer events
-    }
-  },
-  none: {
-    // neither fill nor stroke intercept pointer events
-  }
-}
-
-// bit flags
-var POINT_OVER_FILL   = 0x1;
-var POINT_OVER_STROKE = 0x2;
-
-/**
- * Examine the element's attribute values and, based on the area(s) of the
- * element that the pointer event is over (fill and/or stroke areas), return
- * true if the element is expected to intercept the event, otherwise false.
- */
-function hit_expected(element, over /* bit flags indicating which area(s) of the element the pointer is over */)
-{
-  function expect_hit(target){
-    var intercepts_iff =
-      hit_conditions[element.getAttribute('pointer-events')][target + '-intercepts-iff'];
-
-    if (!intercepts_iff) {
-      return false; // never intercepts events
-    }
-
-    for (var attr in intercepts_iff) {
-      var vals = intercepts_iff[attr];  // must get this before we adjust 'attr'
-      var invert = false;
-      if (attr.substr(-1) == '!') {
-        invert = true;
-        attr = attr.substr(0, attr.length-1);
-      }
-      var match = vals.indexOf(element.getAttribute(attr)) > -1;
-      if (invert) {
-        match = !match;
-      }
-      if (!match) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  return (over & POINT_OVER_FILL) != 0   && expect_hit('fill') ||
-         (over & POINT_OVER_STROKE) != 0 && expect_hit('stroke');
-}
-
-function for_all_permutations(inputs, callback)
-{
-  var current_permutation = arguments[2] || {};
-  var index = arguments[3] || 0;
-
-  if (index < inputs.length) {
-    var name = inputs[index].name;
-    var values = inputs[index].values;
-    for (var i = 0; i < values.length; ++i) {
-      current_permutation[name] = values[i];
-      for_all_permutations(inputs, callback, current_permutation, index+1);
-    }
-    return;
-  }
-
-  callback(current_permutation);
-}
-
-function make_log_msg(over, tag, attributes)
-{
-  var target;
-  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
-    target = 'fill and stroke';
-  } else if (over == POINT_OVER_FILL) {
-    target = 'fill';
-  } else if (over == POINT_OVER_STROKE) {
-    target = 'stroke';
-  } else {
-    throw "unexpected bit combination in 'over'";
-  }
-  var msg = 'Check if events are intercepted at a point over the '+target+' on <'+tag+'> for';
-  for (var attr in attributes) {
-    msg += ' '+attr+'='+attributes[attr];
-  }
-  return msg;
-}
-
-var dx, dy; // offset of <svg> element from pointer coordinates origin
-
-function test_element(id, x, y, over /* bit flags indicating which area(s) of the element the pointer is over */)
-{
-  var element = document.getElementById(id);
-  var tag = element.tagName;
-
-  function test_permutation(attributes) {
-    for (var attr in attributes) {
-      element.setAttribute(attr, attributes[attr]);
-    }
-    var hits = document.elementFromPoint(dx + x, dy + y) == element;
-    var msg = make_log_msg(over, tag, attributes);
-
-    is(hits, hit_expected(element, over), msg);
-  }
-
-  var inputs;
-  if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
-    inputs = hit_test_inputs['both'];
-  } else if (over == POINT_OVER_FILL) {
-    inputs = hit_test_inputs['fill'];
-  } else if (over == POINT_OVER_STROKE) {
-    inputs = hit_test_inputs['stroke'];
-  } else {
-    throw "unexpected bit combination in 'over'";
-  }
-
-  for_all_permutations(inputs, test_permutation);
-
-  // To reduce the chance of bogus results in subsequent tests:
-  element.setAttribute('fill', 'none');
-  element.setAttribute('stroke', 'none');
-}
-
-function run_tests()
-{
-  var div = document.getElementById("div");
-  dx = div.offsetLeft;
-  dy = div.offsetTop;
-
-  test_element('rect', 30, 30, POINT_OVER_FILL);
-  test_element('rect', 5, 5, POINT_OVER_STROKE);
-
-  // The SVG 1.1 spec essentially says that, for text, hit testing is done
-  // against the character cells of the text, and not the fill and stroke as
-  // you might expect for a normal graphics element like <path>. See the
-  // paragraph starting "For text elements..." in this section:
-  //
-  //   http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
-  //
-  // This requirement essentially means that for the purposes of hit testing
-  // the fill and stroke areas are the same area - the character cell. (At
-  // least until we support having any fill or stroke that lies outside the
-  // character cells intercept events like Opera does - see below.) Thus, for
-  // text, when a pointer event is over a character cell it is essentially over
-  // both the fill and stroke at the same time. That's the reason we pass both
-  // the POINT_OVER_FILL and POINT_OVER_STROKE bits in test_element's 'over'
-  // argument below. It's also the reason why we only test one point in the
-  // text rather than having separate tests for fill and stroke.
-  //
-  // For hit testing of text, Opera essentially treats fill and stroke like it
-  // would on any normal element, but it adds the character cells of glyhs to
-  // both the glyphs' fill AND stroke. I think this is what we should do too.
-  // It's compatible with the letter of the SVG 1.1 rules, and it allows any
-  // parts of a glyph that are outside the glyph's character cells to also
-  // intercept events in the normal way. When we make that change we'll be able
-  // to add separate fill and stroke tests for text below.
-
-  test_element('text', 210, 30, POINT_OVER_FILL | POINT_OVER_STROKE);
-
-  SimpleTest.finish();
-}
-
-]]>
-</script>
+<body onload="run_tests(1)">
+<script class="testbody" src="pointer-events.js"></script>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619959">Mozilla Bug 619959</a>
 <p id="display"></p>
 <div id="content">
 
   <div width="100%" height="1" id="div"></div>
 
   <svg xmlns="http://www.w3.org/2000/svg" id="svg">
     <rect id="rect" x="10" y="10" width="40" height="40" stroke-width="20"/>
--- a/dom/base/ConsoleAPIStorage.jsm
+++ b/dom/base/ConsoleAPIStorage.jsm
@@ -47,20 +47,17 @@ this.ConsoleAPIStorage = {
       Services.obs.removeObserver(this, "inner-window-destroyed");
       Services.obs.removeObserver(this, "memory-pressure");
     }
     else if (aTopic == "inner-window-destroyed") {
       let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
       this.clearEvents(innerWindowID);
     }
     else if (aTopic == "memory-pressure") {
-      /* Handle both low-memory and low-memory-no-forward events */
-      if (aData.startsWith("low-memory")) {
-        this.clearEvents();
-      }
+      this.clearEvents();
     }
   },
 
   /** @private */
   init: function CS_init()
   {
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     Services.obs.addObserver(this, "inner-window-destroyed", false);
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -235,16 +235,22 @@ public:
 
 NS_IMPL_ISUPPORTS1(nsJSEnvironmentObserver, nsIObserver)
 
 NS_IMETHODIMP
 nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
                                  const PRUnichar* aData)
 {
   if (sGCOnMemoryPressure && !nsCRT::strcmp(aTopic, "memory-pressure")) {
+    if(StringBeginsWith(nsDependentString(aData),
+                        NS_LITERAL_STRING("low-memory-ongoing"))) {
+      // Don't GC/CC if we are in an ongoing low-memory state since its very
+      // slow and it likely won't help us anyway.
+      return NS_OK;
+    }
     nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE,
                                    nsJSContext::NonIncrementalGC,
                                    nsJSContext::NonCompartmentGC,
                                    nsJSContext::ShrinkingGC);
     nsJSContext::CycleCollectNow();
   } else if (!nsCRT::strcmp(aTopic, "quit-application")) {
     sShuttingDown = true;
     KillTimers();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1438,17 +1438,18 @@ ContentParent::Observe(nsISupports* aSub
         NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess");
     }
 
     if (!mIsAlive || !mSubprocess)
         return NS_OK;
 
     // listening for memory pressure event
     if (!strcmp(aTopic, "memory-pressure") &&
-        !NS_LITERAL_STRING("low-memory-no-forward").Equals(aData)) {
+        !StringEndsWith(nsDependentString(aData),
+                        NS_LITERAL_STRING("-no-forward"))) {
         unused << SendFlushMemory(nsDependentString(aData));
     }
     // listening for remotePrefs...
     else if (!strcmp(aTopic, "nsPref:changed")) {
         // We know prefs are ASCII here.
         NS_LossyConvertUTF16toASCII strData(aData);
 
         PrefSetting pref(strData, null_t(), null_t());
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1285,19 +1285,29 @@ TabChild::GetCachedFileDescriptor(const 
         return false;
     }
 
     nsAutoPtr<CachedFileDescriptorInfo>& info =
         mCachedFileDescriptorInfos[index];
 
     MOZ_ASSERT(info);
     MOZ_ASSERT(info->mPath == aPath);
+
+    // If we got a previous request for this file descriptor that was then
+    // canceled, insert the new request ahead of the old in the queue so that
+    // it will be serviced first.
+    if (info->mCanceled) {
+        // This insertion will change the array and invalidate |info|, so
+        // be careful not to touch |info| after this.
+        mCachedFileDescriptorInfos.InsertElementAt(index,
+            new CachedFileDescriptorInfo(aPath, aCallback));
+        return false;
+    }
+
     MOZ_ASSERT(!info->mCallback);
-    MOZ_ASSERT(!info->mCanceled);
-
     info->mCallback = aCallback;
 
     nsRefPtr<CachedFileDescriptorCallbackRunnable> runnable =
         new CachedFileDescriptorCallbackRunnable(info.forget());
     runnable->Dispatch();
 
     mCachedFileDescriptorInfos.RemoveElementAt(index);
     return true;
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -33,17 +33,19 @@
 #include "nsPrintfCString.h"
 
 #include "prsystem.h"
 
 #ifdef XP_WIN
 #include "PluginHangUIParent.h"
 #include "mozilla/widget/AudioSession.h"
 #endif
+#ifdef MOZ_ENABLE_PROFILER_SPS
 #include "nsIProfileSaveEvent.h"
+#endif
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 
 using base::KillProcess;
 
 using mozilla::PluginLibrary;
 using mozilla::ipc::SyncChannel;
 using mozilla::dom::PCrashReporterParent;
@@ -136,24 +138,28 @@ PluginModuleParent::PluginModuleParent(c
 
     Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this);
     Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this);
 #ifdef XP_WIN
     Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this);
     Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this);
 #endif
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
     InitPluginProfiling();
+#endif
 }
 
 PluginModuleParent::~PluginModuleParent()
 {
     NS_ASSERTION(OkToCleanup(), "unsafe destruction");
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
     ShutdownPluginProfiling();
+#endif
 
     if (!mShutdown) {
         NS_WARNING("Plugin host deleted the module without shutting down.");
         NPError err;
         NP_Shutdown(&err);
     }
     NS_ASSERTION(mShutdown, "NP_Shutdown didn't");
 
@@ -1712,16 +1718,17 @@ PluginModuleParent::OnCrash(DWORD proces
     if (!mShutdown) {
         GetIPCChannel()->CloseWithError();
         KillProcess(OtherProcess(), 1, false);
     }
 }
 
 #endif // MOZ_CRASHREPORTER_INJECTOR
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
 class PluginProfilerObserver MOZ_FINAL : public nsIObserver,
                                          public nsSupportsWeakReference
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
 
     explicit PluginProfilerObserver(PluginModuleParent* pmp)
@@ -1763,9 +1770,9 @@ PluginModuleParent::InitPluginProfiling(
 void
 PluginModuleParent::ShutdownPluginProfiling()
 {
     nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
     if (observerService) {
         observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess");
     }
 }
-
+#endif
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -296,18 +296,20 @@ private:
     void ProcessFirstMinidump();
     void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes);
 #endif
     void CleanupFromTimeout(const bool aByHangUI);
     void SetChildTimeout(const int32_t aChildTimeout);
     static int TimeoutChanged(const char* aPref, void* aModule);
     void NotifyPluginCrashed();
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
     void InitPluginProfiling();
     void ShutdownPluginProfiling();
+#endif
 
     PluginProcessParent* mSubprocess;
     // the plugin thread in mSubprocess
     NativeThreadId mPluginThread;
     bool mShutdown;
     bool mClearSiteDataSupported;
     bool mGetSitesWithDataSupported;
     const NPNetscapeFuncs* mNPNIface;
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -200,59 +200,57 @@ nsJSON::EncodeInternal(JSContext* cx, co
 {
   JSAutoRequest ar(cx);
 
   // Backward compatibility:
   // nsIJSON does not allow to serialize anything other than objects
   if (!aValue.isObject()) {
     return NS_ERROR_INVALID_ARG;
   }
-
-  JSObject* obj = &aValue.toObject();
-
-  JS::Value val = aValue;
+  JS::Rooted<JSObject*> obj(cx, &aValue.toObject());
 
   /* Backward compatibility:
    * Manually call toJSON if implemented by the object and check that
    * the result is still an object
    * Note: It is perfectly fine to not implement toJSON, so it is
    * perfectly fine for GetMethod to fail
    */
-  JS::Value toJSON;
-  if (JS_GetMethod(cx, obj, "toJSON", NULL, &toJSON) &&
-      !JSVAL_IS_PRIMITIVE(toJSON) &&
-      JS_ObjectIsCallable(cx, JSVAL_TO_OBJECT(toJSON))) {
+  JS::Rooted<JS::Value> val(cx, aValue);
+  JS::Rooted<JS::Value> toJSON(cx);
+  if (JS_GetProperty(cx, obj, "toJSON", toJSON.address()) &&
+      toJSON.isObject() &&
+      JS_ObjectIsCallable(cx, &toJSON.toObject())) {
     // If toJSON is implemented, it must not throw
-    if (!JS_CallFunctionValue(cx, obj, toJSON, 0, NULL, &val)) {
+    if (!JS_CallFunctionValue(cx, obj, toJSON, 0, NULL, val.address())) {
       if (JS_IsExceptionPending(cx))
         // passing NS_OK will throw the pending exception
         return NS_OK;
 
       // No exception, but still failed
       return NS_ERROR_FAILURE;
     }
 
     // Backward compatibility:
     // nsIJSON does not allow to serialize anything other than objects
-    if (JSVAL_IS_PRIMITIVE(val))
+    if (val.isPrimitive())
       return NS_ERROR_INVALID_ARG;
   }
   // GetMethod may have thrown
   else if (JS_IsExceptionPending(cx))
     // passing NS_OK will throw the pending exception
     return NS_OK;
 
   // Backward compatibility:
   // function shall not pass, just "plain" objects and arrays
   JSType type = JS_TypeOfValue(cx, val);
   if (type == JSTYPE_FUNCTION)
     return NS_ERROR_INVALID_ARG;
 
   // We're good now; try to stringify
-  if (!JS_Stringify(cx, &val, NULL, JSVAL_NULL, WriteCallback, writer))
+  if (!JS_Stringify(cx, val.address(), NULL, JSVAL_NULL, WriteCallback, writer))
     return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
 
 nsJSONWriter::nsJSONWriter() : mStream(nullptr),
                                mBuffer(nullptr),
--- a/dom/telephony/test/marionette/test_crash_emulator.js
+++ b/dom/telephony/test/marionette/test_crash_emulator.js
@@ -4,16 +4,48 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let outNumber = "5555551111";
 let outgoingCall;
 
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      dial();
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(dial, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
 function dial() {
   log("Make an outgoing call.");
   outgoingCall = telephony.dial(outNumber);
 
   outgoingCall.onalerting = function onalerting(event) {
     log("Received 'alerting' call event.");
     answer();
   };  
@@ -36,9 +68,9 @@ function answer() {
 }
 
 function cleanUp(){
   outgoingCall.hangUp();
   ok("passed");
   finish();
 }
 
-dial();
\ No newline at end of file
+getExistingCalls();
\ No newline at end of file
--- a/dom/telephony/test/marionette/test_incoming_already_connected.js
+++ b/dom/telephony/test/marionette/test_incoming_already_connected.js
@@ -7,33 +7,69 @@ SpecialPowers.addPermission("telephony",
 
 let telephony = window.navigator.mozTelephony;
 let outNumber = "5555551111";
 let inNumber = "5555552222";
 let outgoingCall;
 let incomingCall;
 let gotOriginalConnected = false;
 
-function verifyInitialState() {
+
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
   outgoingCall = telephony.dial(outNumber);
   ok(outgoingCall);
   is(outgoingCall.number, outNumber);
   is(outgoingCall.state, "dialing");
@@ -209,9 +245,9 @@ function hangUpIncoming() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_already_held.js
+++ b/dom/telephony/test/marionette/test_incoming_already_held.js
@@ -7,35 +7,71 @@ SpecialPowers.addPermission("telephony",
 
 let telephony = window.navigator.mozTelephony;
 let outNumber = "5555551111";
 let inNumber = "5555552222";
 let outgoingCall;
 let incomingCall;
 let gotOriginalConnected = false;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
+
 function dial() {
   log("Make an outgoing call.");
   outgoingCall = telephony.dial(outNumber);
   ok(outgoingCall);
   is(outgoingCall.number, outNumber);
   is(outgoingCall.state, "dialing");
 
   is(outgoingCall, telephony.active);
@@ -240,9 +276,9 @@ function hangUpIncoming() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_answer_hangup.js
+++ b/dom/telephony/test/marionette/test_incoming_answer_hangup.js
@@ -5,34 +5,68 @@ MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let incoming;
 let calls;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incoming = event.call;
@@ -112,9 +146,9 @@ function hangUp() {
   incoming.hangUp();
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_answer_hangup_oncallschanged.js
+++ b/dom/telephony/test/marionette/test_incoming_answer_hangup_oncallschanged.js
@@ -3,36 +3,69 @@
 
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let incoming;
-let calls;
+
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
 
-function verifyInitialState() {
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.oncallschanged = function oncallschanged(event) {
     log("Received 'callschanged' event.");
 
@@ -41,17 +74,16 @@ function simulateIncoming() {
       "Unexpected call state: " + event.call.state);
 
     if (event.call.state == "incoming") {
       log("Received 'callschanged' event for an incoming call.");
       incoming = event.call;
       ok(incoming);
       is(incoming.number, number);
 
-      //ok(telephony.calls === calls); // bug 717414
       is(telephony.calls.length, 1);
       is(telephony.calls[0], incoming);
 
       runEmulatorCmd("gsm list", function(result) {
         log("Call list is now: " + result);
         is(result[0], "inbound from " + number + " : incoming");
         is(result[1], "OK");
         answer();
@@ -132,9 +164,9 @@ function hangUp() {
 }
 
 function cleanUp() {
   telephony.oncallschanged = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_answer_remote_hangup.js
+++ b/dom/telephony/test/marionette/test_incoming_answer_remote_hangup.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -104,9 +139,9 @@ function remoteHangUp() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_connecting_hangup.js
+++ b/dom/telephony/test/marionette/test_incoming_connecting_hangup.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -110,9 +145,9 @@ function hangUp() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_connecting_remote_hangup.js
+++ b/dom/telephony/test/marionette/test_incoming_connecting_remote_hangup.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -92,9 +127,9 @@ function remoteHangUp() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_hangup_held.js
+++ b/dom/telephony/test/marionette/test_incoming_hangup_held.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -142,9 +177,9 @@ function hangUp() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_hold_resume.js
+++ b/dom/telephony/test/marionette/test_incoming_hold_resume.js
@@ -5,33 +5,68 @@ MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555551234";
 let connectedCalls;
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -176,9 +211,9 @@ function hangUp() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
\ No newline at end of file
+getExistingCalls();
\ No newline at end of file
--- a/dom/telephony/test/marionette/test_incoming_onstatechange.js
+++ b/dom/telephony/test/marionette/test_incoming_onstatechange.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let incomingCall;
 let inNumber = "5555551111";
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -159,9 +194,9 @@ function hangUp() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_reject.js
+++ b/dom/telephony/test/marionette/test_incoming_reject.js
@@ -5,34 +5,68 @@ MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let incoming;
 let calls;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incoming = event.call;
@@ -84,9 +118,9 @@ function reject() {
 }
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_remote_cancel.js
+++ b/dom/telephony/test/marionette/test_incoming_remote_cancel.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -75,9 +110,9 @@ function cancelIncoming(){
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_incoming_remote_hangup_held.js
+++ b/dom/telephony/test/marionette/test_incoming_remote_hangup_held.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -135,9 +170,9 @@ function hangUp() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_multiple_hold.js
+++ b/dom/telephony/test/marionette/test_multiple_hold.js
@@ -6,28 +6,68 @@ MARIONETTE_TIMEOUT = 60000;
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let outNumber = "5555552222";
 let incomingCall;
 let outgoingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
     simulateIncoming();
-  });
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -281,9 +321,9 @@ function hangUpOutgoing() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_already_held.js
+++ b/dom/telephony/test/marionette/test_outgoing_already_held.js
@@ -6,33 +6,68 @@ MARIONETTE_TIMEOUT = 60000;
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let outNumber = "5555552222";
 let incomingCall;
 let outgoingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -232,9 +267,9 @@ function hangUpOutgoing() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_answer_hangup.js
+++ b/dom/telephony/test/marionette/test_outgoing_answer_hangup.js
@@ -5,34 +5,68 @@ MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let outgoing;
 let calls;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
@@ -101,9 +135,9 @@ function hangUp() {
   runEmulatorCmd("gsm cancel " + number);
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_answer_hangup_oncallschanged.js
+++ b/dom/telephony/test/marionette/test_outgoing_answer_hangup_oncallschanged.js
@@ -3,36 +3,69 @@
 
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let outgoing;
-let calls;
+
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
 
-function verifyInitialState() {
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   telephony.oncallschanged = function oncallschanged(event) {
     log("Received 'callschanged' call event.");
 
@@ -103,9 +136,9 @@ function hangUp() {
 }
 
 function cleanUp() {
   telephony.oncallschanged = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_answer_local_hangup.js
+++ b/dom/telephony/test/marionette/test_outgoing_answer_local_hangup.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let outgoingCall;
 let outNumber = "5555551111";
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoingCall = telephony.dial(outNumber);
   ok(outgoingCall);
   is(outgoingCall.number, outNumber);
@@ -107,9 +142,9 @@ function hangUp() {
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_badNumber.js
+++ b/dom/telephony/test/marionette/test_outgoing_badNumber.js
@@ -3,48 +3,80 @@
 
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "****5555552368****";
 let outgoing;
-let calls;
+
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
 
-function verifyInitialState() {
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call to an invalid number.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
   is(outgoing.state, "dialing");
 
   is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
   is(telephony.calls.length, 1);
   is(telephony.calls[0], outgoing);
 
   outgoing.onerror = function onerror(event) {
     log("Received 'error' event.");
     is(event.call, outgoing);
     ok(event.call.error);
     is(event.call.error.name, "BadNumberError");
@@ -57,9 +89,9 @@ function dial() {
   };
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_busy.js
+++ b/dom/telephony/test/marionette/test_outgoing_busy.js
@@ -3,48 +3,80 @@
 
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let outgoing;
-let calls;
+
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
 
-function verifyInitialState() {
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
   is(outgoing.state, "dialing");
 
   is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
   is(telephony.calls.length, 1);
   is(telephony.calls[0], outgoing);
 
   outgoing.onalerting = function onalerting(event) {
     log("Received 'onalerting' call event.");
     is(outgoing, event.call);
     is(outgoing.state, "alerting");
 
@@ -74,9 +106,9 @@ function busy() {
   runEmulatorCmd("gsm busy " + number);
 };
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
+++ b/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
@@ -12,17 +12,49 @@ if (!gSettingsEnabled) {
 SpecialPowers.addPermission("telephony", true, document);
 SpecialPowers.addPermission("settings-write", true, document);
 
 let settings = window.navigator.mozSettings;
 let telephony = window.navigator.mozTelephony;
 let number = "112";
 let outgoing;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Turning on airplane mode");
 
   let setLock = settings.createLock();
   let obj = {};
   obj[KEY] = false;
   let setReq = setLock.set(obj);
   setReq.addEventListener("success", function onSetSuccess() {
     ok(true, "set '" + KEY + "' to " + obj[KEY]);
@@ -31,27 +63,30 @@ function verifyInitialState() {
     ok(false, "cannot set '" + KEY + "'");
   });
 
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
-    log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
+      log("Initial call list: " + result);
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      }
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
@@ -121,9 +156,9 @@ function hangUp() {
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   SpecialPowers.removePermission("settings-write", document);
   SpecialPowers.clearUserPref("dom.mozSettings.enabled");
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_hangup_alerting.js
+++ b/dom/telephony/test/marionette/test_outgoing_hangup_alerting.js
@@ -5,34 +5,68 @@ MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let outgoing;
 let calls;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
@@ -85,9 +119,9 @@ function hangUp() {
   outgoing.hangUp();
 };
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_hangup_held.js
+++ b/dom/telephony/test/marionette/test_outgoing_hangup_held.js
@@ -5,34 +5,68 @@ MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let outgoing;
 let calls;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
@@ -137,9 +171,9 @@ function hangUp() {
   outgoing.hangUp();
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_hold_resume.js
+++ b/dom/telephony/test/marionette/test_outgoing_hold_resume.js
@@ -5,33 +5,68 @@ MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555557777";
 let connectedCalls;
 let outgoingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoingCall = telephony.dial(number);
   ok(outgoingCall);
   is(outgoingCall.number, number);
@@ -173,9 +208,9 @@ function hangUp() {
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
\ No newline at end of file
+getExistingCalls();
\ No newline at end of file
--- a/dom/telephony/test/marionette/test_outgoing_onstatechange.js
+++ b/dom/telephony/test/marionette/test_outgoing_onstatechange.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let outgoingCall;
 let outNumber = "5555551111";
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoingCall = telephony.dial(outNumber);
   ok(outgoingCall);
   is(outgoingCall.number, outNumber);
@@ -159,9 +194,9 @@ function hangUp() {
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_reject.js
+++ b/dom/telephony/test/marionette/test_outgoing_reject.js
@@ -3,48 +3,80 @@
 
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let number = "5555552368";
 let outgoing;
-let calls;
+
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
 
-function verifyInitialState() {
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-  calls = telephony.calls;
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }    
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoing = telephony.dial(number);
   ok(outgoing);
   is(outgoing.number, number);
   is(outgoing.state, "dialing");
 
   is(outgoing, telephony.active);
-  //ok(telephony.calls === calls); // bug 717414
   is(telephony.calls.length, 1);
   is(telephony.calls[0], outgoing);
 
   outgoing.onalerting = function onalerting(event) {
     log("Received 'onalerting' call event.");
     is(outgoing, event.call);
     is(outgoing.state, "alerting");
 
@@ -78,9 +110,9 @@ function reject() {
   runEmulatorCmd("gsm cancel " + number);
 };
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_outgoing_remote_hangup_held.js
+++ b/dom/telephony/test/marionette/test_outgoing_remote_hangup_held.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let outNumber = "5555551111";
 let outgoingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
 
   outgoingCall = telephony.dial(outNumber);
   ok(outgoingCall);
   is(outgoingCall.number, outNumber);
@@ -130,9 +165,9 @@ function hangUp() {
 }
 
 function cleanUp() {
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_redundant_operations.js
+++ b/dom/telephony/test/marionette/test_redundant_operations.js
@@ -4,33 +4,68 @@
 MARIONETTE_TIMEOUT = 60000;
 
 SpecialPowers.addPermission("telephony", true, document);
 
 let telephony = window.navigator.mozTelephony;
 let inNumber = "5555551111";
 let incomingCall;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      simulateIncoming();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        simulateIncoming();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    simulateIncoming();
+  }
 }
 
 function simulateIncoming() {
   log("Simulating an incoming call.");
 
   telephony.onincoming = function onincoming(event) {
     log("Received 'incoming' call event.");
     incomingCall = event.call;
@@ -343,9 +378,9 @@ function hangUpNonConnected() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/dom/telephony/test/marionette/test_swap_held_and_active.js
+++ b/dom/telephony/test/marionette/test_swap_held_and_active.js
@@ -9,33 +9,68 @@ let telephony = window.navigator.mozTele
 let outNumber = "5555551111";
 let inNumber = "5555552222";
 let outgoingCall;
 let incomingCall;
 let gotOriginalConnected = false;
 let gotHeld = false;
 let gotConnected = false;
 
-function verifyInitialState() {
+function getExistingCalls() {
+  runEmulatorCmd("gsm list", function(result) {
+    log("Initial call list: " + result);
+    if (result[0] == "OK") {
+      verifyInitialState(false);
+    } else {
+      cancelExistingCalls(result);
+    };
+  });
+}
+
+function cancelExistingCalls(callList) {
+  if (callList.length && callList[0] != "OK") {
+    // Existing calls remain; get rid of the next one in the list
+    nextCall = callList.shift().split(' ')[2].trim();
+    log("Cancelling existing call '" + nextCall +"'");
+    runEmulatorCmd("gsm cancel " + nextCall, function(result) {
+      if (result[0] == "OK") {
+        cancelExistingCalls(callList);
+      } else {
+        log("Failed to cancel existing call");
+        cleanUp();
+      };
+    });
+  } else {
+    // No more calls in the list; give time for emulator to catch up
+    waitFor(verifyInitialState, function() {
+      return (telephony.calls.length == 0);
+    });
+  };
+}
+
+function verifyInitialState(confirmNoCalls = true) {
   log("Verifying initial state.");
   ok(telephony);
   is(telephony.active, null);
   ok(telephony.calls);
   is(telephony.calls.length, 0);
-
-  runEmulatorCmd("gsm list", function(result) {
+  if (confirmNoCalls) {
+    runEmulatorCmd("gsm list", function(result) {
     log("Initial call list: " + result);
-    is(result[0], "OK");
-    if (result[0] == "OK") {
-      dial();
-    } else {
-      log("Call exists from a previous test, failing out.");
-      cleanUp();
-    }
-  });
+      is(result[0], "OK");
+      if (result[0] == "OK") {
+        dial();
+      } else {
+        log("Call exists from a previous test, failing out.");
+        cleanUp();
+      };
+    });
+  } else {
+    dial();
+  }
 }
 
 function dial() {
   log("Make an outgoing call.");
   outgoingCall = telephony.dial(outNumber);
   ok(outgoingCall);
   is(outgoingCall.number, outNumber);
   is(outgoingCall.state, "dialing");
@@ -297,9 +332,9 @@ function hangUpIncoming() {
 
 function cleanUp() {
   telephony.onincoming = null;
   SpecialPowers.removePermission("telephony", document);
   finish();
 }
 
 // Start the test
-verifyInitialState();
+getExistingCalls();
--- a/gfx/layers/composite/TextureHost.h
+++ b/gfx/layers/composite/TextureHost.h
@@ -240,23 +240,29 @@ public:
 
   SurfaceDescriptor* GetBuffer() const { return mBuffer; }
   /**
    * Set a SurfaceDescriptor for this texture host. By setting a buffer and
    * allocator/de-allocator for the TextureHost, you cause the TextureHost to
    * retain a SurfaceDescriptor.
    * Ownership of the SurfaceDescriptor passes to this.
    */
-  void SetBuffer(SurfaceDescriptor* aBuffer, ISurfaceAllocator* aAllocator)
+  // only made virtual to allow overriding in GrallocTextureHostOGL, for hacky fix in gecko 23 for bug 862324.
+  // see bug 865908 about fixing this.
+  virtual void SetBuffer(SurfaceDescriptor* aBuffer, ISurfaceAllocator* aAllocator)
   {
     MOZ_ASSERT(!mBuffer, "Will leak the old mBuffer");
     mBuffer = aBuffer;
     mDeAllocator = aAllocator;
   }
 
+  // used only for hacky fix in gecko 23 for bug 862324
+  // see bug 865908 about fixing this.
+  virtual void ForgetBuffer() {}
+
 protected:
   /**
    * Should be implemented by the backend-specific TextureHost classes
    *
    * It should not take a reference to aImage, unless it knows the data
    * to be thread-safe.
    */
   virtual void UpdateImpl(const SurfaceDescriptor& aImage,
@@ -285,17 +291,21 @@ protected:
   // not be exposed publicly.
   virtual uint64_t GetIdentifier() const
   {
     return reinterpret_cast<uint64_t>(this);
   }
 
   // Texture info
   TextureFlags mFlags;
-  SurfaceDescriptor* mBuffer;
+  SurfaceDescriptor* mBuffer; // FIXME [bjacob] it's terrible to have a SurfaceDescriptor here,
+                              // because SurfaceDescriptor's may have raw pointers to IPDL actors,
+                              // which can go away under our feet at any time. This is the cause
+                              // of bug 862324 among others. Our current understanding is that
+                              // this will be gone in Gecko 24. See bug 858914.
   gfx::SurfaceFormat mFormat;
 
   ISurfaceAllocator* mDeAllocator;
 };
 
 class AutoLockTextureHost
 {
 public:
--- a/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp
+++ b/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp
@@ -180,16 +180,18 @@ GrallocBufferActor::GrallocBufferActor()
   if (!registered) {
     // We want to be sure that the first call here will always run on
     // the main thread.
     NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(GrallocBufferActor));
     registered = true;
   }
+
+  mTextureHost = nullptr;
 }
 
 GrallocBufferActor::~GrallocBufferActor()
 {
   if (mAllocBytes > 0) {
     sCurrentAlloc -= mAllocBytes;
   }
 }
@@ -216,16 +218,30 @@ GrallocBufferActor::Create(const gfxIntS
   actor->mAllocBytes = aSize.width * aSize.height * bpp;
   sCurrentAlloc += actor->mAllocBytes;
 
   actor->mGraphicBuffer = buffer;
   *aOutHandle = MagicGrallocBufferHandle(buffer);
   return actor;
 }
 
+// used only for hacky fix in gecko 23 for bug 862324
+void GrallocBufferActor::ActorDestroy(ActorDestroyReason)
+{
+  if (mTextureHost) {
+    mTextureHost->ForgetBuffer();
+  }
+}
+
+// used only for hacky fix in gecko 23 for bug 862324
+void GrallocBufferActor::SetTextureHost(TextureHost* aTextureHost)
+{
+  mTextureHost = aTextureHost;
+}
+
 /*static*/ already_AddRefed<TextureImage>
 LayerManagerComposite::OpenDescriptorForDirectTexturing(GLContext* aGL,
                                                         const SurfaceDescriptor& aDescriptor,
                                                         GLenum aWrapMode)
 {
   PROFILER_LABEL("LayerManagerComposite", "OpenDescriptorForDirectTexturing");
   if (SurfaceDescriptor::TSurfaceDescriptorGralloc != aDescriptor.type()) {
     return nullptr;
--- a/gfx/layers/ipc/ShadowLayerUtilsGralloc.h
+++ b/gfx/layers/ipc/ShadowLayerUtilsGralloc.h
@@ -10,16 +10,20 @@
 
 #include <unistd.h>
 #include <ui/GraphicBuffer.h>
 
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/layers/PGrallocBufferChild.h"
 #include "mozilla/layers/PGrallocBufferParent.h"
 
+// used only for hacky fix in gecko 23 for bug 862324
+// see bug 865908 about fixing this.
+#include "TextureHost.h"
+
 #define MOZ_HAVE_SURFACEDESCRIPTORGRALLOC
 #define MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS
 
 class gfxASurface;
 
 namespace mozilla {
 namespace layers {
 
@@ -76,27 +80,39 @@ public:
          MaybeMagicGrallocBufferHandle* aOutHandle);
 
   static PGrallocBufferChild*
   Create();
 
   static android::sp<GraphicBuffer>
   GetFrom(const SurfaceDescriptorGralloc& aDescriptor);
 
+  // used only for hacky fix in gecko 23 for bug 862324
+  // see bug 865908 about fixing this.
+  void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
+
+  // used only for hacky fix in gecko 23 for bug 862324
+  // see bug 865908 about fixing this.
+  void SetTextureHost(TextureHost* aTextureHost);
+
 private:
   GrallocBufferActor();
 
   void InitFromHandle(const MagicGrallocBufferHandle& aHandle);
 
   android::sp<GraphicBuffer> mGraphicBuffer;
 
   // This value stores the number of bytes allocated in this
   // BufferActor. This will be used for the memory reporter.
   size_t mAllocBytes;
 
+  // used only for hacky fix in gecko 23 for bug 862324
+  // see bug 865908 about fixing this.
+  TextureHost* mTextureHost;
+
   friend class ISurfaceAllocator;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 namespace IPC {
 
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -632,36 +632,50 @@ GrallocTextureHostOGL::DeleteTextures()
     }
     if (mEGLImage) {
       mGL->DestroyEGLImage(mEGLImage);
       mEGLImage = 0;
     }
   }
 }
 
+// only used for hacky fix in gecko 23 for bug 862324
+static void
+RegisterTextureHostAtGrallocBufferActor(TextureHost* aTextureHost, const SurfaceDescriptor& aSurfaceDescriptor)
+{
+  if (IsSurfaceDescriptorValid(aSurfaceDescriptor)) {
+    GrallocBufferActor* actor = static_cast<GrallocBufferActor*>(aSurfaceDescriptor.get_SurfaceDescriptorGralloc().bufferParent());
+    actor->SetTextureHost(aTextureHost);
+  }
+}
+
 void
 GrallocTextureHostOGL::UpdateImpl(const SurfaceDescriptor& aImage,
                                  nsIntRegion* aRegion)
 {
   SwapTexturesImpl(aImage, aRegion);
 }
 
 void
 GrallocTextureHostOGL::SwapTexturesImpl(const SurfaceDescriptor& aImage,
                                         nsIntRegion*)
 {
-  android::sp<android::GraphicBuffer> buffer = GrallocBufferActor::GetFrom(aImage);
   MOZ_ASSERT(aImage.type() == SurfaceDescriptor::TSurfaceDescriptorGralloc);
 
   const SurfaceDescriptorGralloc& desc = aImage.get_SurfaceDescriptorGralloc();
   mGraphicBuffer = GrallocBufferActor::GetFrom(desc);
   mFormat = SurfaceFormatForAndroidPixelFormat(mGraphicBuffer->getPixelFormat());
   mTextureTarget = TextureTargetForAndroidPixelFormat(mGraphicBuffer->getPixelFormat());
 
   DeleteTextures();
+
+  // only done for hacky fix in gecko 23 for bug 862324.
+  // Doing this in SetBuffer is not enough, as ImageHostBuffered::SwapTextures can
+  // change the value of *mBuffer without calling SetBuffer again.
+  RegisterTextureHostAtGrallocBufferActor(this, aImage);
 }
 
 void GrallocTextureHostOGL::BindTexture(GLenum aTextureUnit)
 {
   MOZ_ASSERT(mGLTexture);
 
   mGL->MakeCurrent();
   mGL->fActiveTexture(aTextureUnit);
@@ -673,16 +687,23 @@ bool
 GrallocTextureHostOGL::IsValid() const
 {
   return !!mGraphicBuffer.get();
 }
 
 GrallocTextureHostOGL::~GrallocTextureHostOGL()
 {
   DeleteTextures();
+
+  // only done for hacky fix in gecko 23 for bug 862324.
+  if (mBuffer) {
+    // make sure that if the GrallocBufferActor survives us, it doesn't keep a dangling
+    // pointer to us.
+    RegisterTextureHostAtGrallocBufferActor(nullptr, *mBuffer);
+  }
 }
 
 bool
 GrallocTextureHostOGL::Lock()
 {
   /*
    * The job of this function is to ensure that the texture is tied to the
    * android::GraphicBuffer, so that texturing will source the GraphicBuffer.
@@ -729,13 +750,24 @@ GrallocTextureHostOGL::Unlock()
 }
 
 gfx::SurfaceFormat
 GrallocTextureHostOGL::GetFormat() const
 {
   return mFormat;
 }
 
+void
+GrallocTextureHostOGL::SetBuffer(SurfaceDescriptor* aBuffer, ISurfaceAllocator* aAllocator) MOZ_OVERRIDE
+{
+  MOZ_ASSERT(!mBuffer, "Will leak the old mBuffer");
+  mBuffer = aBuffer;
+  mDeAllocator = aAllocator;
+
+  // only done for hacky fix in gecko 23 for bug 862324.
+  // Doing this in SwapTextures is not enough, as the crash could occur right after SetBuffer.
+  RegisterTextureHostAtGrallocBufferActor(this, *mBuffer);
+}
 
 #endif
 
 } // namespace
 } // namespace
--- a/gfx/layers/opengl/TextureHostOGL.h
+++ b/gfx/layers/opengl/TextureHostOGL.h
@@ -600,16 +600,36 @@ public:
 
   virtual gfx::SurfaceFormat GetFormat() const;
 
   virtual TextureSourceOGL* AsSourceOGL() MOZ_OVERRIDE
   {
     return this;
   }
 
+  // only overridden for hacky fix in gecko 23 for bug 862324
+  // see bug 865908 about fixing this.
+  virtual void SetBuffer(SurfaceDescriptor* aBuffer, ISurfaceAllocator* aAllocator) MOZ_OVERRIDE;
+
+  // used only for hacky fix in gecko 23 for bug 862324
+  virtual void ForgetBuffer()
+  {
+    if (mBuffer) {
+      // Intentionally don't destroy the actor held by mBuffer here.
+      // The point is that this is only called from GrallocBufferActor::ActorDestroy
+      // where we know that the actor is already being deleted.
+      // See bug 862324 comment 39.
+      delete mBuffer;
+      mBuffer = nullptr;
+    }
+
+    mGraphicBuffer = nullptr;
+    DeleteTextures();
+  }
+
 private:
   void DeleteTextures();
 
   RefPtr<gl::GLContext> mGL;
   android::sp<android::GraphicBuffer> mGraphicBuffer;
   GLenum mTextureTarget;
   GLuint mGLTexture;
   EGLImage mEGLImage;
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -2743,28 +2743,31 @@ RasterImage::RequestDecodeCore(RequestDe
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv;
 
   if (mError)
     return NS_ERROR_FAILURE;
 
-  // If we've already got a full decoder running, and have already
-  // decoded some bytes, we have nothing to do
-  if (mDecoder && !mDecoder->IsSizeDecode() && mBytesDecoded) {
+  // If we're already decoded, there's nothing to do.
+  if (mDecoded)
     return NS_OK;
-  }
 
   // mFinishing protects against the case when we enter RequestDecode from
   // ShutdownDecoder -- in that case, we're done with the decode, we're just
   // not quite ready to admit it.  See bug 744309.
   if (mFinishing)
     return NS_OK;
 
+  // If we're currently waiting for a new frame, we can't do anything until
+  // that frame is allocated.
+  if (mDecoder && mDecoder->NeedsNewFrame())
+    return NS_OK;
+
   // If our callstack goes through a size decoder, we have a problem.
   // We need to shutdown the size decode and replace it with  a full
   // decoder, but can't do that from within the decoder itself. Thus, we post
   // an asynchronous event to the event loop to do it later. Since
   // RequestDecode() is an asynchronous function this works fine (though it's
   // a little slower).
   if (mInDecoder) {
     nsRefPtr<imgDecodeRequestor> requestor = new imgDecodeRequestor(*this);
@@ -2779,23 +2782,38 @@ RasterImage::RequestDecodeCore(RequestDe
     // If we didn't get the size out of the image, we won't until we get more
     // data, so signal that we want a full decode and give up for now.
     if (!mHasSize) {
       mWantFullDecode = true;
       return NS_OK;
     }
   }
 
-  // If we're fully decoded, we have nothing to do. This has to be after
-  // DecodeUntilSizeAvailable because it can result in a synchronous decode if
-  // we're already waiting on a full decode.
-  if (mDecoded)
+  MutexAutoLock lock(mDecodingMutex);
+
+  // If the image is waiting for decode work to be notified, go ahead and do that.
+  if (mDecodeRequest &&
+      mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
+    nsresult rv = FinishedSomeDecoding();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If we're fully decoded, we have nothing to do. We need this check after
+  // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result
+  // in us finishing an in-progress decode (or kicking off and finishing a
+  // synchronous decode if we're already waiting on a full decode).
+  if (mDecoded) {
     return NS_OK;
-
-  MutexAutoLock lock(mDecodingMutex);
+  }
+
+  // If we've already got a full decoder running, and have already
+  // decoded some bytes, we have nothing to do
+  if (mDecoder && !mDecoder->IsSizeDecode() && mBytesDecoded) {
+    return NS_OK;
+  }
 
   // If we have a size decode open, interrupt it and shut it down; or if
   // the decoder has different flags than what we need
   if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) {
     nsresult rv = FinishedSomeDecoding(eShutdownIntent_NotNeeded);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
@@ -2805,23 +2823,16 @@ RasterImage::RequestDecodeCore(RequestDe
     CONTAINER_ENSURE_SUCCESS(rv);
 
     rv = FinishedSomeDecoding();
     CONTAINER_ENSURE_SUCCESS(rv);
 
     MOZ_ASSERT(mDecoder);
   }
 
-  // If we're waiting for decode work to be notified, go ahead and do that.
-  if (mDecodeRequest &&
-      mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
-    nsresult rv = FinishedSomeDecoding();
-    CONTAINER_ENSURE_SUCCESS(rv);
-  }
-
   // If we've read all the data we have, we're done
   if (mHasSourceData && mBytesDecoded == mSourceData.Length())
     return NS_OK;
 
   // If we can do decoding now, do so.  Small images will decode completely,
   // large images will decode a bit and post themselves to the event loop
   // to finish decoding.
   if (!mDecoded && !mInDecoder && mHasSourceData && aDecodeType == SOMEWHAT_SYNCHRONOUS) {
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -18,17 +18,17 @@
 /*
  * Moving GC Stack Rooting
  *
  * A moving GC may change the physical location of GC allocated things, even
  * when they are rooted, updating all pointers to the thing to refer to its new
  * location. The GC must therefore know about all live pointers to a thing,
  * not just one of them, in order to behave correctly.
  *
- * The |Root| and |Handle| classes below are used to root stack locations
+ * The |Rooted| and |Handle| classes below are used to root stack locations
  * whose value may be held live across a call that can trigger GC. For a
  * code fragment such as:
  *
  * JSObject *obj = NewObject(cx);
  * DoSomething(cx);
  * ... = obj->lastProperty();
  *
  * If |DoSomething()| can trigger a GC, the stack location of |obj| must be
@@ -68,42 +68,37 @@
  *   generated, which is quicker and easier to fix than when relying on a
  *   separate rooting analysis.
  *
  * - MutableHandle<T> is a non-const reference to Rooted<T>. It is used in the
  *   same way as Handle<T> and includes a |set(const T &v)| method to allow
  *   updating the value of the referenced Rooted<T>. A MutableHandle<T> can be
  *   created from a Rooted<T> by using |Rooted<T>::operator&()|.
  *
- * In some cases the small performance overhead of exact rooting is too much.
- * In these cases, try the following:
+ * In some cases the small performance overhead of exact rooting (measured to
+ * be a few nanoseconds on desktop) is too much. In these cases, try the
+ * following:
  *
  * - Move all Rooted<T> above inner loops: this allows you to re-use the root
  *   on each iteration of the loop.
  *
  * - Pass Handle<T> through your hot call stack to avoid re-rooting costs at
  *   every invocation.
  *
- * There also exists a set of RawT typedefs for modules without rooting
- * concerns, such as the GC. Do not use these as they provide no rooting
- * protection whatsoever.
- *
  * The following diagram explains the list of supported, implicit type
  * conversions between classes of this family:
  *
  *  Rooted<T> ----> Handle<T>
  *     |               ^
  *     |               |
  *     |               |
  *     +---> MutableHandle<T>
  *     (via &)
  *
- * Currently, all of these types have an implicit conversion to RawT. These are
- * present only for the purpose of bootstrapping exact rooting and will be
- * removed in the future (Bug 817164).
+ * All of these types have an implicit conversion to raw pointers.
  */
 
 namespace js {
 
 class Module;
 
 template <typename T>
 struct RootMethods {};
--- a/js/src/ion/BaselineInspector.cpp
+++ b/js/src/ion/BaselineInspector.cpp
@@ -123,24 +123,97 @@ BaselineInspector::expectedResultType(js
 // Whether a baseline stub kind is suitable for a double comparison that
 // converts its operands to doubles.
 static bool
 CanUseDoubleCompare(ICStub::Kind kind)
 {
     return kind == ICStub::Compare_Double || kind == ICStub::Compare_NumberWithUndefined;
 }
 
+// Whether a baseline stub kind is suitable for an int32 comparison that
+// converts its operands to int32.
+static bool
+CanUseInt32Compare(ICStub::Kind kind)
+{
+    return kind == ICStub::Compare_Int32 || kind == ICStub::Compare_Int32WithBoolean;
+}
+
 MCompare::CompareType
 BaselineInspector::expectedCompareType(jsbytecode *pc)
 {
     ICStub::Kind kind = monomorphicStubKind(pc);
 
+    if (CanUseInt32Compare(kind))
+        return MCompare::Compare_Int32;
     if (CanUseDoubleCompare(kind))
         return MCompare::Compare_Double;
 
     ICStub::Kind first, second;
     if (dimorphicStubKind(pc, &first, &second)) {
+        if (CanUseInt32Compare(first) && CanUseInt32Compare(second))
+            return MCompare::Compare_Int32;
         if (CanUseDoubleCompare(first) && CanUseDoubleCompare(second))
             return MCompare::Compare_Double;
     }
 
     return MCompare::Compare_Unknown;
 }
+
+static bool
+TryToSpecializeBinaryArithOp(ICStub::Kind *kinds,
+                             uint32_t nkinds,
+                             MIRType *result)
+{
+    bool sawInt32 = false;
+    bool sawDouble = false;
+    bool sawOther = false;
+
+    for (uint32_t i = 0; i < nkinds; i++) {
+        switch (kinds[i]) {
+          case ICStub::BinaryArith_Int32:
+            sawInt32 = true;
+            break;
+          case ICStub::BinaryArith_BooleanWithInt32:
+            sawInt32 = true;
+            break;
+          case ICStub::BinaryArith_Double:
+            sawDouble = true;
+            break;
+          case ICStub::BinaryArith_DoubleWithInt32:
+            sawDouble = true;
+            break;
+          default:
+            sawOther = true;
+            break;
+        }
+    }
+
+    if (sawOther)
+        return false;
+
+    if (sawDouble) {
+        *result = MIRType_Double;
+        return true;
+    }
+
+    JS_ASSERT(sawInt32);
+    *result = MIRType_Int32;
+    return true;
+}
+
+MIRType
+BaselineInspector::expectedBinaryArithSpecialization(jsbytecode *pc)
+{
+    MIRType result;
+    ICStub::Kind kinds[2];
+
+    kinds[0] = monomorphicStubKind(pc);
+    if (TryToSpecializeBinaryArithOp(kinds, 1, &result))
+        return result;
+
+    if (dimorphicStubKind(pc, &kinds[0], &kinds[1])) {
+        if (TryToSpecializeBinaryArithOp(kinds, 2, &result))
+            return result;
+    }
+
+    return MIRType_None;
+}
+
--- a/js/src/ion/BaselineInspector.h
+++ b/js/src/ion/BaselineInspector.h
@@ -96,15 +96,16 @@ class BaselineInspector
     RawShape maybeMonomorphicShapeForPropertyOp(jsbytecode *pc);
 
     SetElemICInspector setElemICInspector(jsbytecode *pc) {
         return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback);
     }
 
     MIRType expectedResultType(jsbytecode *pc);
     MCompare::CompareType expectedCompareType(jsbytecode *pc);
+    MIRType expectedBinaryArithSpecialization(jsbytecode *pc);
 };
 
 } // namespace ion
 } // namespace js
 
 #endif
 
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -2172,18 +2172,22 @@ struct ScriptCountBlockState
 
     void visitInstruction(LInstruction *ins)
     {
         if (last)
             *last += masm.size() - lastLength;
         lastLength = masm.size();
         last = ins->isMoveGroup() ? &spillBytes : &instructionBytes;
 
-        // Prefix stream of assembly instructions with their LIR instruction name.
-        printer.printf("[%s]\n", ins->opName());
+        // Prefix stream of assembly instructions with their LIR instruction
+        // name and any associated high level info.
+        if (const char *extra = ins->extraName())
+            printer.printf("[%s:%s]\n", ins->opName(), extra);
+        else
+            printer.printf("[%s]\n", ins->opName());
     }
 
     ~ScriptCountBlockState()
     {
         masm.setPrinter(NULL);
 
         if (last)
             *last += masm.size() - lastLength;
@@ -3171,62 +3175,54 @@ CodeGenerator::visitBinaryV(LBinaryV *li
         return callVM(UrshInfo, lir);
 
       default:
         JS_NOT_REACHED("Unexpected binary op");
         return false;
     }
 }
 
-bool
-CodeGenerator::visitParCompareS(LParCompareS *lir)
-{
-    JSOp op = lir->mir()->jsop();
-    Register left = ToRegister(lir->left());
-    Register right = ToRegister(lir->right());
-
-    JS_ASSERT((op == JSOP_EQ || op == JSOP_STRICTEQ) ||
-              (op == JSOP_NE || op == JSOP_STRICTNE));
-
-    masm.setupUnalignedABICall(2, CallTempReg2);
-    masm.passABIArg(left);
-    masm.passABIArg(right);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCompareStrings));
-    masm.and32(Imm32(0xF), ReturnReg); // The C functions return an enum whose size is undef
-
-    // Check for cases that we do not currently handle in par exec
-    Label *bail;
-    if (!ensureOutOfLineParallelAbort(&bail))
-        return false;
-    masm.branch32(Assembler::Equal, ReturnReg, Imm32(ParCompareUnknown), bail);
-
-    if (op == JSOP_NE || op == JSOP_STRICTNE)
-        masm.xor32(Imm32(1), ReturnReg);
-
-    return true;
-}
-
 typedef bool (*StringCompareFn)(JSContext *, HandleString, HandleString, JSBool *);
 static const VMFunction stringsEqualInfo =
     FunctionInfo<StringCompareFn>(ion::StringsEqual<true>);
 static const VMFunction stringsNotEqualInfo =
     FunctionInfo<StringCompareFn>(ion::StringsEqual<false>);
 
+typedef ParallelResult (*ParStringCompareFn)(ForkJoinSlice *, HandleString, HandleString, JSBool *);
+static const VMFunction parStringsEqualInfo =
+    FunctionInfo<ParStringCompareFn>(ion::ParStringsEqual);
+static const VMFunction parStringsNotEqualInfo =
+    FunctionInfo<ParStringCompareFn>(ion::ParStringsUnequal);
+
 bool
 CodeGenerator::emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
                             Register output, Register temp)
 {
     JS_ASSERT(lir->isCompareS() || lir->isCompareStrictS());
 
     OutOfLineCode *ool = NULL;
-    if (op == JSOP_EQ || op == JSOP_STRICTEQ) {
-        ool = oolCallVM(stringsEqualInfo, lir, (ArgList(), left, right),  StoreRegisterTo(output));
-    } else {
-        JS_ASSERT(op == JSOP_NE || op == JSOP_STRICTNE);
-        ool = oolCallVM(stringsNotEqualInfo, lir, (ArgList(), left, right), StoreRegisterTo(output));
+
+    switch (gen->info().executionMode()) {
+      case SequentialExecution:
+        if (op == JSOP_EQ || op == JSOP_STRICTEQ) {
+            ool = oolCallVM(stringsEqualInfo, lir, (ArgList(), left, right),  StoreRegisterTo(output));
+        } else {
+            JS_ASSERT(op == JSOP_NE || op == JSOP_STRICTNE);
+            ool = oolCallVM(stringsNotEqualInfo, lir, (ArgList(), left, right), StoreRegisterTo(output));
+        }
+        break;
+
+      case ParallelExecution:
+        if (op == JSOP_EQ || op == JSOP_STRICTEQ) {
+            ool = oolCallVM(parStringsEqualInfo, lir, (ArgList(), left, right),  StoreRegisterTo(output));
+        } else {
+            JS_ASSERT(op == JSOP_NE || op == JSOP_STRICTNE);
+            ool = oolCallVM(parStringsNotEqualInfo, lir, (ArgList(), left, right), StoreRegisterTo(output));
+        }
+        break;
     }
 
     if (!ool)
         return false;
 
     masm.compareStrings(op, left, right, output, temp, ool->entry());
 
     masm.bind(ool->rejoin());
@@ -3277,51 +3273,97 @@ static const VMFunction EqInfo = Functio
 static const VMFunction NeInfo = FunctionInfo<CompareFn>(ion::LooselyEqual<false>);
 static const VMFunction StrictEqInfo = FunctionInfo<CompareFn>(ion::StrictlyEqual<true>);
 static const VMFunction StrictNeInfo = FunctionInfo<CompareFn>(ion::StrictlyEqual<false>);
 static const VMFunction LtInfo = FunctionInfo<CompareFn>(ion::LessThan);
 static const VMFunction LeInfo = FunctionInfo<CompareFn>(ion::LessThanOrEqual);
 static const VMFunction GtInfo = FunctionInfo<CompareFn>(ion::GreaterThan);
 static const VMFunction GeInfo = FunctionInfo<CompareFn>(ion::GreaterThanOrEqual);
 
+typedef ParallelResult (*ParCompareFn)(ForkJoinSlice *, MutableHandleValue, MutableHandleValue, JSBool *);
+static const VMFunction ParLooselyEqInfo = FunctionInfo<ParCompareFn>(ion::ParLooselyEqual);
+static const VMFunction ParStrictlyEqInfo = FunctionInfo<ParCompareFn>(ion::ParStrictlyEqual);
+static const VMFunction ParLooselyNeInfo = FunctionInfo<ParCompareFn>(ion::ParLooselyUnequal);
+static const VMFunction ParStrictlyNeInfo = FunctionInfo<ParCompareFn>(ion::ParStrictlyUnequal);
+static const VMFunction ParLtInfo = FunctionInfo<ParCompareFn>(ion::ParLessThan);
+static const VMFunction ParLeInfo = FunctionInfo<ParCompareFn>(ion::ParLessThanOrEqual);
+static const VMFunction ParGtInfo = FunctionInfo<ParCompareFn>(ion::ParGreaterThan);
+static const VMFunction ParGeInfo = FunctionInfo<ParCompareFn>(ion::ParGreaterThanOrEqual);
+
 bool
 CodeGenerator::visitCompareVM(LCompareVM *lir)
 {
     pushArg(ToValue(lir, LBinaryV::RhsInput));
     pushArg(ToValue(lir, LBinaryV::LhsInput));
 
-    switch (lir->mir()->jsop()) {
-      case JSOP_EQ:
-        return callVM(EqInfo, lir);
-
-      case JSOP_NE:
-        return callVM(NeInfo, lir);
-
-      case JSOP_STRICTEQ:
-        return callVM(StrictEqInfo, lir);
-
-      case JSOP_STRICTNE:
-        return callVM(StrictNeInfo, lir);
-
-      case JSOP_LT:
-        return callVM(LtInfo, lir);
-
-      case JSOP_LE:
-        return callVM(LeInfo, lir);
-
-      case JSOP_GT:
-        return callVM(GtInfo, lir);
-
-      case JSOP_GE:
-        return callVM(GeInfo, lir);
-
-      default:
-        JS_NOT_REACHED("Unexpected compare op");
-        return false;
+    switch (gen->info().executionMode()) {
+      case SequentialExecution:
+        switch (lir->mir()->jsop()) {
+          case JSOP_EQ:
+            return callVM(EqInfo, lir);
+
+          case JSOP_NE:
+            return callVM(NeInfo, lir);
+
+          case JSOP_STRICTEQ:
+            return callVM(StrictEqInfo, lir);
+
+          case JSOP_STRICTNE:
+            return callVM(StrictNeInfo, lir);
+
+          case JSOP_LT:
+            return callVM(LtInfo, lir);
+
+          case JSOP_LE:
+            return callVM(LeInfo, lir);
+
+          case JSOP_GT:
+            return callVM(GtInfo, lir);
+
+          case JSOP_GE:
+            return callVM(GeInfo, lir);
+
+          default:
+            JS_NOT_REACHED("Unexpected compare op");
+            return false;
+        }
+
+      case ParallelExecution:
+        switch (lir->mir()->jsop()) {
+          case JSOP_EQ:
+            return callVM(ParLooselyEqInfo, lir);
+
+          case JSOP_STRICTEQ:
+            return callVM(ParStrictlyEqInfo, lir);
+
+          case JSOP_NE:
+            return callVM(ParLooselyNeInfo, lir);
+
+          case JSOP_STRICTNE:
+            return callVM(ParStrictlyNeInfo, lir);
+
+          case JSOP_LT:
+            return callVM(ParLtInfo, lir);
+
+          case JSOP_LE:
+            return callVM(ParLeInfo, lir);
+
+          case JSOP_GT:
+            return callVM(ParGtInfo, lir);
+
+          case JSOP_GE:
+            return callVM(ParGeInfo, lir);
+
+          default:
+            JS_NOT_REACHED("Unexpected compare op");
+            return false;
+        }
     }
+
+    JS_NOT_REACHED("Unexpected exec mode");
 }
 
 bool
 CodeGenerator::visitIsNullOrLikeUndefined(LIsNullOrLikeUndefined *lir)
 {
     JSOp op = lir->mir()->jsop();
     MCompare::CompareType compareType = lir->mir()->compareType();
     JS_ASSERT(compareType == MCompare::Compare_Undefined ||
@@ -3543,62 +3585,162 @@ CodeGenerator::visitEmulatesUndefinedAnd
 
 bool
 CodeGenerator::visitConcat(LConcat *lir)
 {
     Register lhs = ToRegister(lir->lhs());
     Register rhs = ToRegister(lir->rhs());
 
     Register output = ToRegister(lir->output());
-    Register temp = ToRegister(lir->temp());
+
+    JS_ASSERT(lhs == CallTempReg0);
+    JS_ASSERT(rhs == CallTempReg1);
+    JS_ASSERT(ToRegister(lir->temp1()) == CallTempReg2);
+    JS_ASSERT(ToRegister(lir->temp2()) == CallTempReg3);
+    JS_ASSERT(ToRegister(lir->temp3()) == CallTempReg4);
+    JS_ASSERT(ToRegister(lir->temp4()) == CallTempReg5);
+    JS_ASSERT(output == CallTempReg6);
 
     OutOfLineCode *ool = oolCallVM(ConcatStringsInfo, lir, (ArgList(), lhs, rhs),
                                    StoreRegisterTo(output));
     if (!ool)
         return false;
 
-    Label done;
+    IonCode *stringConcatStub = gen->ionCompartment()->stringConcatStub();
+    masm.call(stringConcatStub);
+    masm.branchTestPtr(Assembler::Zero, output, output, ool->entry());
+
+    masm.bind(ool->rejoin());
+    return true;
+}
+
+static void
+CopyStringChars(MacroAssembler &masm, Register to, Register from, Register len, Register scratch)
+{
+    // Copy |len| jschars from |from| to |to|. Assumes len > 0 (checked below in
+    // debug builds), and when done |to| must point to the next available char.
+
+#ifdef DEBUG
+    Label ok;
+    masm.branch32(Assembler::GreaterThan, len, Imm32(0), &ok);
+    masm.breakpoint();
+    masm.bind(&ok);
+#endif
+
+    JS_STATIC_ASSERT(sizeof(jschar) == 2);
+
+    Label start;
+    masm.bind(&start);
+    masm.load16ZeroExtend(Address(from, 0), scratch);
+    masm.store16(scratch, Address(to, 0));
+    masm.addPtr(Imm32(2), from);
+    masm.addPtr(Imm32(2), to);
+    masm.sub32(Imm32(1), len);
+    masm.j(Assembler::NonZero, &start);
+}
+
+IonCode *
+IonCompartment::generateStringConcatStub(JSContext *cx)
+{
+    MacroAssembler masm(cx);
+
+    Register lhs = CallTempReg0;
+    Register rhs = CallTempReg1;
+    Register temp1 = CallTempReg2;
+    Register temp2 = CallTempReg3;
+    Register temp3 = CallTempReg4;
+    Register temp4 = CallTempReg5;
+    Register output = CallTempReg6;
+
+    Label failure;
 
     // If lhs is empty, return rhs.
     Label leftEmpty;
-    masm.loadStringLength(lhs, temp);
-    masm.branchTest32(Assembler::Zero, temp, temp, &leftEmpty);
+    masm.loadStringLength(lhs, temp1);
+    masm.branchTest32(Assembler::Zero, temp1, temp1, &leftEmpty);
 
     // If rhs is empty, return lhs.
     Label rightEmpty;
-    masm.loadStringLength(rhs, output);
-    masm.branchTest32(Assembler::Zero, output, output, &rightEmpty);
-
-    // Ensure total length <= JSString::MAX_LENGTH.
-    masm.add32(output, temp);
-    masm.branch32(Assembler::Above, temp, Imm32(JSString::MAX_LENGTH), ool->entry());
+    masm.loadStringLength(rhs, temp2);
+    masm.branchTest32(Assembler::Zero, temp2, temp2, &rightEmpty);
+
+    masm.add32(temp1, temp2);
+
+    // Check if we can use a JSShortString.
+    Label isShort;
+    masm.branch32(Assembler::BelowOrEqual, temp2, Imm32(JSShortString::MAX_SHORT_LENGTH),
+                  &isShort);
+
+    // Ensure result length <= JSString::MAX_LENGTH.
+    masm.branch32(Assembler::Above, temp1, Imm32(JSString::MAX_LENGTH), &failure);
 
     // Allocate a new rope.
-    masm.newGCString(output, ool->entry());
+    masm.newGCString(output, &failure);
 
     // Store lengthAndFlags.
     JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
-    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp);
-    masm.storePtr(temp, Address(output, JSString::offsetOfLengthAndFlags()));
+    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
+    masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
 
     // Store left and right nodes.
     masm.storePtr(lhs, Address(output, JSRope::offsetOfLeft()));
     masm.storePtr(rhs, Address(output, JSRope::offsetOfRight()));
-    masm.jump(&done);
+    masm.ret();
 
     masm.bind(&leftEmpty);
     masm.mov(rhs, output);
-    masm.jump(&done);
+    masm.ret();
 
     masm.bind(&rightEmpty);
     masm.mov(lhs, output);
-
-    masm.bind(&done);
-    masm.bind(ool->rejoin());
-    return true;
+    masm.ret();
+
+    masm.bind(&isShort);
+
+    // State: lhs length in temp1, result length in temp2.
+
+    // Ensure both strings are linear (flags != 0).
+    JS_STATIC_ASSERT(JSString::ROPE_FLAGS == 0);
+    masm.branchTestPtr(Assembler::Zero, Address(lhs, JSString::offsetOfLengthAndFlags()),
+                       Imm32(JSString::FLAGS_MASK), &failure);
+    masm.branchTestPtr(Assembler::Zero, Address(rhs, JSString::offsetOfLengthAndFlags()),
+                       Imm32(JSString::FLAGS_MASK), &failure);
+
+    // Allocate a JSShortString.
+    masm.newGCShortString(output, &failure);
+
+    // Set lengthAndFlags.
+    masm.lshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp2);
+    masm.orPtr(Imm32(JSString::FIXED_FLAGS), temp2);
+    masm.storePtr(temp2, Address(output, JSString::offsetOfLengthAndFlags()));
+
+    // Set chars pointer, keep in temp2 for copy loop below.
+    masm.computeEffectiveAddress(Address(output, JSShortString::offsetOfInlineStorage()), temp2);
+    masm.storePtr(temp2, Address(output, JSShortString::offsetOfChars()));
+
+    // Copy lhs chars. Temp1 still holds the lhs length. Note that this
+    // advances temp2 to point to the next char.
+    masm.loadPtr(Address(lhs, JSString::offsetOfChars()), temp3);
+    CopyStringChars(masm, temp2, temp3, temp1, temp4);
+
+    // Copy rhs chars.
+    masm.loadPtr(Address(rhs, JSString::offsetOfChars()), temp3);
+    masm.loadStringLength(rhs, temp1);
+    CopyStringChars(masm, temp2, temp3, temp1, temp4);
+
+    // Null-terminate.
+    masm.store16(Imm32(0), Address(temp2, 0));
+    masm.ret();
+
+    masm.bind(&failure);
+    masm.movePtr(ImmWord((void *)NULL), output);
+    masm.ret();
+
+    Linker linker(masm);
+    return linker.newCode(cx, JSC::OTHER_CODE);
 }
 
 typedef bool (*CharCodeAtFn)(JSContext *, HandleString, int32_t, uint32_t *);
 static const VMFunction CharCodeAtInfo = FunctionInfo<CharCodeAtFn>(ion::CharCodeAt);
 
 bool
 CodeGenerator::visitCharCodeAt(LCharCodeAt *lir)
 {
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -148,17 +148,16 @@ class CodeGenerator : public CodeGenerat
     bool visitMathFunctionD(LMathFunctionD *ins);
     bool visitModD(LModD *ins);
     bool visitMinMaxI(LMinMaxI *lir);
     bool visitBinaryV(LBinaryV *lir);
     bool emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
                       Register output, Register temp);
     bool visitCompareS(LCompareS *lir);
     bool visitCompareStrictS(LCompareStrictS *lir);
-    bool visitParCompareS(LParCompareS *lir);
     bool visitCompareVM(LCompareVM *lir);
     bool visitIsNullOrLikeUndefined(LIsNullOrLikeUndefined *lir);
     bool visitIsNullOrLikeUndefinedAndBranch(LIsNullOrLikeUndefinedAndBranch *lir);
     bool visitEmulatesUndefined(LEmulatesUndefined *lir);
     bool visitEmulatesUndefinedAndBranch(LEmulatesUndefinedAndBranch *lir);
     bool visitConcat(LConcat *lir);
     bool visitCharCodeAt(LCharCodeAt *lir);
     bool visitFromCharCode(LFromCharCode *lir);
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -178,26 +178,26 @@ IonRuntime::~IonRuntime()
     freeOsrTempData();
 }
 
 bool
 IonRuntime::initialize(JSContext *cx)
 {
     AutoEnterAtomsCompartment ac(cx);
 
-    if (!cx->compartment->ensureIonCompartmentExists(cx))
-        return false;
-
     IonContext ictx(cx, NULL);
     AutoFlushCache afc("IonRuntime::initialize");
 
     execAlloc_ = cx->runtime->getExecAlloc(cx);
     if (!execAlloc_)
         return false;
 
+    if (!cx->compartment->ensureIonCompartmentExists(cx))
+        return false;
+
     functionWrappers_ = cx->new_<VMWrapperMap>(cx);
     if (!functionWrappers_ || !functionWrappers_->init())
         return false;
 
     if (cx->runtime->jitSupportsFloatingPoint) {
         // Initialize some Ion-only stubs that require floating-point support.
         if (!bailoutTables_.reserve(FrameSizeClass::ClassLimit().classId()))
             return false;
@@ -279,32 +279,46 @@ IonRuntime::freeOsrTempData()
 {
     js_free(osrTempData_);
     osrTempData_ = NULL;
 }
 
 IonCompartment::IonCompartment(IonRuntime *rt)
   : rt(rt),
     stubCodes_(NULL),
-    baselineCallReturnAddr_(NULL)
+    baselineCallReturnAddr_(NULL),
+    stringConcatStub_(NULL)
 {
 }
 
 IonCompartment::~IonCompartment()
 {
     if (stubCodes_)
         js_delete(stubCodes_);
 }
 
 bool
 IonCompartment::initialize(JSContext *cx)
 {
     stubCodes_ = cx->new_<ICStubCodeMap>(cx);
     if (!stubCodes_ || !stubCodes_->init())
         return false;
+
+    return true;
+}
+
+bool
+IonCompartment::ensureIonStubsExist(JSContext *cx)
+{
+    if (!stringConcatStub_) {
+        stringConcatStub_ = generateStringConcatStub(cx);
+        if (!stringConcatStub_)
+            return false;
+    }
+
     return true;
 }
 
 void
 ion::FinishOffThreadBuilder(IonBuilder *builder)
 {
     JS_ASSERT(builder->info().executionMode() == SequentialExecution);
 
@@ -359,16 +373,19 @@ IonCompartment::mark(JSTracer *trc, JSCo
 void
 IonCompartment::sweep(FreeOp *fop)
 {
     stubCodes_->sweep(fop);
 
     // If the sweep removed the ICCall_Fallback stub, NULL the baselineCallReturnAddr_ field.
     if (!stubCodes_->lookup(static_cast<uint32_t>(ICStub::Call_Fallback)))
         baselineCallReturnAddr_ = NULL;
+
+    if (stringConcatStub_ && !IsIonCodeMarked(stringConcatStub_.unsafeGet()))
+        stringConcatStub_ = NULL;
 }
 
 IonCode *
 IonCompartment::getBailoutTable(const FrameSizeClass &frameClass)
 {
     JS_ASSERT(frameClass != FrameSizeClass::None());
     return rt->bailoutTables_[frameClass.classId()];
 }
@@ -1318,16 +1335,19 @@ IonCompile(JSContext *cx, JSScript *scri
 
     IonContext ictx(cx, temp);
 
     types::AutoEnterAnalysis enter(cx);
 
     if (!cx->compartment->ensureIonCompartmentExists(cx))
         return AbortReason_Alloc;
 
+    if (!cx->compartment->ionCompartment()->ensureIonStubsExist(cx))
+        return AbortReason_Alloc;
+
     MIRGraph *graph = alloc->new_<MIRGraph>(temp);
     ExecutionMode executionMode = compileContext.executionMode();
     CompileInfo *info = alloc->new_<CompileInfo>(script, script->function(), osrPc, constructing,
                                                  executionMode);
     if (!info)
         return AbortReason_Alloc;
 
     BaselineInspector inspector(cx, script);
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -3254,17 +3254,17 @@ IonBuilder::jsop_binary(JSOp op, MDefini
       default:
         JS_NOT_REACHED("unexpected binary opcode");
         return false;
     }
 
     bool overflowed = types::HasOperationOverflowed(script(), pc);
 
     current->add(ins);
-    ins->infer(overflowed);
+    ins->infer(inspector, pc, overflowed);
     current->push(ins);
 
     if (ins->isEffectful())
         return resumeAfter(ins);
     return maybeInsertResume();
 }
 
 bool
@@ -5851,34 +5851,31 @@ IonBuilder::jsop_getgname(HandleProperty
     current->push(load);
     return pushTypeBarrier(load, types, barrier);
 }
 
 // Whether 'types' includes all possible values represented by input/inputTypes.
 bool
 ion::TypeSetIncludes(types::TypeSet *types, MIRType input, types::TypeSet *inputTypes)
 {
-    if (inputTypes)
-        return inputTypes->isSubset(types);
-
     switch (input) {
       case MIRType_Undefined:
       case MIRType_Null:
       case MIRType_Boolean:
       case MIRType_Int32:
       case MIRType_Double:
       case MIRType_String:
       case MIRType_Magic:
         return types->hasType(types::Type::PrimitiveType(ValueTypeFromMIRType(input)));
 
       case MIRType_Object:
-        return types->unknownObject();
+        return types->unknownObject() || (inputTypes && inputTypes->isSubset(types));
 
       case MIRType_Value:
-        return types->unknown();
+        return types->unknown() || (inputTypes && inputTypes->isSubset(types));
 
       default:
         JS_NOT_REACHED("Bad input type");
         return false;
     }
 }
 
 bool
@@ -6229,19 +6226,103 @@ IonBuilder::getTypedArrayElements(MDefin
         types::HeapTypeSet::WatchObjectStateChange(cx, array->getType(cx));
 
         obj->setFoldedUnchecked();
         return MConstantElements::New(data);
     }
     return MTypedArrayElements::New(obj);
 }
 
+MDefinition *
+IonBuilder::convertShiftToMaskForStaticTypedArray(MDefinition *id,
+                                                  ArrayBufferView::ViewType viewType)
+{
+    if (!id->isRsh() || id->isEffectful())
+        return NULL;
+    if (!id->getOperand(1)->isConstant())
+        return NULL;
+    const Value &value = id->getOperand(1)->toConstant()->value();
+    if (!value.isInt32() || uint32_t(value.toInt32()) != TypedArrayShift(viewType))
+        return NULL;
+
+    // Instead of shifting, mask off the low bits of the index so that
+    // a non-scaled access on the typed array can be performed.
+    MConstant *mask = MConstant::New(Int32Value(~((1 << value.toInt32()) - 1)));
+    MBitAnd *ptr = MBitAnd::New(id->getOperand(0), mask);
+
+    ptr->infer();
+    JS_ASSERT(!ptr->isEffectful());
+
+    current->add(mask);
+    current->add(ptr);
+
+    return ptr;
+}
+
+bool
+IonBuilder::jsop_getelem_typed_static(bool *psucceeded)
+{
+    if (!LIRGenerator::allowStaticTypedArrayAccesses())
+        return true;
+
+    MDefinition *id = current->peek(-1);
+    MDefinition *obj = current->peek(-2);
+
+    if (ElementAccessHasExtraIndexedProperty(cx, obj))
+        return true;
+
+    if (!obj->resultTypeSet())
+        return true;
+    JSObject *typedArray = obj->resultTypeSet()->getSingleton();
+    if (!typedArray)
+        return true;
+    JS_ASSERT(typedArray->isTypedArray());
+
+    ArrayBufferView::ViewType viewType = JS_GetArrayBufferViewType(typedArray);
+
+    MDefinition *ptr = convertShiftToMaskForStaticTypedArray(id, viewType);
+    if (!ptr)
+        return true;
+
+    obj->setFolded();
+
+    MLoadTypedArrayElementStatic *load = MLoadTypedArrayElementStatic::New(typedArray, ptr);
+    current->add(load);
+
+    // The load is infallible if an undefined result will be coerced to the
+    // appropriate numeric type if the read is out of bounds. The truncation
+    // analysis picks up some of these cases, but is incomplete with respect
+    // to others. For now, sniff the bytecode for simple patterns following
+    // the load which guarantee a truncation or numeric conversion.
+    if (viewType == ArrayBufferView::TYPE_FLOAT32 || viewType == ArrayBufferView::TYPE_FLOAT64) {
+        jsbytecode *next = pc + JSOP_GETELEM_LENGTH;
+        if (*next == JSOP_POS)
+            load->setInfallible();
+    } else {
+        jsbytecode *next = pc + JSOP_GETELEM_LENGTH;
+        if (*next == JSOP_ZERO && *(next + JSOP_ZERO_LENGTH) == JSOP_BITOR)
+            load->setInfallible();
+    }
+
+    current->popn(2);
+    current->push(load);
+
+    *psucceeded = true;
+    return true;
+}
+
 bool
 IonBuilder::jsop_getelem_typed(int arrayType)
 {
+    bool staticAccess = false;
+    if (!jsop_getelem_typed_static(&staticAccess))
+        return false;
+    if (staticAccess)
+        return true;
+
     types::StackTypeSet *types = script()->analysis()->bytecodeTypes(pc);
 
     MDefinition *id = current->pop();
     MDefinition *obj = current->pop();
 
     bool maybeUndefined = types->hasType(types::Type::UndefinedType());
 
     // Reading from an Uint32Array will result in a double for values
@@ -6446,18 +6527,68 @@ IonBuilder::jsop_setelem_dense(types::St
 
     if (elementType != MIRType_None && packed)
         store->setElementType(elementType);
 
     return true;
 }
 
 bool
+IonBuilder::jsop_setelem_typed_static(bool *psucceeded)
+{
+    if (!LIRGenerator::allowStaticTypedArrayAccesses())
+        return true;
+
+    MDefinition *value = current->peek(-1);
+    MDefinition *id = current->peek(-2);
+    MDefinition *obj = current->peek(-3);
+
+    if (ElementAccessHasExtraIndexedProperty(cx, obj))
+        return true;
+
+    if (!obj->resultTypeSet())
+        return true;
+    JSObject *typedArray = obj->resultTypeSet()->getSingleton();
+    if (!typedArray)
+        return true;
+    JS_ASSERT(typedArray->isTypedArray());
+
+    ArrayBufferView::ViewType viewType = JS_GetArrayBufferViewType(typedArray);
+
+    MDefinition *ptr = convertShiftToMaskForStaticTypedArray(id, viewType);
+    if (!ptr)
+        return true;
+
+    obj->setFolded();
+
+    // Clamp value to [0, 255] for Uint8ClampedArray.
+    MDefinition *toWrite = value;
+    if (viewType == ArrayBufferView::TYPE_UINT8_CLAMPED) {
+        toWrite = MClampToUint8::New(value);
+        current->add(toWrite->toInstruction());
+    }
+
+    MInstruction *store = MStoreTypedArrayElementStatic::New(typedArray, ptr, toWrite);
+    current->add(store);
+    current->popn(3);
+    current->push(value);
+
+    *psucceeded = true;
+    return resumeAfter(store);
+}
+
+bool
 IonBuilder::jsop_setelem_typed(int arrayType)
 {
+    bool staticAccess = false;
+    if (!jsop_setelem_typed_static(&staticAccess))
+        return false;
+    if (staticAccess)
+        return true;
+
     MDefinition *value = current->pop();
     MDefinition *id = current->pop();
     MDefinition *obj = current->pop();
 
     SetElemICInspector icInspect(inspector->setElemICInspector(pc));
     bool expectOOB = icInspect.sawOOBTypedArrayWrite();
     if (expectOOB)
         spew("Emitting OOB TypedArray SetElem");
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -319,16 +319,18 @@ class IonBuilder : public MIRGenerator
 
     MDefinition *walkScopeChain(unsigned hops);
 
     MInstruction *addConvertElementsToDoubles(MDefinition *elements);
     MInstruction *addBoundsCheck(MDefinition *index, MDefinition *length);
     MInstruction *addShapeGuard(MDefinition *obj, const RawShape shape, BailoutKind bailoutKind);
 
     JSObject *getNewArrayTemplateObject(uint32_t count);
+    MDefinition *convertShiftToMaskForStaticTypedArray(MDefinition *id,
+                                                       ArrayBufferView::ViewType viewType);
 
     bool invalidatedIdempotentCache();
 
     bool loadSlot(MDefinition *obj, HandleShape shape, MIRType rvalType,
                   bool barrier, types::StackTypeSet *types);
     bool storeSlot(MDefinition *obj, RawShape shape, MDefinition *value, bool needsBarrier);
 
     // jsop_getprop() helpers.
@@ -372,20 +374,22 @@ class IonBuilder : public MIRGenerator
     bool jsop_getgname(HandlePropertyName name);
     bool jsop_setgname(HandlePropertyName name);
     bool jsop_getname(HandlePropertyName name);
     bool jsop_intrinsic(HandlePropertyName name);
     bool jsop_bindname(PropertyName *name);
     bool jsop_getelem();
     bool jsop_getelem_dense();
     bool jsop_getelem_typed(int arrayType);
+    bool jsop_getelem_typed_static(bool *psucceeded);
     bool jsop_getelem_string();
     bool jsop_setelem();
     bool jsop_setelem_dense(types::StackTypeSet::DoubleConversion conversion);
     bool jsop_setelem_typed(int arrayType);
+    bool jsop_setelem_typed_static(bool *psucceeded);
     bool jsop_length();
     bool jsop_length_fastPath();
     bool jsop_arguments();
     bool jsop_arguments_length();
     bool jsop_arguments_getelem();
     bool jsop_arguments_setelem();
     bool jsop_not();
     bool jsop_getprop(HandlePropertyName name);
--- a/js/src/ion/IonCaches.cpp
+++ b/js/src/ion/IonCaches.cpp
@@ -393,24 +393,18 @@ IonCache::linkAndAttachStub(JSContext *c
 {
     IonCode *code = NULL;
     LinkStatus status = linkCode(cx, masm, ion, &code);
     if (status != LINK_GOOD)
         return status != LINK_ERROR;
 
     attachStub(masm, attacher, code);
 
-    if (pc) {
-        IonSpew(IonSpew_InlineCaches, "Cache %p(%s:%d/%d) generated %s %s stub at %p",
-                this, script->filename(), script->lineno, pc - script->code,
-                attachKind, CacheName(kind()), code->raw());
-    } else {
-        IonSpew(IonSpew_InlineCaches, "Cache %p generated %s %s stub at %p",
-                this, attachKind, CacheName(kind()), code->raw());
-    }
+    IonSpew(IonSpew_InlineCaches, "Generated %s %s stub at %p",
+            attachKind, CacheName(kind()), code->raw());
     return true;
 }
 
 void
 IonCache::updateBaseAddress(IonCode *code, MacroAssembler &masm)
 {
     fallbackLabel_.repoint(code, &masm);
 }
@@ -545,49 +539,16 @@ IsCacheableNoProperty(JSObject *obj, JSO
     // monitor and invalidate the script.
     if (!output.hasValue())
         return false;
 
     return true;
 }
 
 static bool
-IsOptimizableArgumentsObjectForLength(JSObject *obj)
-{
-    if (!obj->isArguments())
-        return false;
-
-    if (obj->asArguments().hasOverriddenLength())
-        return false;
-
-    return true;
-}
-
-static bool
-IsOptimizableArgumentsObjectForGetElem(JSObject *obj, Value idval)
-{
-    if (!IsOptimizableArgumentsObjectForLength(obj))
-        return false;
-
-    ArgumentsObject &argsObj = obj->asArguments();
-
-    if (argsObj.isAnyElementDeleted())
-        return false;
-
-    if (!idval.isInt32())
-        return false;
-
-    int32_t idint = idval.toInt32();
-    if (idint < 0 || static_cast<uint32_t>(idint) >= argsObj.initialLength())
-        return false;
-
-    return true;
-}
-
-static bool
 IsCacheableGetPropCallNative(JSObject *obj, JSObject *holder, RawShape shape)
 {
     if (!shape || !IsCacheableProtoChain(obj, holder))
         return false;
 
     if (!shape->hasGetterValue() || !shape->getterValue().isObject())
         return false;
 
@@ -1110,71 +1071,16 @@ GetPropertyIC::attachTypedArrayLength(JS
     masm.bind(&failures);
     attacher.jumpNextStub(masm);
 
     JS_ASSERT(!hasTypedArrayLengthStub_);
     hasTypedArrayLengthStub_ = true;
     return linkAndAttachStub(cx, masm, attacher, ion, "typed array length");
 }
 
-bool
-GetPropertyIC::attachArgumentsLength(JSContext *cx, IonScript *ion, JSObject *obj)
-{
-    JS_ASSERT(obj->isArguments());
-    JS_ASSERT(!idempotent());
-
-    Label failures;
-    MacroAssembler masm(cx);
-    RepatchStubAppender attacher(*this);
-
-    Register tmpReg;
-    if (output().hasValue()) {
-        tmpReg = output().valueReg().scratchReg();
-    } else {
-        JS_ASSERT(output().type() == MIRType_Int32);
-        tmpReg = output().typedReg().gpr();
-    }
-    JS_ASSERT(object() != tmpReg);
-
-    Class *clasp = obj->isStrictArguments() ? &StrictArgumentsObjectClass
-                                            : &NormalArgumentsObjectClass;
-
-    Label fail;
-    Label pass;
-    masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures);
-
-    // Get initial ArgsObj length value, test if length has been overridden.
-    masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
-    masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
-                      &failures);
-
-    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
-
-    // If output is Int32, result is already in right place, otherwise box it into output.
-    if (output().hasValue())
-        masm.tagValue(JSVAL_TYPE_INT32, tmpReg, output().valueReg());
-
-    // Success.
-    attacher.jumpRejoin(masm);
-
-    // Failure.
-    masm.bind(&failures);
-    attacher.jumpNextStub(masm);
-
-    if (obj->isStrictArguments()) {
-        JS_ASSERT(!hasStrictArgumentsLengthStub_);
-        hasStrictArgumentsLengthStub_ = true;
-        return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (strict)");
-    }
-
-    JS_ASSERT(!hasNormalArgumentsLengthStub_);
-    hasNormalArgumentsLengthStub_ = true;
-    return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (normal)");
-}
-
 static bool
 IsIdempotentAndMaybeHasHooks(IonCache &cache, JSObject *obj)
 {
     // If the cache is idempotent, watch out for resolve hooks or non-native
     // objects on the proto chain. We check this before calling lookupProperty,
     // to make sure no effectful lookup hooks or resolve hooks are called.
     return cache.idempotent() && !obj->hasIdempotentProtoChain();
 }
@@ -1235,17 +1141,16 @@ IsIdempotentAndHasSingletonHolder(IonCac
 static bool
 TryAttachNativeGetPropStub(JSContext *cx, IonScript *ion,
                            GetPropertyIC &cache, HandleObject obj,
                            HandlePropertyName name,
                            const SafepointIndex *safepointIndex,
                            void *returnAddr, bool *isCacheable)
 {
     JS_ASSERT(!*isCacheable);
-    JS_ASSERT(cache.canAttachStub());
 
     RootedObject checkObj(cx, obj);
     if (IsCacheableListBase(obj)) {
         Value expandoVal = obj->getFixedSlot(GetListBaseExpandoSlot());
 
         // Expando objects just hold any extra properties the object has been given by a script,
         // and have no prototype or anything else that will complicate property lookups on them.
         JS_ASSERT_IF(expandoVal.isObject(),
@@ -1276,16 +1181,20 @@ TryAttachNativeGetPropStub(JSContext *cx
         return true;
     }
 
     if (IsIdempotentAndHasSingletonHolder(cache, holder, shape))
         return true;
 
     *isCacheable = true;
 
+    // Falback to the interpreter function.
+    if (!cache.canAttachStub())
+        return true;
+
     if (readSlot)
         return cache.attachReadSlot(cx, ion, obj, holder, shape);
     else if (obj->isArray() && !cache.hasArrayLengthStub() && cx->names().length == name)
         return cache.attachArrayLength(cx, ion, obj);
     return cache.attachCallGetter(cx, ion, obj, holder, shape, safepointIndex, returnAddr);
 }
 
 bool
@@ -1307,45 +1216,35 @@ GetPropertyIC::update(JSContext *cx, siz
     // If the cache is idempotent, we will redo the op in the interpreter.
     if (cache.idempotent())
         adi.disable();
 
     // For now, just stop generating new stubs once we hit the stub count
     // limit. Once we can make calls from within generated stubs, a new call
     // stub will be generated instead and the previous stubs unlinked.
     bool isCacheable = false;
-    if (cache.canAttachStub()) {
-        if (name == cx->names().length &&
-            IsOptimizableArgumentsObjectForLength(obj) &&
-            (cache.output().type() == MIRType_Value || cache.output().type() == MIRType_Int32) &&
-            !cache.hasArgumentsLengthStub(obj->isStrictArguments()))
-        {
+    if (!TryAttachNativeGetPropStub(cx, ion, cache, obj, name,
+                                    safepointIndex, returnAddr,
+                                    &isCacheable))
+    {
+        return false;
+    }
+
+    if (!isCacheable && cache.canAttachStub() &&
+        !cache.idempotent() && cx->names().length == name)
+    {
+        if (cache.output().type() != MIRType_Value && cache.output().type() != MIRType_Int32) {
+            // The next execution should cause an invalidation because the type
+            // does not fit.
+            isCacheable = false;
+        } else if (obj->isTypedArray() && !cache.hasTypedArrayLengthStub()) {
             isCacheable = true;
-            if (!cache.attachArgumentsLength(cx, ion, obj))
+            if (!cache.attachTypedArrayLength(cx, ion, obj))
                 return false;
         }
-
-        if (!isCacheable && !TryAttachNativeGetPropStub(cx, ion, cache, obj, name,
-                                                        safepointIndex, returnAddr,
-                                                        &isCacheable))
-        {
-            return false;
-        }
-
-        if (!isCacheable && !cache.idempotent() && cx->names().length == name) {
-            if (cache.output().type() != MIRType_Value && cache.output().type() != MIRType_Int32) {
-                // The next execution should cause an invalidation because the type
-                // does not fit.
-                isCacheable = false;
-            } else if (obj->isTypedArray() && !cache.hasTypedArrayLengthStub()) {
-                isCacheable = true;
-                if (!cache.attachTypedArrayLength(cx, ion, obj))
-                    return false;
-            }
-        }
     }
 
     if (cache.idempotent() && !isCacheable) {
         // Invalidate the cache if the property was not found, or was found on
         // a non-native object. This ensures:
         // 1) The property read has no observable side-effects.
         // 2) There's no need to dynamically monitor the return type. This would
         //    be complicated since (due to GVN) there can be multiple pc's
@@ -2212,124 +2111,16 @@ GetElementIC::attachTypedArrayElement(JS
     masm.bind(&failures);
 
     attacher.jumpNextStub(masm);
 
     return linkAndAttachStub(cx, masm, attacher, ion, "typed array");
 }
 
 bool
-GetElementIC::attachArgumentsElement(JSContext *cx, IonScript *ion, JSObject *obj)
-{
-    JS_ASSERT(obj->isArguments());
-
-    Label failures;
-    MacroAssembler masm(cx);
-    RepatchStubAppender attacher(*this);
-
-    Register tmpReg = output().scratchReg().gpr();
-    JS_ASSERT(tmpReg != InvalidReg);
-
-    Class *clasp = obj->isStrictArguments() ? &StrictArgumentsObjectClass
-                                            : &NormalArgumentsObjectClass;
-
-    Label fail;
-    Label pass;
-    masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures);
-
-    // Get initial ArgsObj length value, test if length has been overridden.
-    masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
-    masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
-                      &failures);
-
-    // Decide to what type index the stub should be optimized
-    Register indexReg;
-    JS_ASSERT(!index().constant());
-
-    // Check index against length.
-    Label failureRestoreIndex;
-    if (index().reg().hasValue()) {
-        ValueOperand val = index().reg().valueReg();
-        masm.branchTestInt32(Assembler::NotEqual, val, &failures);
-        indexReg = val.scratchReg();
-
-        masm.unboxInt32(val, indexReg);
-        masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failureRestoreIndex);
-    } else {
-        JS_ASSERT(index().reg().type() == MIRType_Int32);
-        indexReg = index().reg().typedReg().gpr();
-        masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failures);
-    }
-    // Save indexReg because it needs to be clobbered to check deleted bit.
-    Label failurePopIndex;
-    masm.push(indexReg);
-
-    // Check if property was deleted on arguments object.
-    masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
-    masm.loadPtr(Address(tmpReg, offsetof(ArgumentsData, deletedBits)), tmpReg);
-
-    // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord)
-    masm.rshiftPtr(Imm32(JS_BITS_PER_WORD_LOG2), indexReg);
-    masm.loadPtr(BaseIndex(tmpReg, indexReg, ScaleFromElemWidth(sizeof(size_t))), tmpReg);
-
-    // Don't bother testing specific bit, if any bit is set in the word, fail.
-    masm.branchPtr(Assembler::NotEqual, tmpReg, ImmWord((size_t)0), &failurePopIndex);
-
-    // Get the address to load from into tmpReg
-    masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
-    masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg);
-
-    // Restore original index register value, to use for indexing element.
-    masm.pop(indexReg);
-    BaseIndex elemIdx(tmpReg, indexReg, ScaleFromElemWidth(sizeof(Value)));
-
-    // Ensure result is not magic value, and type-check result.
-    masm.branchTestMagic(Assembler::Equal, elemIdx, &failureRestoreIndex);
-
-    if (output().hasTyped()) {
-        JS_ASSERT(!output().typedReg().isFloat());
-        JS_ASSERT(index().reg().type() == MIRType_Boolean ||
-                  index().reg().type() == MIRType_Int32 ||
-                  index().reg().type() == MIRType_String ||
-                  index().reg().type() == MIRType_Object);
-        masm.branchTestMIRType(Assembler::NotEqual, elemIdx, index().reg().type(),
-                               &failureRestoreIndex);
-    }
-
-    masm.loadTypedOrValue(elemIdx, output());
-
-    // indexReg may need to be reconstructed if it was originally a value.
-    if (index().reg().hasValue())
-        masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg());
-
-    // Success.
-    attacher.jumpRejoin(masm);
-
-    // Restore the object before continuing to the next stub.
-    masm.bind(&failurePopIndex);
-    masm.pop(indexReg);
-    masm.bind(&failureRestoreIndex);
-    if (index().reg().hasValue())
-        masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg());
-    masm.bind(&failures);
-    attacher.jumpNextStub(masm);
-
-
-    if (obj->isStrictArguments()) {
-        JS_ASSERT(!hasStrictArgumentsStub_);
-        hasStrictArgumentsStub_ = true;
-        return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (strict)");
-    }
-
-    JS_ASSERT(!hasNormalArgumentsStub_);
-    hasNormalArgumentsStub_ = true;
-    return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (normal)");
-}
-
-bool
 GetElementIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
                      HandleValue idval, MutableHandleValue res)
 {
     IonScript *ion = GetTopIonJSScript(cx)->ionScript();
     GetElementIC &cache = ion->getCache(cacheIndex).toGetElement();
     RootedScript script(cx);
     jsbytecode *pc;
     cache.getScriptedLocation(&script, &pc);
@@ -2347,27 +2138,17 @@ GetElementIC::update(JSContext *cx, size
     AutoDetectInvalidation adi(cx, res.address(), ion);
 
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, idval, &id))
         return false;
 
     bool attachedStub = false;
     if (cache.canAttachStub()) {
-        if (IsOptimizableArgumentsObjectForGetElem(obj, idval) &&
-            !cache.hasArgumentsStub(obj->isStrictArguments()) &&
-            !cache.index().constant() &&
-            (cache.index().reg().hasValue() ||
-             cache.index().reg().type() == MIRType_Int32) &&
-            (cache.output().hasValue() || !cache.output().typedReg().isFloat()))
-        {
-            if (!cache.attachArgumentsElement(cx, ion, obj))
-                return false;
-            attachedStub = true;
-        } else if (obj->isNative() && cache.monitoredResult()) {
+        if (obj->isNative() && cache.monitoredResult()) {
             uint32_t dummy;
             if (idval.isString() && JSID_IS_ATOM(id) && !JSID_TO_ATOM(id)->isIndex(&dummy)) {
                 RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
                 if (!cache.attachGetProp(cx, ion, obj, idval, name))
                     return false;
                 attachedStub = true;
             }
         } else if (!cache.hasDenseStub() && obj->isNative() && idval.isInt32()) {
--- a/js/src/ion/IonCaches.h
+++ b/js/src/ion/IonCaches.h
@@ -491,18 +491,16 @@ class GetPropertyIC : public RepatchIonC
     RegisterSet liveRegs_;
 
     Register object_;
     PropertyName *name_;
     TypedOrValueRegister output_;
     bool allowGetters_ : 1;
     bool hasArrayLengthStub_ : 1;
     bool hasTypedArrayLengthStub_ : 1;
-    bool hasStrictArgumentsLengthStub_ : 1;
-    bool hasNormalArgumentsLengthStub_ : 1;
 
   public:
     GetPropertyIC(RegisterSet liveRegs,
                   Register object, PropertyName *name,
                   TypedOrValueRegister output,
                   bool allowGetters)
       : liveRegs_(liveRegs),
         object_(object),
@@ -531,28 +529,24 @@ class GetPropertyIC : public RepatchIonC
         return allowGetters_;
     }
     bool hasArrayLengthStub() const {
         return hasArrayLengthStub_;
     }
     bool hasTypedArrayLengthStub() const {
         return hasTypedArrayLengthStub_;
     }
-    bool hasArgumentsLengthStub(bool strict) const {
-        return strict ? hasStrictArgumentsLengthStub_ : hasNormalArgumentsLengthStub_;
-    }
 
     bool attachReadSlot(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
                         HandleShape shape);
     bool attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
                           HandleShape shape,
                           const SafepointIndex *safepointIndex, void *returnAddr);
     bool attachArrayLength(JSContext *cx, IonScript *ion, JSObject *obj);
     bool attachTypedArrayLength(JSContext *cx, IonScript *ion, JSObject *obj);
-    bool attachArgumentsLength(JSContext *cx, IonScript *ion, JSObject *obj);
 
     static bool update(JSContext *cx, size_t cacheIndex, HandleObject obj, MutableHandleValue vp);
 };
 
 class SetPropertyIC : public RepatchIonCache
 {
   protected:
     // Registers live after the cache, excluding output registers. The initial
@@ -609,18 +603,16 @@ class GetElementIC : public RepatchIonCa
 {
   protected:
     Register object_;
     ConstantOrRegister index_;
     TypedOrValueRegister output_;
 
     bool monitoredResult_ : 1;
     bool hasDenseStub_ : 1;
-    bool hasStrictArgumentsStub_ : 1;
-    bool hasNormalArgumentsStub_ : 1;
 
     size_t failedUpdates_;
 
     static const size_t MAX_FAILED_UPDATES;
 
   public:
     GetElementIC(Register object, ConstantOrRegister index,
                  TypedOrValueRegister output, bool monitoredResult)
@@ -647,28 +639,24 @@ class GetElementIC : public RepatchIonCa
         return output_;
     }
     bool monitoredResult() const {
         return monitoredResult_;
     }
     bool hasDenseStub() const {
         return hasDenseStub_;
     }
-    bool hasArgumentsStub(bool strict) const {
-        return strict ? hasStrictArgumentsStub_ : hasNormalArgumentsStub_;
-    }
     void setHasDenseStub() {
         JS_ASSERT(!hasDenseStub());
         hasDenseStub_ = true;
     }
 
     bool attachGetProp(JSContext *cx, IonScript *ion, HandleObject obj, const Value &idval, HandlePropertyName name);
     bool attachDenseElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval);
     bool attachTypedArrayElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval);
-    bool attachArgumentsElement(JSContext *cx, IonScript *ion, JSObject *obj);
 
     static bool
     update(JSContext *cx, size_t cacheIndex, HandleObject obj, HandleValue idval,
                 MutableHandleValue vp);
 
     void incFailedUpdates() {
         failedUpdates_++;
     }
--- a/js/src/ion/IonCompartment.h
+++ b/js/src/ion/IonCompartment.h
@@ -192,16 +192,24 @@ class IonCompartment
 
     // Keep track of offset into baseline ICCall_Scripted stub's code at return
     // point from called script.
     void *baselineCallReturnAddr_;
 
     // Allocated space for optimized baseline stubs.
     OptimizedICStubSpace optimizedStubSpace_;
 
+    // Stub to concatenate two strings inline. Note that it can't be
+    // stored in IonRuntime because masm.newGCString bakes in zone-specific
+    // pointers. This has to be a weak pointer to avoid keeping the whole
+    // compartment alive.
+    ReadBarriered<IonCode> stringConcatStub_;
+
+    IonCode *generateStringConcatStub(JSContext *cx);
+
   public:
     IonCode *getVMWrapper(const VMFunction &f);
 
     OffThreadCompilationVector &finishedOffThreadCompilations() {
         return finishedOffThreadCompilations_;
     }
 
     IonCode *getStubCode(uint32_t key) {
@@ -230,16 +238,19 @@ class IonCompartment
     void toggleBaselineStubBarriers(bool enabled);
 
   public:
     IonCompartment(IonRuntime *rt);
     ~IonCompartment();
 
     bool initialize(JSContext *cx);
 
+    // Initialize code stubs only used by Ion, not Baseline.
+    bool ensureIonStubsExist(JSContext *cx);
+
     void mark(JSTracer *trc, JSCompartment *compartment);
     void sweep(FreeOp *fop);
 
     JSC::ExecutableAllocator *execAlloc() {
         return rt->execAlloc_;
     }
 
     IonCode *getGenericBailoutHandler() {
@@ -279,16 +290,20 @@ class IonCompartment
     IonCode *shapePreBarrier() {
         return rt->shapePreBarrier_;
     }
 
     IonCode *debugTrapHandler(JSContext *cx) {
         return rt->debugTrapHandler(cx);
     }
 
+    IonCode *stringConcatStub() {
+        return stringConcatStub_;
+    }
+
     AutoFlushCache *flusher() {
         return rt->flusher();
     }
     void setFlusher(AutoFlushCache *fl) {
         rt->setFlusher(fl);
     }
     OptimizedICStubSpace *optimizedStubSpace() {
         return &optimizedStubSpace_;
--- a/js/src/ion/IonMacroAssembler.cpp
+++ b/js/src/ion/IonMacroAssembler.cpp
@@ -459,16 +459,22 @@ MacroAssembler::newGCThing(const Registe
 
 void
 MacroAssembler::newGCString(const Register &result, Label *fail)
 {
     newGCThing(result, js::gc::FINALIZE_STRING, fail);
 }
 
 void
+MacroAssembler::newGCShortString(const Register &result, Label *fail)
+{
+    newGCThing(result, js::gc::FINALIZE_SHORT_STRING, fail);
+}
+
+void
 MacroAssembler::parNewGCThing(const Register &result,
                               const Register &threadContextReg,
                               const Register &tempReg1,
                               const Register &tempReg2,
                               JSObject *templateObject,
                               Label *fail)
 {
     // Similar to ::newGCThing(), except that it allocates from a
--- a/js/src/ion/IonMacroAssembler.h
+++ b/js/src/ion/IonMacroAssembler.h
@@ -160,41 +160,16 @@ class MacroAssembler : public MacroAssem
     }
     void branchTestObjShape(Condition cond, Register obj, const Shape *shape, Label *label) {
         branchPtr(cond, Address(obj, JSObject::offsetOfShape()), ImmGCPtr(shape), label);
     }
     void branchTestObjShape(Condition cond, Register obj, Register shape, Label *label) {
         branchPtr(cond, Address(obj, JSObject::offsetOfShape()), shape, label);
     }
 
-    template <typename Value>
-    Condition testMIRType(Condition cond, const Value &val, MIRType type) {
-        JS_ASSERT(type == MIRType_Null    || type == MIRType_Undefined  ||
-                  type == MIRType_Boolean || type == MIRType_Int32      ||
-                  type == MIRType_String  || type == MIRType_Object     ||
-                  type == MIRType_Double);
-        switch (type) {
-          case MIRType_Null:        return testNull(cond, val);
-          case MIRType_Undefined:   return testUndefined(cond, val);
-          case MIRType_Boolean:     return testBoolean(cond, val);
-          case MIRType_Int32:       return testInt32(cond, val);
-          case MIRType_String:      return testString(cond, val);
-          case MIRType_Object:      return testObject(cond, val);
-          case MIRType_Double:      return testDouble(cond, val);
-          default:
-            JS_NOT_REACHED("Bad MIRType");
-        }
-    }
-
-    template <typename Value>
-    void branchTestMIRType(Condition cond, const Value &val, MIRType type, Label *label) {
-        cond = testMIRType(cond, val, type);
-        j(cond, label);
-    }
-
     // Branches to |label| if |reg| is false. |reg| should be a C++ bool.
     void branchIfFalseBool(const Register &reg, Label *label) {
         // Note that C++ bool is only 1 byte, so ignore the higher-order bits.
         branchTest32(Assembler::Zero, reg, Imm32(0xFF), label);
     }
 
     void loadObjPrivate(Register obj, uint32_t nfixed, Register dest) {
         loadPtr(Address(obj, JSObject::getPrivateDataOffset(nfixed)), dest);
@@ -575,16 +550,17 @@ class MacroAssembler : public MacroAssem
 
         bind(&done);
     }
 
     // Inline allocation.
     void newGCThing(const Register &result, gc::AllocKind allocKind, Label *fail);
     void newGCThing(const Register &result, JSObject *templateObject, Label *fail);
     void newGCString(const Register &result, Label *fail);
+    void newGCShortString(const Register &result, Label *fail);
 
     void parNewGCThing(const Register &result,
                        const Register &threadContextReg,
                        const Register &tempReg1,
                        const Register &tempReg2,
                        JSObject *templateObject,
                        Label *fail);
     void initGCThing(const Register &obj, JSObject *templateObject);
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -258,26 +258,34 @@ class LNewParallelArray : public LInstru
     }
 };
 
 class LNewArray : public LInstructionHelper<1, 0, 0>
 {
   public:
     LIR_HEADER(NewArray)
 
+    const char *extraName() const {
+        return mir()->shouldUseVM() ? "VMCall" : NULL;
+    }
+
     MNewArray *mir() const {
         return mir_->toNewArray();
     }
 };
 
 class LNewObject : public LInstructionHelper<1, 0, 0>
 {
   public:
     LIR_HEADER(NewObject)
 
+    const char *extraName() const {
+        return mir()->shouldUseVM() ? "VMCall" : NULL;
+    }
+
     MNewObject *mir() const {
         return mir_->toNewObject();
     }
 };
 
 class LParNew : public LInstructionHelper<1, 1, 2>
 {
   public:
@@ -1291,16 +1299,20 @@ class LTestVAndBranch : public LInstruct
       : ifTruthy_(ifTruthy),
         ifFalsy_(ifFalsy)
     {
         setTemp(0, temp0);
         setTemp(1, temp1);
         setTemp(2, temp2);
     }
 
+    const char *extraName() const {
+        return mir()->operandMightEmulateUndefined() ? "MightEmulateUndefined" : NULL;
+    }
+
     static const size_t Input = 0;
 
     const LAllocation *tempFloat() {
         return getTemp(0)->output();
     }
 
     const LDefinition *temp1() {
         return getTemp(1);
@@ -1312,17 +1324,17 @@ class LTestVAndBranch : public LInstruct
 
     Label *ifTruthy() {
         return ifTruthy_->lir()->label();
     }
     Label *ifFalsy() {
         return ifFalsy_->lir()->label();
     }
 
-    MTest *mir() {
+    MTest *mir() const {
         return mir_->toTest();
     }
 };
 
 // Dispatches control flow to a successor based on incoming JSFunction*.
 // Used to implemenent polymorphic inlining.
 class LFunctionDispatch : public LInstructionHelper<0, 1, 0>
 {
@@ -1553,37 +1565,16 @@ class LCompareStrictS : public LInstruct
     const LDefinition *temp1() {
         return getTemp(1);
     }
     MCompare *mir() {
         return mir_->toCompare();
     }
 };
 
-class LParCompareS : public LCallInstructionHelper<1, 2, 0>
-{
-  public:
-    LIR_HEADER(ParCompareS);
-
-    LParCompareS(const LAllocation &left, const LAllocation &right) {
-        setOperand(0, left);
-        setOperand(1, right);
-    }
-
-    const LAllocation *left() {
-        return getOperand(0);
-    }
-    const LAllocation *right() {
-        return getOperand(1);
-    }
-    MCompare *mir() {
-        return mir_->toCompare();
-    }
-};
-
 // Used for strict-equality comparisons where one side is a boolean
 // and the other is a value. Note that CompareI is used to compare
 // two booleans.
 class LCompareB : public LInstructionHelper<1, BOX_PIECES + 1, 0>
 {
   public:
     LIR_HEADER(CompareB)
 
@@ -1887,17 +1878,23 @@ class LBitOpI : public LInstructionHelpe
 
   public:
     LIR_HEADER(BitOpI)
 
     LBitOpI(JSOp op)
       : op_(op)
     { }
 
-    JSOp bitop() {
+    const char *extraName() const {
+        if (bitop() == JSOP_URSH && mir_->toUrsh()->canOverflow())
+            return "UrshCanOverflow";
+        return NULL;
+    }
+
+    JSOp bitop() const {
         return op_;
     }
 };
 
 // Call a VM function to perform a bitwise operation.
 class LBitOpV : public LCallInstructionHelper<1, 2 * BOX_PIECES, 0>
 {
     JSOp jsop_;
@@ -2153,16 +2150,20 @@ class LAddI : public LBinaryMath<0>
 
   public:
     LIR_HEADER(AddI)
 
     LAddI()
       : recoversInput_(false)
     { }
 
+    const char *extraName() const {
+        return snapshot() ? "OverflowCheck" : NULL;
+    }
+
     virtual bool recoversInput() const {
         return recoversInput_;
     }
     void setRecoversInput() {
         recoversInput_ = true;
     }
 };
 
@@ -2173,16 +2174,20 @@ class LSubI : public LBinaryMath<0>
 
   public:
     LIR_HEADER(SubI)
 
     LSubI()
       : recoversInput_(false)
     { }
 
+    const char *extraName() const {
+        return snapshot() ? "OverflowCheck" : NULL;
+    }
+
     virtual bool recoversInput() const {
         return recoversInput_;
     }
     void setRecoversInput() {
         recoversInput_ = true;
     }
 };
 
@@ -2238,36 +2243,49 @@ class LBinaryV : public LCallInstruction
         return jsop_;
     }
 
     static const size_t LhsInput = 0;
     static const size_t RhsInput = BOX_PIECES;
 };
 
 // Adds two string, returning a string.
-class LConcat : public LInstructionHelper<1, 2, 1>
+class LConcat : public LInstructionHelper<1, 2, 4>
 {
   public:
     LIR_HEADER(Concat)
 
-    LConcat(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp) {
+    LConcat(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp1,
+            const LDefinition &temp2, const LDefinition &temp3, const LDefinition &temp4) {
         setOperand(0, lhs);
         setOperand(1, rhs);
-        setTemp(0, temp);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+        setTemp(2, temp3);
+        setTemp(3, temp4);
     }
 
     const LAllocation *lhs() {
         return this->getOperand(0);
     }
     const LAllocation *rhs() {
         return this->getOperand(1);
     }
-    const LDefinition *temp() {
+    const LDefinition *temp1() {
         return this->getTemp(0);
     }
+    const LDefinition *temp2() {
+        return this->getTemp(1);
+    }
+    const LDefinition *temp3() {
+        return this->getTemp(2);
+    }
+    const LDefinition *temp4() {
+        return this->getTemp(3);
+    }
 };
 
 // Get uint16 character code from a string.
 class LCharCodeAt : public LInstructionHelper<1, 2, 0>
 {
   public:
     LIR_HEADER(CharCodeAt)
 
@@ -2340,16 +2358,20 @@ class LValueToInt32 : public LInstructio
     LIR_HEADER(ValueToInt32)
 
     LValueToInt32(const LDefinition &temp, Mode mode)
       : mode_(mode)
     {
         setTemp(0, temp);
     }
 
+    const char *extraName() const {
+        return mode() == NORMAL ? "Normal" : "Truncate";
+    }
+
     static const size_t Input = 0;
 
     Mode mode() const {
         return mode_;
     }
     const LDefinition *tempFloat() {
         return getTemp(0);
     }
@@ -2791,16 +2813,21 @@ class LLoadElementV : public LInstructio
   public:
     LIR_HEADER(LoadElementV)
     BOX_OUTPUT_ACCESSORS()
 
     LLoadElementV(const LAllocation &elements, const LAllocation &index) {
         setOperand(0, elements);
         setOperand(1, index);
     }
+
+    const char *extraName() const {
+        return mir()->needsHoleCheck() ? "HoleCheck" : NULL;
+    }
+
     const MLoadElement *mir() const {
         return mir_->toLoadElement();
     }
     const LAllocation *elements() {
         return getOperand(0);
     }
     const LAllocation *index() {
         return getOperand(1);
@@ -2845,16 +2872,21 @@ class LLoadElementHole : public LInstruc
     LIR_HEADER(LoadElementHole)
     BOX_OUTPUT_ACCESSORS()
 
     LLoadElementHole(const LAllocation &elements, const LAllocation &index, const LAllocation &initLength) {
         setOperand(0, elements);
         setOperand(1, index);
         setOperand(2, initLength);
     }
+
+    const char *extraName() const {
+        return mir()->needsHoleCheck() ? "HoleCheck" : NULL;
+    }
+
     const MLoadElementHole *mir() const {
         return mir_->toLoadElementHole();
     }
     const LAllocation *elements() {
         return getOperand(0);
     }
     const LAllocation *index() {
         return getOperand(1);
@@ -2872,16 +2904,21 @@ class LLoadElementT : public LInstructio
 {
   public:
     LIR_HEADER(LoadElementT)
 
     LLoadElementT(const LAllocation &elements, const LAllocation &index) {
         setOperand(0, elements);
         setOperand(1, index);
     }
+
+    const char *extraName() const {
+        return mir()->needsHoleCheck() ? "HoleCheck" : (mir()->loadDoubles() ? "Doubles" : NULL);
+    }
+
     const MLoadElement *mir() const {
         return mir_->toLoadElement();
     }
     const LAllocation *elements() {
         return getOperand(0);
     }
     const LAllocation *index() {
         return getOperand(1);
@@ -2894,16 +2931,20 @@ class LStoreElementV : public LInstructi
   public:
     LIR_HEADER(StoreElementV)
 
     LStoreElementV(const LAllocation &elements, const LAllocation &index) {
         setOperand(0, elements);
         setOperand(1, index);
     }
 
+    const char *extraName() const {
+        return mir()->needsHoleCheck() ? "HoleCheck" : NULL;
+    }
+
     static const size_t Value = 2;
 
     const MStoreElement *mir() const {
         return mir_->toStoreElement();
     }
     const LAllocation *elements() {
         return getOperand(0);
     }
@@ -2922,16 +2963,20 @@ class LStoreElementT : public LInstructi
     LIR_HEADER(StoreElementT)
 
     LStoreElementT(const LAllocation &elements, const LAllocation &index, const LAllocation &value) {
         setOperand(0, elements);
         setOperand(1, index);
         setOperand(2, value);
     }
 
+    const char *extraName() const {
+        return mir()->needsHoleCheck() ? "HoleCheck" : NULL;
+    }
+
     const MStoreElement *mir() const {
         return mir_->toStoreElement();
     }
     const LAllocation *elements() {
         return getOperand(0);
     }
     const LAllocation *index() {
         return getOperand(1);
@@ -3007,16 +3052,20 @@ class LArrayPopShiftV : public LInstruct
     LIR_HEADER(ArrayPopShiftV)
 
     LArrayPopShiftV(const LAllocation &object, const LDefinition &temp0, const LDefinition &temp1) {
         setOperand(0, object);
         setTemp(0, temp0);
         setTemp(1, temp1);
     }
 
+    const char *extraName() const {
+        return mir()->mode() == MArrayPopShift::Pop ? "Pop" : "Shift";
+    }
+
     const MArrayPopShift *mir() const {
         return mir_->toArrayPopShift();
     }
     const LAllocation *object() {
         return getOperand(0);
     }
     const LDefinition *temp0() {
         return getTemp(0);
@@ -3032,16 +3081,20 @@ class LArrayPopShiftT : public LInstruct
     LIR_HEADER(ArrayPopShiftT)
 
     LArrayPopShiftT(const LAllocation &object, const LDefinition &temp0, const LDefinition &temp1) {
         setOperand(0, object);
         setTemp(0, temp0);
         setTemp(1, temp1);
     }
 
+    const char *extraName() const {
+        return mir()->mode() == MArrayPopShift::Pop ? "Pop" : "Shift";
+    }
+
     const MArrayPopShift *mir() const {
         return mir_->toArrayPopShift();
     }
     const LAllocation *object() {
         return getOperand(0);
     }
     const LDefinition *temp0() {
         return getTemp(0);
@@ -3170,16 +3223,31 @@ class LLoadTypedArrayElementHole : publi
     const LAllocation *object() {
         return getOperand(0);
     }
     const LAllocation *index() {
         return getOperand(1);
     }
 };
 
+class LLoadTypedArrayElementStatic : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(LoadTypedArrayElementStatic);
+    LLoadTypedArrayElementStatic(const LAllocation &ptr) {
+        setOperand(0, ptr);
+    }
+    MLoadTypedArrayElementStatic *mir() const {
+        return mir_->toLoadTypedArrayElementStatic();
+    }
+    const LAllocation *ptr() {
+        return getOperand(0);
+    }
+};
+
 class LStoreTypedArrayElement : public LInstructionHelper<0, 3, 0>
 {
   public:
     LIR_HEADER(StoreTypedArrayElement)
 
     LStoreTypedArrayElement(const LAllocation &elements, const LAllocation &index,
                             const LAllocation &value) {
         setOperand(0, elements);
@@ -3227,16 +3295,35 @@ class LStoreTypedArrayElementHole : publ
     const LAllocation *index() {
         return getOperand(2);
     }
     const LAllocation *value() {
         return getOperand(3);
     }
 };
 
+class LStoreTypedArrayElementStatic : public LInstructionHelper<0, 2, 0>
+{
+  public:
+    LIR_HEADER(StoreTypedArrayElementStatic);
+    LStoreTypedArrayElementStatic(const LAllocation &ptr, const LAllocation &value) {
+        setOperand(0, ptr);
+        setOperand(1, value);
+    }
+    MStoreTypedArrayElementStatic *mir() const {
+        return mir_->toStoreTypedArrayElementStatic();
+    }
+    const LAllocation *ptr() {
+        return getOperand(0);
+    }
+    const LAllocation *value() {
+        return getOperand(1);
+    }
+};
+
 class LEffectiveAddress : public LInstructionHelper<1, 2, 0>
 {
   public:
     LIR_HEADER(EffectiveAddress);
 
     LEffectiveAddress(const LAllocation &base, const LAllocation &index) {
         setOperand(0, base);
         setOperand(1, index);
--- a/js/src/ion/LIR.h
+++ b/js/src/ion/LIR.h
@@ -604,16 +604,22 @@ class LInstruction
             case LOp_##name: return #name;
             LIR_OPCODE_LIST(LIR_NAME_INS)
 #   undef LIR_NAME_INS
           default:
             return "Invalid";
         }
     }
 
+    // Hook for opcodes to add extra high level detail about what code will be
+    // emitted for the op.
+    virtual const char *extraName() const {
+        return NULL;
+    }
+
   public:
     virtual Opcode op() const = 0;
 
     // Returns the number of outputs of this instruction. If an output is
     // unallocated, it is an LDefinition, defining a virtual register.
     virtual size_t numDefs() const = 0;
     virtual LDefinition *getDef(size_t index) = 0;
     virtual void setDef(size_t index, const LDefinition &def) = 0;
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -71,17 +71,16 @@
     _(TypeObjectDispatch)           \
     _(PolyInlineDispatch)           \
     _(Compare)                      \
     _(CompareAndBranch)             \
     _(CompareD)                     \
     _(CompareDAndBranch)            \
     _(CompareS)                     \
     _(CompareStrictS)               \
-    _(ParCompareS)                  \
     _(CompareB)                     \
     _(CompareBAndBranch)            \
     _(CompareV)                     \
     _(CompareVAndBranch)            \
     _(CompareVM)                    \
     _(IsNullOrLikeUndefined)        \
     _(IsNullOrLikeUndefinedAndBranch)\
     _(EmulatesUndefined)            \
@@ -154,18 +153,20 @@
     _(ArrayPopShiftT)               \
     _(ArrayPushV)                   \
     _(ArrayPushT)                   \
     _(ArrayConcat)                  \
     _(StoreElementHoleV)            \
     _(StoreElementHoleT)            \
     _(LoadTypedArrayElement)        \
     _(LoadTypedArrayElementHole)    \
+    _(LoadTypedArrayElementStatic)  \
     _(StoreTypedArrayElement)       \
     _(StoreTypedArrayElementHole)   \
+    _(StoreTypedArrayElementStatic) \
     _(EffectiveAddress)             \
     _(ClampIToUint8)                \
     _(ClampDToUint8)                \
     _(ClampVToUint8)                \
     _(LoadFixedSlotV)               \
     _(LoadFixedSlotT)               \
     _(StoreFixedSlotV)              \
     _(StoreFixedSlotT)              \
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -738,34 +738,20 @@ LIRGenerator::visitCompare(MCompare *com
     bool result;
     if (comp->tryFold(&result))
         return define(new LInteger(result), comp);
 
     // Move below the emitAtUses call if we ever implement
     // LCompareSAndBranch. Doing this now wouldn't be wrong, but doesn't
     // make sense and avoids confusion.
     if (comp->compareType() == MCompare::Compare_String) {
-        switch (comp->block()->info().executionMode()) {
-          case SequentialExecution:
-          {
-              LCompareS *lir = new LCompareS(useRegister(left), useRegister(right), temp());
-              if (!define(lir, comp))
-                  return false;
-              return assignSafepoint(lir, comp);
-          }
-
-          case ParallelExecution:
-          {
-              LParCompareS *lir = new LParCompareS(useFixed(left, CallTempReg0),
-                                                   useFixed(right, CallTempReg1));
-              return defineReturn(lir, comp);
-          }
-        }
-
-        JS_NOT_REACHED("Unexpected execution mode");
+        LCompareS *lir = new LCompareS(useRegister(left), useRegister(right), temp());
+        if (!define(lir, comp))
+            return false;
+        return assignSafepoint(lir, comp);
     }
 
     // Strict compare between value and string
     if (comp->compareType() == MCompare::Compare_StrictString) {
         JS_ASSERT(left->type() == MIRType_Value);
         JS_ASSERT(right->type() == MIRType_String);
 
         LCompareStrictS *lir = new LCompareStrictS(useRegister(right), temp(), temp());
@@ -1302,18 +1288,23 @@ LIRGenerator::visitConcat(MConcat *ins)
 {
     MDefinition *lhs = ins->getOperand(0);
     MDefinition *rhs = ins->getOperand(1);
 
     JS_ASSERT(lhs->type() == MIRType_String);
     JS_ASSERT(rhs->type() == MIRType_String);
     JS_ASSERT(ins->type() == MIRType_String);
 
-    LConcat *lir = new LConcat(useRegister(lhs), useRegister(rhs), temp());
-    if (!define(lir, ins))
+    LConcat *lir = new LConcat(useFixed(lhs, CallTempReg0),
+                               useFixed(rhs, CallTempReg1),
+                               tempFixed(CallTempReg2),
+                               tempFixed(CallTempReg3),
+                               tempFixed(CallTempReg4),
+                               tempFixed(CallTempReg5));
+    if (!defineFixed(lir, ins, LAllocation(AnyRegister(CallTempReg6))))
         return false;
     return assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitCharCodeAt(MCharCodeAt *ins)
 {
     MDefinition *str = ins->getOperand(0);
@@ -2114,16 +2105,27 @@ LIRGenerator::visitLoadTypedArrayElement
 
     LLoadTypedArrayElementHole *lir = new LLoadTypedArrayElementHole(object, index);
     if (ins->fallible() && !assignSnapshot(lir))
         return false;
     return defineBox(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
+LIRGenerator::visitLoadTypedArrayElementStatic(MLoadTypedArrayElementStatic *ins)
+{
+    LLoadTypedArrayElementStatic *lir =
+        new LLoadTypedArrayElementStatic(useRegisterAtStart(ins->ptr()));
+
+    if (ins->fallible() && !assignSnapshot(lir))
+        return false;
+    return define(lir, ins);
+}
+
+bool
 LIRGenerator::visitLoadFixedSlot(MLoadFixedSlot *ins)
 {
     JS_ASSERT(ins->object()->type() == MIRType_Object);
 
     if (ins->type() == MIRType_Value) {
         LLoadFixedSlotV *lir = new LLoadFixedSlotV(useRegister(ins->object()));
         return defineBox(lir, ins);
     }
--- a/js/src/ion/Lowering.h
+++ b/js/src/ion/Lowering.h
@@ -184,16 +184,17 @@ class LIRGenerator : public LIRGenerator
     bool visitStoreElement(MStoreElement *ins);
     bool visitStoreElementHole(MStoreElementHole *ins);
     bool visitEffectiveAddress(MEffectiveAddress *ins);
     bool visitArrayPopShift(MArrayPopShift *ins);
     bool visitArrayPush(MArrayPush *ins);
     bool visitArrayConcat(MArrayConcat *ins);
     bool visitLoadTypedArrayElement(MLoadTypedArrayElement *ins);
     bool visitLoadTypedArrayElementHole(MLoadTypedArrayElementHole *ins);
+    bool visitLoadTypedArrayElementStatic(MLoadTypedArrayElementStatic *ins);
     bool visitClampToUint8(MClampToUint8 *ins);
     bool visitLoadFixedSlot(MLoadFixedSlot *ins);
     bool visitStoreFixedSlot(MStoreFixedSlot *ins);
     bool visitGetPropertyCache(MGetPropertyCache *ins);
     bool visitGetElementCache(MGetElementCache *ins);
     bool visitBindNameCache(MBindNameCache *ins);
     bool visitGuardClass(MGuardClass *ins);
     bool visitGuardObject(MGuardObject *ins);
--- a/js/src/ion/MIR.cpp
+++ b/js/src/ion/MIR.cpp
@@ -10,17 +10,17 @@
 #include "MIR.h"
 #include "MIRGraph.h"
 #include "EdgeCaseAnalysis.h"
 #include "RangeAnalysis.h"
 #include "IonSpewer.h"
 #include "jsnum.h"
 #include "jsstr.h"
 #include "jsatominlines.h"
-#include "jstypedarrayinlines.h" // For ClampIntForUint8Array
+#include "jstypedarrayinlines.h"
 
 using namespace js;
 using namespace js::ion;
 
 void
 MDefinition::PrintOpcodeName(FILE *fp, MDefinition::Opcode op)
 {
     static const char *names[] =
@@ -1231,38 +1231,41 @@ static inline bool
 KnownNonStringPrimitive(MDefinition *op)
 {
     return !op->mightBeType(MIRType_Object)
         && !op->mightBeType(MIRType_String)
         && !op->mightBeType(MIRType_Magic);
 }
 
 void
-MBinaryArithInstruction::infer(bool overflowed)
+MBinaryArithInstruction::infer(BaselineInspector *inspector,
+                               jsbytecode *pc,
+                               bool overflowed)
 {
     JS_ASSERT(this->type() == MIRType_Value);
 
     specialization_ = MIRType_None;
 
     // Retrieve type information of lhs and rhs.
     MIRType lhs = getOperand(0)->type();
     MIRType rhs = getOperand(1)->type();
 
-    // Anything complex - strings and objects - are not specialized.
+    // Anything complex - strings and objects - are not specialized
+    // unless baseline type hints suggest it might be profitable
     if (!KnownNonStringPrimitive(getOperand(0)) || !KnownNonStringPrimitive(getOperand(1)))
-        return;
+        return inferFallback(inspector, pc);
 
     // Guess a result type based on the inputs.
     // Don't specialize for neither-integer-nor-double results.
     if (lhs == MIRType_Int32 && rhs == MIRType_Int32)
         setResultType(MIRType_Int32);
     else if (lhs == MIRType_Double || rhs == MIRType_Double)
         setResultType(MIRType_Double);
     else
-        return;
+        return inferFallback(inspector, pc);
 
     // If the operation has ever overflowed, use a double specialization.
     if (overflowed)
         setResultType(MIRType_Double);
 
     JS_ASSERT(lhs < MIRType_String || lhs == MIRType_Value);
     JS_ASSERT(rhs < MIRType_String || rhs == MIRType_Value);
 
@@ -1285,16 +1288,39 @@ MBinaryArithInstruction::infer(bool over
 
     specialization_ = rval;
 
     if (isAdd() || isMul())
         setCommutative();
     setResultType(rval);
 }
 
+void
+MBinaryArithInstruction::inferFallback(BaselineInspector *inspector,
+                                       jsbytecode *pc)
+{
+    // Try to specialize based on what baseline observed in practice.
+    specialization_ = inspector->expectedBinaryArithSpecialization(pc);
+    if (specialization_ != MIRType_None) {
+        setResultType(specialization_);
+        return;
+    }
+
+    // In parallel execution, for now anyhow, we *only* support adding
+    // and manipulating numbers (not strings or objects).  So no
+    // matter what we can specialize to double...if the result ought
+    // to have been something else, we'll fail in the various type
+    // guards that get inserted later.
+    if (block()->info().executionMode() == ParallelExecution) {
+        specialization_ = MIRType_Double;
+        setResultType(MIRType_Double);
+        return;
+    }
+}
+
 static bool
 SafelyCoercesToDouble(MDefinition *op)
 {
     // Strings are unhandled -- visitToDouble() doesn't support them yet.
     // Null is unhandled -- ToDouble(null) == 0, but (0 == null) is false.
     return KnownNonStringPrimitive(op) && !op->mightBeType(MIRType_Null);
 }
 
@@ -2164,16 +2190,40 @@ InlinePropertyTable::buildTypeSetForFunc
 }
 
 bool
 MInArray::needsNegativeIntCheck() const
 {
     return !index()->range() || index()->range()->lower() < 0;
 }
 
+void *
+MLoadTypedArrayElementStatic::base() const
+{
+    return TypedArray::viewData(typedArray_);
+}
+
+size_t
+MLoadTypedArrayElementStatic::length() const
+{
+    return TypedArray::byteLength(typedArray_);
+}
+
+void *
+MStoreTypedArrayElementStatic::base() const
+{
+    return TypedArray::viewData(typedArray_);
+}
+
+size_t
+MStoreTypedArrayElementStatic::length() const
+{
+    return TypedArray::byteLength(typedArray_);
+}
+
 MDefinition *
 MAsmJSUnsignedToDouble::foldsTo(bool useValueNumbers)
 {
     if (input()->isConstant()) {
         const Value &v = input()->toConstant()->value();
         if (v.isInt32())
             return MConstant::New(DoubleValue(uint32_t(v.toInt32())));
     }
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -2834,16 +2834,18 @@ class MBinaryArithInstruction
     // NeedNegativeZeroCheck to check if the result of a multiplication needs to
     // produce -0 double value, and for avoiding overflow checks.
 
     // This optimization happens when the multiplication cannot be truncated
     // even if all uses are truncating its result, such as when the range
     // analysis detect a precision loss in the multiplication.
     bool implicitTruncate_;
 
+    void inferFallback(BaselineInspector *inspector, jsbytecode *pc);
+
   public:
     MBinaryArithInstruction(MDefinition *left, MDefinition *right)
       : MBinaryInstruction(left, right),
         implicitTruncate_(false)
     {
         setMovable();
     }
 
@@ -2853,17 +2855,18 @@ class MBinaryArithInstruction
     MIRType specialization() const {
         return specialization_;
     }
 
     MDefinition *foldsTo(bool useValueNumbers);
 
     virtual double getIdentity() = 0;
 
-    void infer(bool overflowed);
+    void infer(BaselineInspector *inspector,
+               jsbytecode *pc, bool overflowed);
 
     void setInt32() {
         specialization_ = MIRType_Int32;
         setResultType(MIRType_Int32);
     }
 
     bool congruentTo(MDefinition *const &ins) const {
         return MBinaryInstruction::congruentTo(ins);
@@ -3856,16 +3859,18 @@ class MLambda
     public SingleObjectPolicy
 {
     CompilerRootFunction fun_;
 
     MLambda(MDefinition *scopeChain, JSFunction *fun)
       : MUnaryInstruction(scopeChain), fun_(fun)
     {
         setResultType(MIRType_Object);
+        if (!fun->hasSingletonType() && !types::UseNewTypeForClone(fun))
+            setResultTypeSet(MakeSingletonTypeSet(fun));
     }
 
   public:
     INSTRUCTION_HEADER(Lambda)
 
     static MLambda *New(MDefinition *scopeChain, JSFunction *fun) {
         return new MLambda(scopeChain, fun);
     }
@@ -3885,17 +3890,20 @@ class MParLambda
     public SingleObjectPolicy
 {
     CompilerRootFunction fun_;
 
     MParLambda(MDefinition *parSlice,
                MDefinition *scopeChain, JSFunction *fun)
       : MBinaryInstruction(parSlice, scopeChain), fun_(fun)
     {
+        JS_ASSERT(!fun->hasSingletonType());
+        JS_ASSERT(!types::UseNewTypeForClone(fun));
         setResultType(MIRType_Object);
+        setResultTypeSet(MakeSingletonTypeSet(fun));
     }
 
   public:
     INSTRUCTION_HEADER(ParLambda);
 
     static MParLambda *New(MDefinition *parSlice,
                            MDefinition *scopeChain, JSFunction *fun) {
         return new MParLambda(parSlice, scopeChain, fun);
@@ -4814,16 +4822,60 @@ class MLoadTypedArrayElementHole
     }
     AliasSet getAliasSet() const {
         // Out-of-bounds accesses are handled using a VM call, this may
         // invoke getters on the prototype chain.
         return AliasSet::Store(AliasSet::Any);
     }
 };
 
+// Load a value fallibly or infallibly from a statically known typed array.
+class MLoadTypedArrayElementStatic : public MUnaryInstruction
+{
+    MLoadTypedArrayElementStatic(JSObject *typedArray, MDefinition *ptr)
+      : MUnaryInstruction(ptr), typedArray_(typedArray), fallible_(true)
+    {
+        int type = TypedArray::type(typedArray_);
+        if (type == TypedArray::TYPE_FLOAT32 || type == TypedArray::TYPE_FLOAT64)
+            setResultType(MIRType_Double);
+        else
+            setResultType(MIRType_Int32);
+    }
+
+    CompilerRootObject typedArray_;
+    bool fallible_;
+
+  public:
+    INSTRUCTION_HEADER(LoadTypedArrayElementStatic);
+
+    static MLoadTypedArrayElementStatic *New(JSObject *typedArray, MDefinition *ptr) {
+        return new MLoadTypedArrayElementStatic(typedArray, ptr);
+    }
+
+    ArrayBufferView::ViewType viewType() const { return JS_GetArrayBufferViewType(typedArray_); }
+    void *base() const;
+    size_t length() const;
+
+    MDefinition *ptr() const { return getOperand(0); }
+    AliasSet getAliasSet() const {
+        return AliasSet::Load(AliasSet::TypedArrayElement);
+    }
+
+    bool fallible() const {
+        return fallible_;
+    }
+
+    void setInfallible() {
+        fallible_ = false;
+    }
+
+    void computeRange();
+    bool truncate();
+};
+
 class MStoreTypedArrayElement
   : public MTernaryInstruction,
     public StoreTypedArrayPolicy
 {
     int arrayType_;
 
     // See note in MStoreElementCommon.
     bool racy_;
@@ -4938,16 +4990,49 @@ class MStoreTypedArrayElementHole
     MDefinition *value() const {
         return getOperand(3);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::TypedArrayElement);
     }
 };
 
+// Store a value infallibly to a statically known typed array.
+class MStoreTypedArrayElementStatic :
+    public MBinaryInstruction
+  , public StoreTypedArrayElementStaticPolicy
+{
+    MStoreTypedArrayElementStatic(JSObject *typedArray, MDefinition *ptr, MDefinition *v)
+      : MBinaryInstruction(ptr, v), typedArray_(typedArray)
+    {}
+
+    CompilerRootObject typedArray_;
+
+  public:
+    INSTRUCTION_HEADER(StoreTypedArrayElementStatic);
+
+    static MStoreTypedArrayElementStatic *New(JSObject *typedArray, MDefinition *ptr, MDefinition *v) {
+        return new MStoreTypedArrayElementStatic(typedArray, ptr, v);
+    }
+
+    TypePolicy *typePolicy() {
+        return this;
+    }
+
+    ArrayBufferView::ViewType viewType() const { return JS_GetArrayBufferViewType(typedArray_); }
+    void *base() const;
+    size_t length() const;
+
+    MDefinition *ptr() const { return getOperand(0); }
+    MDefinition *value() const { return getOperand(1); }
+    AliasSet getAliasSet() const {
+        return AliasSet::Store(AliasSet::TypedArrayElement);
+    }
+};
+
 // Compute an "effective address", i.e., a compound computation of the form:
 //   base + index * scale + displacement
 class MEffectiveAddress : public MBinaryInstruction
 {
     MEffectiveAddress(MDefinition *base, MDefinition *index, Scale scale, int32_t displacement)
       : MBinaryInstruction(base, index), scale_(scale), displacement_(displacement)
     {
         JS_ASSERT(base->type() == MIRType_Int32);
--- a/js/src/ion/MOpcodes.h
+++ b/js/src/ion/MOpcodes.h
@@ -121,18 +121,20 @@ namespace ion {
     _(LoadElementHole)                                                      \
     _(StoreElement)                                                         \
     _(StoreElementHole)                                                     \
     _(ArrayPopShift)                                                        \
     _(ArrayPush)                                                            \
     _(ArrayConcat)                                                          \
     _(LoadTypedArrayElement)                                                \
     _(LoadTypedArrayElementHole)                                            \
+    _(LoadTypedArrayElementStatic)                                          \
     _(StoreTypedArrayElement)                                               \
     _(StoreTypedArrayElementHole)                                           \
+    _(StoreTypedArrayElementStatic)                                         \
     _(EffectiveAddress)                                                     \
     _(ClampToUint8)                                                         \
     _(LoadFixedSlot)                                                        \
     _(StoreFixedSlot)                                                       \
     _(CallGetProperty)                                                      \
     _(GetNameCache)                                                         \
     _(CallGetIntrinsicValue)                                                \
     _(CallsiteCloneCache)                                                   \
--- a/js/src/ion/ParallelArrayAnalysis.cpp
+++ b/js/src/ion/ParallelArrayAnalysis.cpp
@@ -208,18 +208,20 @@ class ParallelArrayVisitor : public MIns
     SAFE_OP(LoadElement)
     SAFE_OP(LoadElementHole)
     MAYBE_WRITE_GUARDED_OP(StoreElement, elements)
     WRITE_GUARDED_OP(StoreElementHole, elements)
     UNSAFE_OP(ArrayPopShift)
     UNSAFE_OP(ArrayPush)
     SAFE_OP(LoadTypedArrayElement)
     SAFE_OP(LoadTypedArrayElementHole)
+    SAFE_OP(LoadTypedArrayElementStatic)
     MAYBE_WRITE_GUARDED_OP(StoreTypedArrayElement, elements)
     WRITE_GUARDED_OP(StoreTypedArrayElementHole, elements)
+    UNSAFE_OP(StoreTypedArrayElementStatic)
     UNSAFE_OP(ClampToUint8)
     SAFE_OP(LoadFixedSlot)
     WRITE_GUARDED_OP(StoreFixedSlot, object)
     UNSAFE_OP(CallGetProperty)
     UNSAFE_OP(GetNameCache)
     SAFE_OP(CallGetIntrinsicValue) // Bails in parallel mode
     UNSAFE_OP(CallsiteCloneCache)
     UNSAFE_OP(CallGetElement)
@@ -488,19 +490,36 @@ ParallelArrayVisitor::visitTest(MTest *)
 {
     return true;
 }
 
 bool
 ParallelArrayVisitor::visitCompare(MCompare *compare)
 {
     MCompare::CompareType type = compare->compareType();
-    return type == MCompare::Compare_Int32 ||
-           type == MCompare::Compare_Double ||
-           type == MCompare::Compare_String;
+    MBox *lhsBox, *rhsBox;
+    MBasicBlock *block;
+
+    switch (type) {
+      case MCompare::Compare_Int32:
+      case MCompare::Compare_Double:
+      case MCompare::Compare_Null:
+      case MCompare::Compare_Undefined:
+      case MCompare::Compare_Boolean:
+      case MCompare::Compare_Object:
+      case MCompare::Compare_Value:
+      case MCompare::Compare_Unknown:
+      case MCompare::Compare_String:
+        // These paths through compare are ok in any mode.
+        return true;
+
+      default:
+        SpewMIR(compare, "unsafe compareType=%d\n", type);
+        return markUnsafe();
+    }
 }
 
 bool
 ParallelArrayVisitor::convertToBailout(MBasicBlock *block, MInstruction *ins)
 {
     JS_ASSERT(unsafe()); // `block` must have contained unsafe items
     JS_ASSERT(block->isMarked()); // `block` must have been reachable to get here
 
--- a/js/src/ion/ParallelFunctions.cpp
+++ b/js/src/ion/ParallelFunctions.cpp
@@ -12,16 +12,17 @@
 #include "jscompartmentinlines.h"
 
 #include "vm/ParallelDo.h"
 
 using namespace js;
 using namespace ion;
 
 using parallel::Spew;
+using parallel::SpewOps;
 using parallel::SpewBailouts;
 using parallel::SpewBailoutIR;
 
 // Load the current thread context.
 ForkJoinSlice *
 ion::ParForkJoinSlice()
 {
     return ForkJoinSlice::Current();
@@ -178,30 +179,193 @@ ion::ParExtendArray(ForkJoinSlice *slice
 {
     JSObject::EnsureDenseResult res =
         array->parExtendDenseElements(slice->allocator, NULL, length);
     if (res != JSObject::ED_OK)
         return NULL;
     return array;
 }
 
-ParCompareResult
-ion::ParCompareStrings(JSString *str1, JSString *str2)
+#define PAR_RELATIONAL_OP(OP, EXPECTED)                                         \
+do {                                                                            \
+    /* Optimize for two int-tagged operands (typical loop control). */          \
+    if (lhs.isInt32() && rhs.isInt32()) {                                       \
+        *res = (lhs.toInt32() OP rhs.toInt32()) == EXPECTED;                    \
+    } else if (lhs.isNumber() && rhs.isNumber()) {                              \
+        double l = lhs.toNumber(), r = rhs.toNumber();                          \
+        *res = (l OP r) == EXPECTED;                                            \
+    } else if (lhs.isBoolean() && rhs.isBoolean()) {                            \
+        bool l = lhs.toBoolean();                                               \
+        bool r = rhs.toBoolean();                                               \
+        *res = (l OP r) == EXPECTED;                                            \
+    } else if (lhs.isBoolean() && rhs.isNumber()) {                             \
+        bool l = lhs.toBoolean();                                               \
+        double r = rhs.toNumber();                                              \
+        *res = (l OP r) == EXPECTED;                                            \
+    } else if (lhs.isNumber() && rhs.isBoolean()) {                             \
+        double l = lhs.toNumber();                                              \
+        bool r = rhs.toBoolean();                                               \
+        *res = (l OP r) == EXPECTED;                                            \
+    } else {                                                                    \
+        int32_t vsZero;                                                         \
+        ParallelResult ret = ParCompareMaybeStrings(slice, lhs, rhs, &vsZero);  \
+        if (ret != TP_SUCCESS)                                                  \
+            return ret;                                                         \
+        *res = (vsZero OP 0) == EXPECTED;                                       \
+    }                                                                           \
+    return TP_SUCCESS;                                                          \
+} while(0)
+
+static ParallelResult
+ParCompareStrings(ForkJoinSlice *slice, HandleString str1,
+                  HandleString str2, int32_t *res)
 {
-    // NYI---the rope case
     if (!str1->isLinear())
-        return ParCompareUnknown;
+        return TP_RETRY_SEQUENTIALLY;
     if (!str2->isLinear())
-        return ParCompareUnknown;
-
+        return TP_RETRY_SEQUENTIALLY;
     JSLinearString &linearStr1 = str1->asLinear();
     JSLinearString &linearStr2 = str2->asLinear();
-    if (EqualStrings(&linearStr1, &linearStr2))
-        return ParCompareEq;
-    return ParCompareNe;
+    if (!CompareChars(linearStr1.chars(), linearStr1.length(),
+                      linearStr2.chars(), linearStr2.length(),
+                      res))
+        return TP_FATAL;
+
+    return TP_SUCCESS;
+}
+
+static ParallelResult
+ParCompareMaybeStrings(ForkJoinSlice *slice,
+                       HandleValue v1,
+                       HandleValue v2,
+                       int32_t *res)
+{
+    if (!v1.isString())
+        return TP_RETRY_SEQUENTIALLY;
+    if (!v2.isString())
+        return TP_RETRY_SEQUENTIALLY;
+    RootedString str1(slice->perThreadData, v1.toString());
+    RootedString str2(slice->perThreadData, v2.toString());
+    return ParCompareStrings(slice, str1, str2, res);
+}
+
+template<bool Equal>
+ParallelResult
+ParLooselyEqualImpl(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    PAR_RELATIONAL_OP(==, Equal);
+}
+
+ParallelResult
+js::ion::ParLooselyEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    return ParLooselyEqualImpl<true>(slice, lhs, rhs, res);
+}
+
+ParallelResult
+js::ion::ParLooselyUnequal(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    return ParLooselyEqualImpl<false>(slice, lhs, rhs, res);
+}
+
+template<bool Equal>
+ParallelResult
+ParStrictlyEqualImpl(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    if (lhs.isNumber()) {
+        if (rhs.isNumber()) {
+            *res = (lhs.toNumber() == rhs.toNumber()) == Equal;
+            return TP_SUCCESS;
+        }
+    } else if (lhs.isBoolean()) {
+        if (rhs.isBoolean()) {
+            *res = (lhs.toBoolean() == rhs.toBoolean()) == Equal;
+            return TP_SUCCESS;
+        }
+    } else if (lhs.isNull()) {
+        if (rhs.isNull()) {
+            *res = Equal;
+            return TP_SUCCESS;
+        }
+    } else if (lhs.isUndefined()) {
+        if (rhs.isUndefined()) {
+            *res = Equal;
+            return TP_SUCCESS;
+        }
+    } else if (lhs.isObject()) {
+        if (rhs.isObject()) {
+            *res = (lhs.toObjectOrNull() == rhs.toObjectOrNull()) == Equal;
+            return TP_SUCCESS;
+        }
+    } else if (lhs.isString()) {
+        if (rhs.isString())
+            return ParLooselyEqualImpl<Equal>(slice, lhs, rhs, res);
+    }
+
+    return TP_RETRY_SEQUENTIALLY;
+}
+
+ParallelResult
+js::ion::ParStrictlyEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    return ParStrictlyEqualImpl<true>(slice, lhs, rhs, res);
+}
+
+ParallelResult
+js::ion::ParStrictlyUnequal(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    return ParStrictlyEqualImpl<false>(slice, lhs, rhs, res);
+}
+
+ParallelResult
+js::ion::ParLessThan(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    PAR_RELATIONAL_OP(<, true);
+}
+
+ParallelResult
+js::ion::ParLessThanOrEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    PAR_RELATIONAL_OP(<=, true);
+}
+
+ParallelResult
+js::ion::ParGreaterThan(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    PAR_RELATIONAL_OP(>, true);
+}
+
+ParallelResult
+js::ion::ParGreaterThanOrEqual(ForkJoinSlice *slice, MutableHandleValue lhs, MutableHandleValue rhs, JSBool *res)
+{
+    PAR_RELATIONAL_OP(>=, true);
+}
+
+template<bool Equal>
+ParallelResult
+ParStringsEqualImpl(ForkJoinSlice *slice, HandleString lhs, HandleString rhs, JSBool *res)
+{
+    int32_t vsZero;
+    ParallelResult ret = ParCompareStrings(slice, lhs, rhs, &vsZero);
+    if (ret != TP_SUCCESS)
+        return ret;
+    *res = (vsZero == 0) == Equal;
+    return TP_SUCCESS;
+}
+
+ParallelResult
+js::ion::ParStringsEqual(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *res)
+{
+    return ParStringsEqualImpl<true>(slice, v1, v2, res);
+}
+
+ParallelResult
+js::ion::ParStringsUnequal(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *res)
+{
+    return ParStringsEqualImpl<false>(slice, v1, v2, res);
 }
 
 void
 ion::ParallelAbort(JSScript *script)
 {
     JS_ASSERT(InParallelSection());
 
     ForkJoinSlice *slice = ForkJoinSlice::Current();
--- a/js/src/ion/ParallelFunctions.h
+++ b/js/src/ion/ParallelFunctions.h
@@ -36,22 +36,29 @@ struct ParPushArgs {
 // during code generation.
 JSObject* ParPush(ParPushArgs *args);
 
 // Extends the given array with `length` new holes.  Returns NULL on
 // failure or else `array`, which is convenient during code
 // generation.
 JSObject *ParExtendArray(ForkJoinSlice *slice, JSObject *array, uint32_t length);
 
-enum ParCompareResult {
-    ParCompareNe = false,
-    ParCompareEq = true,
-    ParCompareUnknown = 2
-};
-ParCompareResult ParCompareStrings(JSString *str1, JSString *str2);
+// These parallel operations fail if they would be required to convert
+// to a string etc etc.
+ParallelResult ParStrictlyEqual(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+ParallelResult ParStrictlyUnequal(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+ParallelResult ParLooselyEqual(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+ParallelResult ParLooselyUnequal(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+ParallelResult ParLessThan(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+ParallelResult ParLessThanOrEqual(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+ParallelResult ParGreaterThan(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+ParallelResult ParGreaterThanOrEqual(ForkJoinSlice *slice, MutableHandleValue v1, MutableHandleValue v2, JSBool *);
+
+ParallelResult ParStringsEqual(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *);
+ParallelResult ParStringsUnequal(ForkJoinSlice *slice, HandleString v1, HandleString v2, JSBool *);
 
 void ParallelAbort(JSScript *script);
 
 void TraceLIR(uint32_t bblock, uint32_t lir, uint32_t execModeInt,
               const char *lirOpName, const char *mirOpName,
               JSScript *script, jsbytecode *pc);
 
 void ParCallToUncompiledScript(JSFunction *func);
--- a/js/src/ion/RangeAnalysis.cpp
+++ b/js/src/ion/RangeAnalysis.cpp
@@ -745,16 +745,22 @@ MTruncateToInt32::computeRange()
 
 void
 MToInt32::computeRange()
 {
     Range input(getOperand(0));
     setRange(new Range(input.lower(), input.upper()));
 }
 
+void
+MLoadTypedArrayElementStatic::computeRange()
+{
+    setRange(new Range(this));
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Range Analysis
 ///////////////////////////////////////////////////////////////////////////////
 
 void
 RangeAnalysis::markBlocksInLoopBody(MBasicBlock *header, MBasicBlock *current)
 {
     // Visited.
@@ -1332,16 +1338,23 @@ MToDouble::truncate()
     setResultType(MIRType_Int32);
     if (range())
         range()->truncate();
 
     return true;
 }
 
 bool
+MLoadTypedArrayElementStatic::truncate()
+{
+    setInfallible();
+    return false;
+}
+
+bool
 MDefinition::isOperandTruncated(size_t index) const
 {
     return false;
 }
 
 bool
 MTruncateToInt32::isOperandTruncated(size_t index) const
 {
--- a/js/src/ion/TypePolicy.cpp
+++ b/js/src/ion/TypePolicy.cpp
@@ -544,16 +544,25 @@ StoreTypedArrayHolePolicy::adjustInputs(
     JS_ASSERT(store->elements()->type() == MIRType_Elements);
     JS_ASSERT(store->index()->type() == MIRType_Int32);
     JS_ASSERT(store->length()->type() == MIRType_Int32);
 
     return adjustValueInput(ins, store->arrayType(), store->value(), 3);
 }
 
 bool
+StoreTypedArrayElementStaticPolicy::adjustInputs(MInstruction *ins)
+{
+    MStoreTypedArrayElementStatic *store = ins->toStoreTypedArrayElementStatic();
+    JS_ASSERT(store->ptr()->type() == MIRType_Int32);
+
+    return adjustValueInput(ins, store->viewType(), store->value(), 1);
+}
+
+bool
 ClampPolicy::adjustInputs(MInstruction *ins)
 {
     MDefinition *in = ins->toClampToUint8()->input();
 
     switch (in->type()) {
       case MIRType_Int32:
       case MIRType_Double:
       case MIRType_Value:
--- a/js/src/ion/TypePolicy.h
+++ b/js/src/ion/TypePolicy.h
@@ -224,16 +224,22 @@ class StoreTypedArrayPolicy : public Box
 };
 
 class StoreTypedArrayHolePolicy : public StoreTypedArrayPolicy
 {
   public:
     bool adjustInputs(MInstruction *ins);
 };
 
+class StoreTypedArrayElementStaticPolicy : public StoreTypedArrayPolicy
+{
+  public:
+    bool adjustInputs(MInstruction *ins);
+};
+
 // Accepts integers and doubles. Everything else is boxed.
 class ClampPolicy : public BoxInputsPolicy
 {
   public:
     bool adjustInputs(MInstruction *ins);
 };
 
 static inline bool
--- a/js/src/ion/arm/Assembler-arm.h
+++ b/js/src/ion/arm/Assembler-arm.h
@@ -50,17 +50,17 @@ static const Register ScratchRegister = 
 static const Register OsrFrameReg = r3;
 static const Register ArgumentsRectifierReg = r8;
 static const Register CallTempReg0 = r5;
 static const Register CallTempReg1 = r6;
 static const Register CallTempReg2 = r7;
 static const Register CallTempReg3 = r8;
 static const Register CallTempReg4 = r0;
 static const Register CallTempReg5 = r1;
-
+static const Register CallTempReg6 = r2;
 
 static const Register IntArgReg0 = r0;
 static const Register IntArgReg1 = r1;
 static const Register IntArgReg2 = r2;
 static const Register IntArgReg3 = r3;
 static const Register GlobalReg = r10;
 static const Register HeapReg = r11;
 static const Register CallTempNonArgRegs[] = { r5, r6, r7, r8 };
--- a/js/src/ion/arm/CodeGenerator-arm.cpp
+++ b/js/src/ion/arm/CodeGenerator-arm.cpp
@@ -1657,16 +1657,30 @@ getBase(U *mir)
     switch (mir->base()) {
       case U::Heap: return HeapReg;
       case U::Global: return GlobalReg;
     }
     return InvalidReg;
 }
 
 bool
+CodeGeneratorARM::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins)
+{
+    JS_NOT_REACHED("NYI");
+    return true;
+}
+
+bool
+CodeGeneratorARM::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins)
+{
+    JS_NOT_REACHED("NYI");
+    return true;
+}
+
+bool
 CodeGeneratorARM::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
     const MAsmJSLoadHeap *mir = ins->mir();
     bool isSigned;
     int size;
     bool isFloat = false;
     switch (mir->viewType()) {
       case ArrayBufferView::TYPE_INT8:    isSigned = true; size = 8; break;
--- a/js/src/ion/arm/CodeGenerator-arm.h
+++ b/js/src/ion/arm/CodeGenerator-arm.h
@@ -138,16 +138,18 @@ class CodeGeneratorARM : public CodeGene
     bool visitGuardObjectType(LGuardObjectType *guard);
     bool visitGuardClass(LGuardClass *guard);
     bool visitImplicitThis(LImplicitThis *lir);
 
     bool visitInterruptCheck(LInterruptCheck *lir);
 
     bool visitNegI(LNegI *lir);
     bool visitNegD(LNegD *lir);
+    bool visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins);
+    bool visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins);
     bool visitAsmJSLoadHeap(LAsmJSLoadHeap *ins);
     bool visitAsmJSStoreHeap(LAsmJSStoreHeap *ins);
     bool visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins);
     bool visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar *ins);
     bool visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr *ins);
     bool visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc *ins);
 
     bool visitAsmJSPassStackArg(LAsmJSPassStackArg *ins);
--- a/js/src/ion/arm/Lowering-arm.cpp
+++ b/js/src/ion/arm/Lowering-arm.cpp
@@ -456,9 +456,16 @@ bool
 LIRGeneratorARM::lowerTruncateDToInt32(MTruncateToInt32 *ins)
 {
     MDefinition *opd = ins->input();
     JS_ASSERT(opd->type() == MIRType_Double);
 
     return define(new LTruncateDToInt32(useRegister(opd), LDefinition::BogusTemp()), ins);
 }
 
+bool
+LIRGeneratorARM::visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins)
+{
+    JS_NOT_REACHED("NYI");
+    return true;
+}
+
 //__aeabi_uidiv
--- a/js/src/ion/arm/Lowering-arm.h
+++ b/js/src/ion/arm/Lowering-arm.h
@@ -67,16 +67,17 @@ class LIRGeneratorARM : public LIRGenera
     bool visitGuardShape(MGuardShape *ins);
     bool visitGuardObjectType(MGuardObjectType *ins);
     bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins);
     bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins);
     bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins);
     bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
     bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
     bool visitInterruptCheck(MInterruptCheck *ins);
+    bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
 };
 
 typedef LIRGeneratorARM LIRGeneratorSpecific;
 
 } // namespace ion
 } // namespace js
 
 #endif // jsion_ion_lowering_arm_h__
--- a/js/src/ion/arm/MacroAssembler-arm.cpp
+++ b/js/src/ion/arm/MacroAssembler-arm.cpp
@@ -2295,25 +2295,43 @@ MacroAssemblerARMCompat::testGCThing(Ass
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     extractTag(address, ScratchRegister);
     ma_cmp(ScratchRegister, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
     return cond == Equal ? AboveOrEqual : Below;
 }
 
 Assembler::Condition
+MacroAssemblerARMCompat::testGCThing(Assembler::Condition cond, const BaseIndex &address)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(address, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
+    return cond == Equal ? AboveOrEqual : Below;
+}
+
+Assembler::Condition
 MacroAssemblerARMCompat::testMagic(Assembler::Condition cond, const Address &address)
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     extractTag(address, ScratchRegister);
     ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_MAGIC));
     return cond;
 }
 
 Assembler::Condition
+MacroAssemblerARMCompat::testMagic(Assembler::Condition cond, const BaseIndex &address)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(address, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_MAGIC));
+    return cond;
+}
+
+Assembler::Condition
 MacroAssemblerARMCompat::testInt32(Assembler::Condition cond, const Address &address)
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     extractTag(address, ScratchRegister);
     ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_INT32));
     return cond;
 }
 
@@ -2337,98 +2355,16 @@ MacroAssemblerARMCompat::testDouble(Cond
 Assembler::Condition
 MacroAssemblerARMCompat::testNumber(Condition cond, const Register &tag)
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     ma_cmp(tag, ImmTag(JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET));
     return cond == Equal ? BelowOrEqual : Above;
 }
 
-Assembler::Condition
-MacroAssemblerARMCompat::testUndefined(Condition cond, const BaseIndex &src)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(src, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_UNDEFINED));
-    return cond;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testNull(Condition cond, const BaseIndex &src)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(src, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_NULL));
-    return cond;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testBoolean(Condition cond, const BaseIndex &src)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(src, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_BOOLEAN));
-    return cond;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testString(Condition cond, const BaseIndex &src)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(src, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_STRING));
-    return cond;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testInt32(Condition cond, const BaseIndex &src)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(src, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_INT32));
-    return cond;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testObject(Condition cond, const BaseIndex &src)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(src, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_OBJECT));
-    return cond;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testDouble(Condition cond, const BaseIndex &src)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    Assembler::Condition actual = (cond == Equal) ? Below : AboveOrEqual;
-    extractTag(src, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_CLEAR));
-    return actual;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testMagic(Condition cond, const BaseIndex &address)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(address, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_MAGIC));
-    return cond;
-}
-
-Assembler::Condition
-MacroAssemblerARMCompat::testGCThing(Condition cond, const BaseIndex &address)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(address, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
-    return cond == Equal ? AboveOrEqual : Below;
-}
-
 void
 MacroAssemblerARMCompat::branchTestValue(Condition cond, const ValueOperand &value, const Value &v,
                                          Label *label)
 {
     // If cond == NotEqual, branch when a.payload != b.payload || a.tag != b.tag.
     // If the payloads are equal, compare the tags. If the payloads are not equal,
     // short circuit true (NotEqual).
     //
--- a/js/src/ion/arm/MacroAssembler-arm.h
+++ b/js/src/ion/arm/MacroAssembler-arm.h
@@ -637,30 +637,22 @@ class MacroAssemblerARMCompat : public M
     Condition testString(Condition cond, const Register &tag);
     Condition testObject(Condition cond, const Register &tag);
     Condition testDouble(Condition cond, const Register &tag);
     Condition testNumber(Condition cond, const Register &tag);
     Condition testMagic(Condition cond, const Register &tag);
     Condition testPrimitive(Condition cond, const Register &tag);
 
     Condition testGCThing(Condition cond, const Address &address);
+    Condition testGCThing(Condition cond, const BaseIndex &address);
     Condition testMagic(Condition cond, const Address &address);
+    Condition testMagic(Condition cond, const BaseIndex &address);
     Condition testInt32(Condition cond, const Address &address);
     Condition testDouble(Condition cond, const Address &address);
 
-    Condition testUndefined(Condition cond, const BaseIndex &src);
-    Condition testNull(Condition cond, const BaseIndex &src);
-    Condition testBoolean(Condition cond, const BaseIndex &src);
-    Condition testString(Condition cond, const BaseIndex &src);
-    Condition testInt32(Condition cond, const BaseIndex &src);
-    Condition testObject(Condition cond, const BaseIndex &src);
-    Condition testDouble(Condition cond, const BaseIndex &src);
-    Condition testMagic(Condition cond, const BaseIndex &src);
-    Condition testGCThing(Condition cond, const BaseIndex &src);
-
     template <typename T>
     void branchTestGCThing(Condition cond, const T &t, Label *label) {
         Condition c = testGCThing(cond, t);
         ma_b(label, c);
     }
     template <typename T>
     void branchTestPrimitive(Condition cond, const T &t, Label *label) {
         Condition c = testPrimitive(cond, t);
@@ -848,16 +840,19 @@ class MacroAssemblerARMCompat : public M
         branchTest32(cond, ScratchRegister, imm, label);
     }
     void branchTestPtr(Condition cond, const Register &lhs, const Register &rhs, Label *label) {
         branchTest32(cond, lhs, rhs, label);
     }
     void branchTestPtr(Condition cond, const Register &lhs, const Imm32 rhs, Label *label) {
         branchTest32(cond, lhs, rhs, label);
     }
+    void branchTestPtr(Condition cond, const Address &lhs, Imm32 imm, Label *label) {
+        branchTest32(cond, lhs, imm, label);
+    }
     void branchPtr(Condition cond, Register lhs, Register rhs, Label *label) {
         branch32(cond, lhs, rhs, label);
     }
     void branchPtr(Condition cond, Register lhs, ImmGCPtr ptr, Label *label) {
         movePtr(ptr, ScratchRegister);
         branchPtr(cond, lhs, ScratchRegister, label);
     }
     void branchPtr(Condition cond, Register lhs, ImmWord imm, Label *label) {
--- a/js/src/ion/shared/LIR-x86-shared.h
+++ b/js/src/ion/shared/LIR-x86-shared.h
@@ -16,16 +16,30 @@ class LDivI : public LBinaryMath<1>
     LIR_HEADER(DivI)
 
     LDivI(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp) {
         setOperand(0, lhs);
         setOperand(1, rhs);
         setTemp(0, temp);
     }
 
+    const char *extraName() const {
+        if (mir()->isTruncated()) {
+            if (mir()->canBeNegativeZero()) {
+                return mir()->canBeNegativeOverflow()
+                       ? "Truncate_NegativeZero_NegativeOverflow"
+                       : "Truncate_NegativeZero";
+            }
+            return mir()->canBeNegativeOverflow() ? "Truncate_NegativeOverflow" : "Truncate";
+        }
+        if (mir()->canBeNegativeZero())
+            return mir()->canBeNegativeOverflow() ? "NegativeZero_NegativeOverflow" : "NegativeZero";
+        return mir()->canBeNegativeOverflow() ? "NegativeOverflow" : NULL;
+    }
+
     const LDefinition *remainder() {
         return getTemp(0);
     }
     MDiv *mir() const {
         return mir_->toDiv();
     }
 };
 
@@ -35,16 +49,20 @@ class LModI : public LBinaryMath<1>
     LIR_HEADER(ModI)
 
     LModI(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp) {
         setOperand(0, lhs);
         setOperand(1, rhs);
         setTemp(0, temp);
     }
 
+    const char *extraName() const {
+        return mir()->isTruncated() ? "Truncated" : NULL;
+    }
+
     const LDefinition *remainder() {
         return getDef(0);
     }
     MMod *mir() const {
         return mir_->toMod();
     }
 };
 
@@ -211,17 +229,23 @@ class LMulI : public LBinaryMath<0, 1>
     LIR_HEADER(MulI)
 
     LMulI(const LAllocation &lhs, const LAllocation &rhs, const LAllocation &lhsCopy) {
         setOperand(0, lhs);
         setOperand(1, rhs);
         setOperand(2, lhsCopy);
     }
 
-    MMul *mir() {
+    const char *extraName() const {
+        return (mir()->mode() == MMul::Integer)
+               ? "Integer"
+               : (mir()->canBeNegativeZero() ? "CanBeNegativeZero" : NULL);
+    }
+
+    MMul *mir() const {
         return mir_->toMul();
     }
     const LAllocation *lhsCopy() {
         return this->getOperand(2);
     }
 };
 
 // Constant double.
--- a/js/src/ion/shared/Lowering-shared.h
+++ b/js/src/ion/shared/Lowering-shared.h
@@ -170,16 +170,21 @@ class LIRGeneratorShared : public MInstr
 
   public:
     bool visitConstant(MConstant *ins);
 
     // Whether to generate typed reads for element accesses with hole checks.
     static bool allowTypedElementHoleCheck() {
         return false;
     }
+
+    // Whether to generate typed array accesses on statically known objects.
+    static bool allowStaticTypedArrayAccesses() {
+        return false;
+    }
 };
 
 } // namespace ion
 } // namespace js
 
 #endif // jsion_lowering_shared_h__
 
 
--- a/js/src/ion/x64/Assembler-x64.h
+++ b/js/src/ion/x64/Assembler-x64.h
@@ -77,16 +77,17 @@ static const FloatRegister ScratchFloatR
 
 static const Register ArgumentsRectifierReg = r8;
 static const Register CallTempReg0 = rax;
 static const Register CallTempReg1 = rdi;
 static const Register CallTempReg2 = rbx;
 static const Register CallTempReg3 = rcx;
 static const Register CallTempReg4 = rsi;
 static const Register CallTempReg5 = rdx;
+static const Register CallTempReg6 = rbp;
 
 // Different argument registers for WIN64
 #if defined(_WIN64)
 static const Register IntArgReg0 = rcx;
 static const Register IntArgReg1 = rdx;
 static const Register IntArgReg2 = r8;
 static const Register IntArgReg3 = r9;
 static const uint32_t NumIntArgRegs = 4;
@@ -621,16 +622,29 @@ class Assembler : public AssemblerX86Sha
     }
 
     void testq(const Register &lhs, Imm32 rhs) {
         masm.testq_i32r(rhs.value, lhs.code());
     }
     void testq(const Register &lhs, const Register &rhs) {
         masm.testq_rr(rhs.code(), lhs.code());
     }
+    void testq(const Operand &lhs, Imm32 rhs) {
+        switch (lhs.kind()) {
+          case Operand::REG:
+            masm.testq_i32r(rhs.value, lhs.reg());
+            break;
+          case Operand::REG_DISP:
+            masm.testq_i32m(rhs.value, lhs.disp(), lhs.base());
+            break;
+          default:
+            JS_NOT_REACHED("unexpected operand kind");
+            break;
+        }
+    }
 
     void jmp(void *target, Relocation::Kind reloc = Relocation::HARDCODED) {
         JmpSrc src = masm.jmp();
         addPendingJump(src, target, reloc);
     }
     void j(Condition cond, void *target,
            Relocation::Kind reloc = Relocation::HARDCODED) {
         JmpSrc src = masm.jCC(static_cast<JSC::X86Assembler::Condition>(cond));
--- a/js/src/ion/x64/CodeGenerator-x64.cpp
+++ b/js/src/ion/x64/CodeGenerator-x64.cpp
@@ -380,16 +380,30 @@ CodeGeneratorX64::visitCompareVAndBranch
 bool
 CodeGeneratorX64::visitUInt32ToDouble(LUInt32ToDouble *lir)
 {
     masm.convertUInt32ToDouble(ToRegister(lir->input()), ToFloatRegister(lir->output()));
     return true;
 }
 
 bool
+CodeGeneratorX64::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins)
+{
+    JS_NOT_REACHED("NYI");
+    return true;
+}
+
+bool
+CodeGeneratorX64::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins)
+{
+    JS_NOT_REACHED("NYI");
+    return true;
+}
+
+bool
 CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
     MAsmJSLoadHeap *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
 
     Operand srcAddr(HeapReg, ToRegister(ins->ptr()), TimesOne);
 
     if (vt == ArrayBufferView::TYPE_FLOAT32) {
--- a/js/src/ion/x64/CodeGenerator-x64.h
+++ b/js/src/ion/x64/CodeGenerator-x64.h
@@ -47,16 +47,18 @@ class CodeGeneratorX64 : public CodeGene
     bool visitImplicitThis(LImplicitThis *lir);
     bool visitInterruptCheck(LInterruptCheck *lir);
     bool visitCompareB(LCompareB *lir);
     bool visitCompareBAndBranch(LCompareBAndBranch *lir);
     bool visitCompareV(LCompareV *lir);
     bool visitCompareVAndBranch(LCompareVAndBranch *lir);
     bool visitUInt32ToDouble(LUInt32ToDouble *lir);
     bool visitTruncateDToInt32(LTruncateDToInt32 *ins);
+    bool visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins);
+    bool visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins);
     bool visitAsmJSLoadHeap(LAsmJSLoadHeap *ins);
     bool visitAsmJSStoreHeap(LAsmJSStoreHeap *ins);
     bool visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins);
     bool visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar *ins);
     bool visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr *ins);
     bool visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc *ins);
 
     void postAsmJSCall(LAsmJSCall *lir) {}
--- a/js/src/ion/x64/Lowering-x64.cpp
+++ b/js/src/ion/x64/Lowering-x64.cpp
@@ -209,8 +209,15 @@ LIRGeneratorX64::newLGetPropertyCacheT(M
 bool
 LIRGeneratorX64::lowerTruncateDToInt32(MTruncateToInt32 *ins)
 {
     MDefinition *opd = ins->input();
     JS_ASSERT(opd->type() == MIRType_Double);
 
     return define(new LTruncateDToInt32(useRegister(opd), LDefinition::BogusTemp()), ins);
 }
+
+bool
+LIRGeneratorX64::visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins)
+{
+    JS_NOT_REACHED("NYI");
+    return true;
+}
--- a/js/src/ion/x64/Lowering-x64.h
+++ b/js/src/ion/x64/Lowering-x64.h
@@ -44,16 +44,17 @@ class LIRGeneratorX64 : public LIRGenera
     bool visitBox(MBox *box);
     bool visitUnbox(MUnbox *unbox);
     bool visitReturn(MReturn *ret);
     bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins);
     bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins);
     bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins);
     bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
     bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
+    bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
 };
 
 typedef LIRGeneratorX64 LIRGeneratorSpecific;
 
 } // namespace ion
 } // namespace js
 
 #endif // jsion_ion_lowering_x64_h__
--- a/js/src/ion/x64/MacroAssembler-x64.h
+++ b/js/src/ion/x64/MacroAssembler-x64.h
@@ -313,63 +313,33 @@ class MacroAssemblerX64 : public MacroAs
     Condition testGCThing(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testGCThing(cond, ScratchReg);
     }
     Condition testGCThing(Condition cond, const Address &src) {
         splitTag(src, ScratchReg);
         return testGCThing(cond, ScratchReg);
     }
+    Condition testGCThing(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testGCThing(cond, ScratchReg);
+    }
     Condition testMagic(Condition cond, const Address &src) {
         splitTag(src, ScratchReg);
         return testMagic(cond, ScratchReg);
     }
+    Condition testMagic(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testMagic(cond, ScratchReg);
+    }
     Condition testPrimitive(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testPrimitive(cond, ScratchReg);
     }
 
-
-    Condition testUndefined(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testUndefined(cond, ScratchReg);
-    }
-    Condition testNull(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testNull(cond, ScratchReg);
-    }
-    Condition testBoolean(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testBoolean(cond, ScratchReg);
-    }
-    Condition testString(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testString(cond, ScratchReg);
-    }
-    Condition testInt32(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testInt32(cond, ScratchReg);
-    }
-    Condition testObject(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testObject(cond, ScratchReg);
-    }
-    Condition testDouble(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testDouble(cond, ScratchReg);
-    }
-    Condition testMagic(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testMagic(cond, ScratchReg);
-    }
-    Condition testGCThing(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testGCThing(cond, ScratchReg);
-    }
-
     Condition isMagic(Condition cond, const ValueOperand &src, JSWhyMagic why) {
         uint64_t magic = MagicValue(why).asRawBits();
         cmpPtr(src.valueReg(), ImmWord(magic));
         return cond;
     }
 
     void cmpPtr(const Register &lhs, const ImmWord rhs) {
         JS_ASSERT(lhs != ScratchReg);
@@ -505,16 +475,20 @@ class MacroAssemblerX64 : public MacroAs
     void branchTestPtr(Condition cond, Register lhs, Register rhs, Label *label) {
         testq(lhs, rhs);
         j(cond, label);
     }
     void branchTestPtr(Condition cond, Register lhs, Imm32 imm, Label *label) {
         testq(lhs, imm);
         j(cond, label);
     }
+    void branchTestPtr(Condition cond, const Address &lhs, Imm32 imm, Label *label) {
+        testq(Operand(lhs), imm);
+        j(cond, label);
+    }
     void decBranchPtr(Condition cond, const Register &lhs, Imm32 imm, Label *label) {
         subPtr(imm, lhs);
         j(cond, label);
     }
 
     void movePtr(const Register &src, const Register &dest) {
         movq(src, dest);
     }
--- a/js/src/ion/x86/Assembler-x86.h
+++ b/js/src/ion/x86/Assembler-x86.h
@@ -49,16 +49,17 @@ static const FloatRegister ScratchFloatR
 
 static const Register ArgumentsRectifierReg = esi;
 static const Register CallTempReg0 = edi;
 static const Register CallTempReg1 = eax;
 static const Register CallTempReg2 = ebx;
 static const Register CallTempReg3 = ecx;
 static const Register CallTempReg4 = esi;
 static const Register CallTempReg5 = edx;
+static const Register CallTempReg6 = ebp;
 
 // We have no arg regs, so our NonArgRegs are just our CallTempReg*
 static const Register CallTempNonArgRegs[] = { edi, eax, ebx, ecx, esi, edx };
 static const uint32_t NumCallTempNonArgRegs =
     mozilla::ArrayLength(CallTempNonArgRegs);
 
 class ABIArgGenerator
 {
--- a/js/src/ion/x86/CodeGenerator-x86.cpp
+++ b/js/src/ion/x86/CodeGenerator-x86.cpp
@@ -392,35 +392,94 @@ CodeGeneratorX86::visitUInt32ToDouble(LU
     if (input != temp)
         masm.mov(input, temp);
 
     // Beware: convertUInt32ToDouble clobbers input.
     masm.convertUInt32ToDouble(temp, ToFloatRegister(lir->output()));
     return true;
 }
 
-class ion::OutOfLineAsmJSLoadHeapOutOfBounds : public OutOfLineCodeBase<CodeGeneratorX86>
+// Load a NaN or zero into a register for an out of bounds AsmJS or static
+// typed array load.
+class ion::OutOfLineLoadTypedArrayOutOfBounds : public OutOfLineCodeBase<CodeGeneratorX86>
 {
     AnyRegister dest_;
   public:
-    OutOfLineAsmJSLoadHeapOutOfBounds(AnyRegister dest) : dest_(dest) {}
+    OutOfLineLoadTypedArrayOutOfBounds(AnyRegister dest) : dest_(dest) {}
     const AnyRegister &dest() const { return dest_; }
-    bool accept(CodeGeneratorX86 *codegen) { return codegen->visitOutOfLineAsmJSLoadHeapOutOfBounds(this); }
+    bool accept(CodeGeneratorX86 *codegen) { return codegen->visitOutOfLineLoadTypedArrayOutOfBounds(this); }
 };
 
+void
+CodeGeneratorX86::loadViewTypeElement(ArrayBufferView::ViewType vt, const Address &srcAddr,
+                                      const LDefinition *out)
+{
+    switch (vt) {
+      case ArrayBufferView::TYPE_INT8:    masm.movxblWithPatch(srcAddr, ToRegister(out)); break;
+      case ArrayBufferView::TYPE_UINT8_CLAMPED:
+      case ArrayBufferView::TYPE_UINT8:   masm.movzblWithPatch(srcAddr, ToRegister(out)); break;
+      case ArrayBufferView::TYPE_INT16:   masm.movxwlWithPatch(srcAddr, ToRegister(out)); break;
+      case ArrayBufferView::TYPE_UINT16:  masm.movzwlWithPatch(srcAddr, ToRegister(out)); break;
+      case ArrayBufferView::TYPE_INT32:   masm.movlWithPatch(srcAddr, ToRegister(out)); break;
+      case ArrayBufferView::TYPE_UINT32:  masm.movlWithPatch(srcAddr, ToRegister(out)); break;
+      case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(srcAddr, ToFloatRegister(out)); break;
+      default: JS_NOT_REACHED("unexpected array type");
+    }
+}
+
+bool
+CodeGeneratorX86::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins)
+{
+    const MLoadTypedArrayElementStatic *mir = ins->mir();
+    ArrayBufferView::ViewType vt = mir->viewType();
+
+    Register ptr = ToRegister(ins->ptr());
+    const LDefinition *out = ins->output();
+
+    OutOfLineLoadTypedArrayOutOfBounds *ool = NULL;
+    if (!mir->fallible()) {
+        ool = new OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out));
+        if (!addOutOfLineCode(ool))
+            return false;
+    }
+
+    masm.cmpl(ptr, Imm32(mir->length()));
+    if (ool)
+        masm.j(Assembler::AboveOrEqual, ool->entry());
+    else if (!bailoutIf(Assembler::AboveOrEqual, ins->snapshot()))
+        return false;
+
+    Address srcAddr(ptr, (int32_t) mir->base());
+    if (vt == ArrayBufferView::TYPE_FLOAT32) {
+        FloatRegister dest = ToFloatRegister(out);
+        masm.movssWithPatch(srcAddr, dest);
+        masm.cvtss2sd(dest, dest);
+        if (ool)
+            masm.bind(ool->rejoin());
+        return true;
+    }
+    loadViewTypeElement(vt, srcAddr, out);
+    if (ool)
+        masm.bind(ool->rejoin());
+    return true;
+}
+
 bool
 CodeGeneratorX86::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
+    // This is identical to LoadTypedArrayElementStatic, except that the
+    // array's base and length are not known ahead of time and can be patched
+    // later on, and the instruction is always infallible.
     const MAsmJSLoadHeap *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
 
     Register ptr = ToRegister(ins->ptr());
     const LDefinition *out = ins->output();
 
-    OutOfLineAsmJSLoadHeapOutOfBounds *ool = new OutOfLineAsmJSLoadHeapOutOfBounds(ToAnyRegister(out));
+    OutOfLineLoadTypedArrayOutOfBounds *ool = new OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out));
     if (!addOutOfLineCode(ool))
         return false;
 
     CodeOffsetLabel cmp = masm.cmplWithPatch(ptr, Imm32(0));
     masm.j(Assembler::AboveOrEqual, ool->entry());
 
     Address srcAddr(ptr, 0);
     if (vt == ArrayBufferView::TYPE_FLOAT32) {
@@ -428,45 +487,81 @@ CodeGeneratorX86::visitAsmJSLoadHeap(LAs
         uint32_t before = masm.size();
         masm.movssWithPatch(srcAddr, dest);
         uint32_t after = masm.size();
         masm.cvtss2sd(dest, dest);
         masm.bind(ool->rejoin());
         return gen->noteHeapAccess(AsmJSHeapAccess(cmp.offset(), before, after, vt, AnyRegister(dest)));
     }
     uint32_t before = masm.size();
-    switch (vt) {
-      case ArrayBufferView::TYPE_INT8:    masm.movxblWithPatch(srcAddr, ToRegister(out)); break;
-      case ArrayBufferView::TYPE_UINT8:   masm.movzblWithPatch(srcAddr, ToRegister(out)); break;
-      case ArrayBufferView::TYPE_INT16:   masm.movxwlWithPatch(srcAddr, ToRegister(out)); break;
-      case ArrayBufferView::TYPE_UINT16:  masm.movzwlWithPatch(srcAddr, ToRegister(out)); break;
-      case ArrayBufferView::TYPE_INT32:   masm.movlWithPatch(srcAddr, ToRegister(out)); break;
-      case ArrayBufferView::TYPE_UINT32:  masm.movlWithPatch(srcAddr, ToRegister(out)); break;
-      case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(srcAddr, ToFloatRegister(out)); break;
-      default: JS_NOT_REACHED("unexpected array type");
-    }
+    loadViewTypeElement(vt, srcAddr, out);
     uint32_t after = masm.size();
     masm.bind(ool->rejoin());
     return gen->noteHeapAccess(AsmJSHeapAccess(cmp.offset(), before, after, vt, ToAnyRegister(out)));
 }
 
 bool
-CodeGeneratorX86::visitOutOfLineAsmJSLoadHeapOutOfBounds(OutOfLineAsmJSLoadHeapOutOfBounds *ool)
+CodeGeneratorX86::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds *ool)
 {
     if (ool->dest().isFloat())
         masm.movsd(&js_NaN, ool->dest().fpu());
     else
         masm.movl(Imm32(0), ool->dest().gpr());
     masm.jmp(ool->rejoin());
     return true;
 }
 
+void
+CodeGeneratorX86::storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
+                                       const Address &dstAddr)
+{
+    switch (vt) {
+      case ArrayBufferView::TYPE_INT8:    masm.movbWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_UINT8_CLAMPED:
+      case ArrayBufferView::TYPE_UINT8:   masm.movbWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_INT16:   masm.movwWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_UINT16:  masm.movwWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_INT32:   masm.movlWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_UINT32:  masm.movlWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(ToFloatRegister(value), dstAddr); break;
+      default: JS_NOT_REACHED("unexpected array type");
+    }
+}
+
+bool
+CodeGeneratorX86::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins)
+{
+    MStoreTypedArrayElementStatic *mir = ins->mir();
+    ArrayBufferView::ViewType vt = mir->viewType();
+
+    Register ptr = ToRegister(ins->ptr());
+    const LAllocation *value = ins->value();
+
+    masm.cmpl(ptr, Imm32(mir->length()));
+    Label rejoin;
+    masm.j(Assembler::AboveOrEqual, &rejoin);
+
+    Address dstAddr(ptr, (int32_t) mir->base());
+    if (vt == ArrayBufferView::TYPE_FLOAT32) {
+        masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg);
+        masm.movssWithPatch(ScratchFloatReg, dstAddr);
+        masm.bind(&rejoin);
+        return true;
+    }
+    storeViewTypeElement(vt, value, dstAddr);
+    masm.bind(&rejoin);
+    return true;
+}
+
 bool
 CodeGeneratorX86::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins)
 {
+    // This is identical to StoreTypedArrayElementStatic, except that the
+    // array's base and length are not known ahead of time and can be patched
+    // later on.
     MAsmJSStoreHeap *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
 
     Register ptr = ToRegister(ins->ptr());
     const LAllocation *value = ins->value();
 
     CodeOffsetLabel cmp = masm.cmplWithPatch(ptr, Imm32(0));
     Label rejoin;
@@ -477,26 +572,17 @@ CodeGeneratorX86::visitAsmJSStoreHeap(LA
         masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg);
         uint32_t before = masm.size();
         masm.movssWithPatch(ScratchFloatReg, dstAddr);
         uint32_t after = masm.size();
         masm.bind(&rejoin);
         return gen->noteHeapAccess(AsmJSHeapAccess(cmp.offset(), before, after));
     }
     uint32_t before = masm.size();
-    switch (vt) {
-      case ArrayBufferView::TYPE_INT8:    masm.movbWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_UINT8:   masm.movbWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_INT16:   masm.movwWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_UINT16:  masm.movwWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_INT32:   masm.movlWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_UINT32:  masm.movlWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(ToFloatRegister(value), dstAddr); break;
-      default: JS_NOT_REACHED("unexpected array type");
-    }
+    storeViewTypeElement(vt, value, dstAddr);
     uint32_t after = masm.size();
     masm.bind(&rejoin);
     return gen->noteHeapAccess(AsmJSHeapAccess(cmp.offset(), before, after));
 }
 
 bool
 CodeGeneratorX86::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins)
 {
--- a/js/src/ion/x86/CodeGenerator-x86.h
+++ b/js/src/ion/x86/CodeGenerator-x86.h
@@ -8,31 +8,35 @@
 #define jsion_codegen_x86_h__
 
 #include "Assembler-x86.h"
 #include "ion/shared/CodeGenerator-x86-shared.h"
 
 namespace js {
 namespace ion {
 
-class OutOfLineAsmJSLoadHeapOutOfBounds;
+class OutOfLineLoadTypedArrayOutOfBounds;
 class OutOfLineTruncate;
 
 class CodeGeneratorX86 : public CodeGeneratorX86Shared
 {
   private:
     CodeGeneratorX86 *thisFromCtor() {
         return this;
     }
 
   protected:
     ValueOperand ToValue(LInstruction *ins, size_t pos);
     ValueOperand ToOutValue(LInstruction *ins);
     ValueOperand ToTempValue(LInstruction *ins, size_t pos);
 
+    void loadViewTypeElement(ArrayBufferView::ViewType vt, const Address &srcAddr,
+                             const LDefinition *out);
+    void storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
+                              const Address &dstAddr);
     void storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType,
                            const Register &elements, const LAllocation *index);
 
   public:
     CodeGeneratorX86(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm);
 
   public:
     bool visitBox(LBox *box);
@@ -47,24 +51,26 @@ class CodeGeneratorX86 : public CodeGene
     bool visitImplicitThis(LImplicitThis *lir);
     bool visitInterruptCheck(LInterruptCheck *lir);
     bool visitCompareB(LCompareB *lir);
     bool visitCompareBAndBranch(LCompareBAndBranch *lir);
     bool visitCompareV(LCompareV *lir);
     bool visitCompareVAndBranch(LCompareVAndBranch *lir);
     bool visitUInt32ToDouble(LUInt32ToDouble *lir);
     bool visitTruncateDToInt32(LTruncateDToInt32 *ins);
+    bool visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins);
+    bool visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins);
     bool visitAsmJSLoadHeap(LAsmJSLoadHeap *ins);
     bool visitAsmJSStoreHeap(LAsmJSStoreHeap *ins);
     bool visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins);
     bool visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar *ins);
     bool visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr *ins);
     bool visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc *ins);
 
-    bool visitOutOfLineAsmJSLoadHeapOutOfBounds(OutOfLineAsmJSLoadHeapOutOfBounds *ool);
+    bool visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds *ool);
     bool visitOutOfLineTruncate(OutOfLineTruncate *ool);
 
     void postAsmJSCall(LAsmJSCall *lir);
 };
 
 typedef CodeGeneratorX86 CodeGeneratorSpecific;
 
 } // namespace ion
--- a/js/src/ion/x86/Lowering-x86.cpp
+++ b/js/src/ion/x86/Lowering-x86.cpp
@@ -276,16 +276,40 @@ LIRGeneratorX86::visitAsmJSStoreHeap(MAs
         break;
       default: JS_NOT_REACHED("unexpected array type");
     }
 
     return add(lir, ins);
 }
 
 bool
+LIRGeneratorX86::visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins)
+{
+    // The code generated for StoreTypedArrayElementStatic is identical to that
+    // for AsmJSStoreHeap, and the same concerns apply.
+    LStoreTypedArrayElementStatic *lir;
+    switch (ins->viewType()) {
+      case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8:
+      case ArrayBufferView::TYPE_UINT8_CLAMPED:
+        lir = new LStoreTypedArrayElementStatic(useRegister(ins->ptr()),
+                                                useFixed(ins->value(), eax));
+        break;
+      case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16:
+      case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32:
+      case ArrayBufferView::TYPE_FLOAT32: case ArrayBufferView::TYPE_FLOAT64:
+        lir = new LStoreTypedArrayElementStatic(useRegisterAtStart(ins->ptr()),
+                                                useRegisterAtStart(ins->value()));
+        break;
+      default: JS_NOT_REACHED("unexpected array type");
+    }
+
+    return add(lir, ins);
+}
+
+bool
 LIRGeneratorX86::visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins)
 {
     return define(new LAsmJSLoadFuncPtr(useRegisterAtStart(ins->index())), ins);
 }
 
 LGetPropertyCacheT *
 LIRGeneratorX86::newLGetPropertyCacheT(MGetPropertyCache *ins)
 {
--- a/js/src/ion/x86/Lowering-x86.h
+++ b/js/src/ion/x86/Lowering-x86.h
@@ -46,21 +46,26 @@ class LIRGeneratorX86 : public LIRGenera
     bool visitBox(MBox *box);
     bool visitUnbox(MUnbox *unbox);
     bool visitReturn(MReturn *ret);
     bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins);
     bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins);
     bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins);
     bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
     bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
+    bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
     bool lowerPhi(MPhi *phi);
 
     static bool allowTypedElementHoleCheck() {
         return true;
     }
+
+    static bool allowStaticTypedArrayAccesses() {
+        return true;
+    }
 };
 
 typedef LIRGeneratorX86 LIRGeneratorSpecific;
 
 } // namespace js
 } // namespace ion
 
 #endif // jsion_ion_lowering_x86_h__
--- a/js/src/ion/x86/MacroAssembler-x86.h
+++ b/js/src/ion/x86/MacroAssembler-x86.h
@@ -276,21 +276,31 @@ class MacroAssemblerX86 : public MacroAs
         cmpl(tag, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
         return cond == Equal ? AboveOrEqual : Below;
     }
     Condition testGCThing(Condition cond, const Address &address) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(tagOf(address), ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
         return cond == Equal ? AboveOrEqual : Below;
     }
+    Condition testGCThing(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
+        return cond == Equal ? AboveOrEqual : Below;
+    }
     Condition testMagic(Condition cond, const Address &address) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(tagOf(address), ImmTag(JSVAL_TAG_MAGIC));
         return cond;
     }
+    Condition testMagic(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_MAGIC));
+        return cond;
+    }
     Condition testMagic(Condition cond, const Register &tag) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(tag, ImmTag(JSVAL_TAG_MAGIC));
         return cond;
     }
     Condition testMagic(Condition cond, const Operand &operand) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(ToType(operand), ImmTag(JSVAL_TAG_MAGIC));
@@ -355,66 +365,16 @@ class MacroAssemblerX86 : public MacroAs
     }
     Condition testGCThing(Condition cond, const ValueOperand &value) {
         return testGCThing(cond, value.typeReg());
     }
     Condition testPrimitive(Condition cond, const ValueOperand &value) {
         return testPrimitive(cond, value.typeReg());
     }
 
-
-    Condition testUndefined(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_UNDEFINED));
-        return cond;
-    }
-    Condition testNull(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_NULL));
-        return cond;
-    }
-    Condition testBoolean(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_BOOLEAN));
-        return cond;
-    }
-    Condition testString(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_STRING));
-        return cond;
-    }
-    Condition testInt32(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_INT32));
-        return cond;
-    }
-    Condition testObject(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_OBJECT));
-        return cond;
-    }
-    Condition testDouble(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        Condition actual = (cond == Equal) ? Below : AboveOrEqual;
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_CLEAR));
-        return actual;
-    }
-    Condition testMagic(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_MAGIC));
-        return cond;
-    }
-    Condition testGCThing(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
-        return cond == Equal ? AboveOrEqual : Below;
-    }
-
-
-
     void branchTestValue(Condition cond, const ValueOperand &value, const Value &v, Label *label);
     void branchTestValue(Condition cond, const Address &valaddr, const ValueOperand &value,
                          Label *label)
     {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         branchPtr(cond, tagOf(valaddr), value.typeReg(), label);
         branchPtr(cond, payloadOf(valaddr), value.payloadReg(), label);
     }
@@ -539,16 +499,20 @@ class MacroAssemblerX86 : public MacroAs
     void branchTestPtr(Condition cond, Register lhs, Register rhs, Label *label) {
         testl(lhs, rhs);
         j(cond, label);
     }
     void branchTestPtr(Condition cond, Register lhs, Imm32 imm, Label *label) {
         testl(lhs, imm);
         j(cond, label);
     }
+    void branchTestPtr(Condition cond, const Address &lhs, Imm32 imm, Label *label) {
+        testl(Operand(lhs), imm);
+        j(cond, label);
+    }
     void decBranchPtr(Condition cond, const Register &lhs, Imm32 imm, Label *label) {
         subPtr(imm, lhs);
         j(cond, label);
     }
 
     void movePtr(ImmWord imm, Register dest) {
         movl(Imm32(imm.value), dest);
     }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/string-concat-short.js
@@ -0,0 +1,13 @@
+function f() {
+    var res = 0;
+    for (var i=0; i<100; i++) {
+	var s = "test" + i;
+	res += s.length;
+	assertEq(s[0], "t");
+	assertEq(s[3], "t");
+	if (i > 90)
+	    assertEq(s[4], "9");
+    }
+    return res;
+}
+assertEq(f(), 590);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallelarray/binary-arith-numbers.js
@@ -0,0 +1,29 @@
+load(libdir + "parallelarray-helpers.js");
+
+// Test that we are able to add numbers even if the typesets are not
+// "clean" because we have previously added strings and numbers.  This
+// should cause fallible unboxing to occur.
+
+function theTest() {
+  var mixedArray = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.1,
+                    "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
+
+  function op(e, i) {
+    return mixedArray[e % mixedArray.length] + i;
+  }
+
+  // run op once where it has to add doubles and strings,
+  // just to pullute the typesets:
+  var jsarray0 = range(0, 1024);
+  jsarray0.map(op);
+
+  // this version will never actually touch the strings:
+  var jsarray1 = range(0, 1024).map(i => i % 10);
+  compareAgainstArray(jsarray1, "map", op);
+
+  // but if we try against the original we get bailouts:
+  new ParallelArray(jsarray0).map(op, {mode:"par", expect:"disqualified"});
+}
+
+if (getBuildConfiguration().parallelJS)
+  theTest();
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4331,44 +4331,16 @@ JS_PUBLIC_API(JSBool)
 JS_GetUCProperty(JSContext *cx, JSObject *objArg, const jschar *name, size_t namelen, jsval *vp)
 {
     RootedObject obj(cx, objArg);
     JSAtom *atom = AtomizeChars<CanGC>(cx, name, AUTO_NAMELEN(name, namelen));
     return atom && JS_GetPropertyById(cx, obj, AtomToId(atom), vp);
 }
 
 JS_PUBLIC_API(JSBool)
-JS_GetMethodById(JSContext *cx, JSObject *objArg, jsid idArg, JSObject **objp, jsval *vp)
-{
-    RootedObject obj(cx, objArg);
-    RootedId id(cx, idArg);
-
-    AssertHeapIsIdle(cx);
-    CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj, id);
-
-    RootedValue value(cx);
-    if (!GetMethod(cx, obj, id, 0, &value))
-        return JS_FALSE;
-    *vp = value;
-
-    if (objp)
-        *objp = obj;
-    return JS_TRUE;
-}
-
-JS_PUBLIC_API(JSBool)
-JS_GetMethod(JSContext *cx, JSObject *objArg, const char *name, JSObject **objp, jsval *vp)
-{
-    RootedObject obj(cx, objArg);
-    JSAtom *atom = Atomize(cx, name, strlen(name));
-    return atom && JS_GetMethodById(cx, obj, AtomToId(atom), objp, vp);
-}
-
-JS_PUBLIC_API(JSBool)
 JS_SetPropertyById(JSContext *cx, JSObject *objArg, jsid idArg, jsval *vp)
 {
     RootedObject obj(cx, objArg);
     RootedId id(cx, idArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj, id);
     JSAutoResolveFlags rf(cx, JSRESOLVE_ASSIGNING);
@@ -5846,17 +5818,17 @@ JS_CallFunctionName(JSContext *cx, JSObj
     AutoLastFrameCheck lfc(cx);
 
     JSAtom *atom = Atomize(cx, name, strlen(name));
     if (!atom)
         return false;
 
     RootedValue v(cx);
     RootedId id(cx, AtomToId(atom));
-    return GetMethod(cx, obj, id, 0, &v) &&
+    return JSObject::getGeneric(cx, obj, obj, id, &v) &&
            Invoke(cx, ObjectOrNullValue(obj), v, argc, argv, rval);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_CallFunctionValue(JSContext *cx, JSObject *objArg, jsval fval, unsigned argc, jsval *argv,
                      jsval *rval)
 {
     RootedObject obj(cx, objArg);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3462,24 +3462,16 @@ JS_GetPropertyById(JSContext *cx, JSObje
 
 extern JS_PUBLIC_API(JSBool)
 JS_GetPropertyByIdDefault(JSContext *cx, JSObject *obj, jsid id, jsval def, jsval *vp);
 
 extern JS_PUBLIC_API(JSBool)
 JS_ForwardGetPropertyTo(JSContext *cx, JSObject *obj, jsid id, JSObject *onBehalfOf, jsval *vp);
 
 extern JS_PUBLIC_API(JSBool)
-JS_GetMethodById(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
-                 jsval *vp);
-
-extern JS_PUBLIC_API(JSBool)
-JS_GetMethod(JSContext *cx, JSObject *obj, const char *name, JSObject **objp,
-             jsval *vp);
-
-extern JS_PUBLIC_API(JSBool)
 JS_SetProperty(JSContext *cx, JSObject *obj, const char *name, jsval *vp);
 
 extern JS_PUBLIC_API(JSBool)
 JS_SetPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
 
 extern JS_PUBLIC_API(JSBool)
 JS_DeleteProperty(JSContext *cx, JSObject *obj, const char *name);
 
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -2749,17 +2749,20 @@ types::ArrayPrototypeHasIndexedProperty(
     return PrototypeHasIndexedProperty(cx, proto);
 }
 
 bool
 types::TypeCanHaveExtraIndexedProperties(JSContext *cx, StackTypeSet *types)
 {
     Class *clasp = types->getKnownClass();
 
-    if (!clasp || ClassCanHaveExtraProperties(clasp))
+    // Note: typed arrays have indexed properties not accounted for by type
+    // information, though these are all in bounds and will be accounted for
+    // by JIT paths.
+    if (!clasp || (ClassCanHaveExtraProperties(clasp) && !IsTypedArrayClass(clasp)))
         return true;
 
     if (types->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES))
         return true;
 
     JSObject *proto = types->getCommonPrototype();
     if (!proto)
         return true;
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -215,20 +215,20 @@ Class js_NoSuchMethodClass = {
  * call by name, and args is an Array containing this invocation's actual
  * parameters.
  */
 bool
 js::OnUnknownMethod(JSContext *cx, HandleObject obj, Value idval_, MutableHandleValue vp)
 {
     RootedValue idval(cx, idval_);
 
-    RootedId id(cx, NameToId(cx->names().noSuchMethod));
     RootedValue value(cx);
-    if (!GetMethod(cx, obj, id, 0, &value))
+    if (!JSObject::getProperty(cx, obj, obj, cx->names().noSuchMethod, &value))
         return false;
+
     TypeScript::MonitorUnknown(cx);
 
     if (value.get().isPrimitive()) {
         vp.set(value);
     } else {
         JSObject *obj = NewObjectWithClassProto(cx, &js_NoSuchMethodClass, NULL, NULL);
         if (!obj)
             return false;
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -307,17 +307,17 @@ size_t sCustomIteratorCount = 0;
 
 static inline bool
 GetCustomIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
 
     /* Check whether we have a valid __iterator__ method. */
     HandlePropertyName name = cx->names().iteratorIntrinsic;
-    if (!GetMethod(cx, obj, name, 0, vp))
+    if (!JSObject::getProperty(cx, obj, obj, name, vp))
         return false;
 
     /* If there is no custom __iterator__ method, we are done here. */
     if (!vp.isObject()) {
         vp.setUndefined();
         return true;
     }
 
@@ -1228,17 +1228,17 @@ js_IteratorMore(JSContext *cx, HandleObj
         ni->incCursor();
         RootedObject obj(cx, ni->obj);
         if (!JSObject::getGeneric(cx, obj, obj, id, rval))
             return false;
         if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, rval, rval))
             return false;
     } else {
         /* Call the iterator object's .next method. */
-        if (!GetMethod(cx, iterobj, cx->names().next, 0, rval))
+        if (!JSObject::getProperty(cx, iterobj, iterobj, cx->names().next, rval))
             return false;
         if (!Invoke(cx, ObjectValue(*iterobj), rval, 0, NULL, rval.address())) {
             /* Check for StopIteration. */
             if (!cx->isExceptionPending() || !IsStopIteration(cx->getPendingException()))
                 return false;
 
             cx->clearPendingException();
             cx->iterValue.setMagic(JS_NO_ITER_VALUE);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3871,20 +3871,17 @@ GetPropertyHelperInline(JSContext *cx,
             /* Don't warn if not strict or for random getprop operations. */
             if (!cx->hasStrictOption() || (op != JSOP_GETPROP && op != JSOP_GETELEM))
                 return true;
 
             /* Don't warn repeatedly for the same script. */
             if (!script || script->warnedAboutUndefinedProp)
                 return true;
 
-            /*
-             * XXX do not warn about missing __iterator__ as the function
-             * may be called from JS_GetMethodById. See bug 355145.
-             */
+            /* We may just be checking if that object has an iterator. */
             if (JSID_IS_ATOM(id, cx->names().iteratorIntrinsic))
                 return JS_TRUE;
 
             /* Do not warn about tests like (obj[prop] == undefined). */
             if (cx->resolveFlags == RESOLVE_INFER) {
                 pc += js_CodeSpec[op].length;
                 if (Detecting(cx, script, pc))
                     return JS_TRUE;
@@ -4079,28 +4076,16 @@ baseops::GetPropertyDefault(JSContext *c
     if (!prop) {
         vp.set(def);
         return true;
     }
 
     return baseops::GetProperty(cx, obj2, id, vp);
 }
 
-JSBool
-js::GetMethod(JSContext *cx, HandleObject obj, HandleId id, unsigned getHow, MutableHandleValue vp)
-{
-    JSAutoResolveFlags rf(cx, 0);
-
-    GenericIdOp op = obj->getOps()->getGeneric;
-    if (!op)
-        return GetPropertyHelper(cx, obj, id, getHow, vp);
-
-    return op(cx, obj, obj, id, vp);
-}
-
 static bool
 MaybeReportUndeclaredVarAssignment(JSContext *cx, JSString *propname)
 {
     {
         RawScript script = cx->stack.currentScript(NULL, ContextStack::ALLOW_CROSS_COMPARTMENT);
         if (!script)
             return true;
 
@@ -4194,19 +4179,20 @@ JSObject::reportNotExtensible(JSContext 
                                     JSDVG_IGNORE_STACK, val, NullPtr(),
                                     NULL, NULL);
 }
 
 bool
 JSObject::callMethod(JSContext *cx, HandleId id, unsigned argc, Value *argv, MutableHandleValue vp)
 {
     RootedValue fval(cx);
-    Rooted<JSObject*> obj(cx, this);
-    return GetMethod(cx, obj, id, 0, &fval) &&
-           Invoke(cx, ObjectValue(*obj), fval, argc, argv, vp.address());
+    RootedObject obj(cx, this);
+    if (!JSObject::getGeneric(cx, obj, obj, id, &fval))
+        return false;
+    return Invoke(cx, ObjectValue(*obj), fval, argc, argv, vp.address());
 }
 
 JSBool
 baseops::SetPropertyHelper(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
                            unsigned defineHow, MutableHandleValue vp, JSBool strict)
 {
     JS_ASSERT((defineHow & ~(DNP_CACHE_RESULT | DNP_UNQUALIFIED)) == 0);
 
@@ -4558,19 +4544,19 @@ js::HasDataProperty(JSContext *cx, JSObj
  * Gets |obj[id]|.  If that value's not callable, returns true and stores a
  * non-primitive value in *vp.  If it's callable, calls it with