Merge m-c to birch.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 31 May 2013 16:13:11 -0400
changeset 133783 6c6eec202bf4d03c52e8a253db1b4f5d7d2307e2
parent 133782 9ea3870393698074ff5b19fae024b2923e171e44 (current diff)
parent 133594 2222b07ab20798f810c685b6f4715f5b8d07a2f0 (diff)
child 133784 1c0a49bbb666c79518fc65e4e75d4c7b886de714
push id28905
push userryanvm@gmail.com
push dateMon, 03 Jun 2013 15:58:12 +0000
treeherdermozilla-inbound@d2eabe8e27b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.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.
js/src/jit-test/tests/ion/bug725062.js
js/src/tests/ecma_5/extensions/legacy-JSON.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -46,17 +46,17 @@ pref("extensions.getAddons.recommended.u
 
 // Blocklist preferences
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 // Controls what level the blocklist switches from warning about items to forcibly
 // blocking them.
 pref("extensions.blocklist.level", 2);
 pref("extensions.blocklist.url", "https://addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/");
-pref("extensions.blocklist.detailsURL", "https://www.mozilla.com/%LOCALE%/blocklist/");
+pref("extensions.blocklist.detailsURL", "https://www.mozilla.org/%LOCALE%/blocklist/");
 pref("extensions.blocklist.itemURL", "https://addons.mozilla.org/%LOCALE%/%APP%/blocked/%blockID%");
 
 pref("extensions.update.autoUpdateDefault", true);
 
 pref("extensions.hotfix.id", "firefox-hotfix@mozilla.org");
 pref("extensions.hotfix.cert.checkAttributes", true);
 pref("extensions.hotfix.certs.1.sha1Fingerprint", "CA:C4:7D:BF:63:4D:24:E9:DC:93:07:2F:E3:C8:EA:6D:C3:94:6E:89");
 
@@ -748,17 +748,17 @@ pref("browser.safebrowsing.keyURL", "htt
 pref("browser.safebrowsing.gethashURL", "http://safebrowsing.clients.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.safebrowsing.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/report?");
 pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
 pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
 
-pref("browser.safebrowsing.warning.infoURL", "http://www.mozilla.com/%LOCALE%/firefox/phishing-protection/");
+pref("browser.safebrowsing.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/phishing-protection/");
 pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 
 #ifdef MOZILLA_OFFICIAL
 // Normally the "client ID" sent in updates is appinfo.name, but for
 // official Firefox releases from Mozilla we use a special identifier.
 pref("browser.safebrowsing.id", "navclient-auto-ffox");
 #endif
 
--- a/browser/base/content/aboutDialog.xul
+++ b/browser/base/content/aboutDialog.xul
@@ -112,17 +112,17 @@
           </description>
         </vbox>
       </vbox>
     </hbox>
     <vbox id="bottomBox">
       <hbox pack="center">
         <label class="text-link bottom-link" href="about:license">&bottomLinks.license;</label>
         <label class="text-link bottom-link" href="about:rights">&bottomLinks.rights;</label>
-        <label class="text-link bottom-link" href="http://www.mozilla.com/legal/privacy/">&bottomLinks.privacy;</label>
+        <label class="text-link bottom-link" href="https://www.mozilla.org/legal/privacy/">&bottomLinks.privacy;</label>
       </hbox>
       <description id="trademark">&trademarkInfo.part1;</description>
     </vbox>
   </vbox>
   
   <keyset>
     <key keycode="VK_ESCAPE" oncommand="window.close();"/>
   </keyset>
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -364,17 +364,17 @@
                  oncommand="openHelpLink('firefox-help')">
           <menupopup id="appmenu_helpMenupopup">
             <menuitem id="appmenu_openHelp"
                       label="&helpMenu.label;"
                       oncommand="openHelpLink('firefox-help')"
                       onclick="checkForMiddleClick(this, event);"/>
             <menuitem id="appmenu_gettingStarted"
                       label="&appMenuGettingStarted.label;"
-                      oncommand="gBrowser.loadOneTab('http://www.mozilla.com/firefox/central/', {inBackground: false});"
+                      oncommand="gBrowser.loadOneTab('https://www.mozilla.org/firefox/central/', {inBackground: false});"
                       onclick="checkForMiddleClick(this, event);"/>
 #ifdef MOZ_SERVICES_HEALTHREPORT
             <menuitem id="appmenu_healthReport"
                       label="&healthReport.label;"
                       oncommand="openHealthReport()"
                       onclick="checkForMiddleClick(this, event);"/>
 #endif
             <menuitem id="appmenu_troubleshootingInfo"
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -16,17 +16,13 @@ pref("app.update.download.backgroundInte
 pref("app.update.promptWaitTime", 86400);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/aurora/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
 
-// Release notes and vendor URLs
-pref("app.releaseNotesURL", "http://www.mozilla.org/projects/firefox/%VERSION%/releasenotes/");
-pref("app.vendorURL", "http://www.mozilla.org/projects/firefox/");
-
 // Search codes belong only in builds with official branding
 pref("browser.search.param.yahoo-fr", "");
 pref("browser.search.param.yahoo-fr-cjkt", ""); // now unused
 pref("browser.search.param.yahoo-fr-ja", "");
 pref("browser.search.param.yahoo-f-CN", "");
--- a/browser/branding/nightly/pref/firefox-branding.js
+++ b/browser/branding/nightly/pref/firefox-branding.js
@@ -13,17 +13,13 @@ pref("app.update.download.backgroundInte
 pref("app.update.promptWaitTime", 43200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://nightly.mozilla.org");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "https://nightly.mozilla.org");
 
-// Release notes and vendor URLs
-pref("app.releaseNotesURL", "http://www.mozilla.org/projects/firefox/%VERSION%/releasenotes/");
-pref("app.vendorURL", "http://www.mozilla.org/projects/firefox/");
-
 // Search codes belong only in builds with official branding
 pref("browser.search.param.yahoo-fr", "");
 pref("browser.search.param.yahoo-fr-cjkt", ""); // now unused
 pref("browser.search.param.yahoo-fr-ja", "");
 pref("browser.search.param.yahoo-f-CN", "");
--- a/browser/branding/official/branding.nsi
+++ b/browser/branding/official/branding.nsi
@@ -5,18 +5,18 @@
 # NSIS branding defines for official release builds.
 # The nightly build branding.nsi is located in browser/installer/windows/nsis/
 # The unofficial build branding.nsi is located in browser/branding/unofficial/
 
 # BrandFullNameInternal is used for some registry and file system values
 # instead of BrandFullName and typically should not be modified.
 !define BrandFullNameInternal "Mozilla Firefox"
 !define CompanyName           "Mozilla Corporation"
-!define URLInfoAbout          "http://www.mozilla.com/${AB_CD}/"
-!define URLUpdateInfo         "http://www.mozilla.com/${AB_CD}/firefox/"
+!define URLInfoAbout          "https://www.mozilla.org/${AB_CD}/"
+!define URLUpdateInfo         "https://www.mozilla.org/${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/${AB_CD}/firefox/installer-help/?channel=release&installer_lang=${AB_CD}"
 !define Channel "release"
--- a/browser/branding/official/pref/firefox-branding.js
+++ b/browser/branding/official/pref/firefox-branding.js
@@ -13,16 +13,12 @@ pref("app.update.download.backgroundInte
 pref("app.update.promptWaitTime", 86400);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
 
-// Release notes and vendor URLs
-pref("app.releaseNotesURL", "http://www.mozilla.com/%LOCALE%/firefox/%VERSION%/releasenotes/");
-pref("app.vendorURL", "http://www.mozilla.com/%LOCALE%/firefox/");
-
 pref("browser.search.param.ms-pc", "MOZI");
 pref("browser.search.param.yahoo-fr", "moz35");
 pref("browser.search.param.yahoo-fr-cjkt", "moz35"); // now unused
 pref("browser.search.param.yahoo-fr-ja", "mozff");
--- a/browser/branding/unofficial/pref/firefox-branding.js
+++ b/browser/branding/unofficial/pref/firefox-branding.js
@@ -13,17 +13,13 @@ pref("app.update.download.backgroundInte
 pref("app.update.promptWaitTime", 86400);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://nightly.mozilla.org");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "https://nightly.mozilla.org");
 
-// Release notes and vendor URLs
-pref("app.releaseNotesURL", "http://www.mozilla.org/projects/firefox/%VERSION%/releasenotes/");
-pref("app.vendorURL", "http://www.mozilla.org/projects/firefox/");
-
 // Search codes belong only in builds with official branding
 pref("browser.search.param.yahoo-fr", "");
 pref("browser.search.param.yahoo-fr-cjkt", ""); // now unused
 pref("browser.search.param.yahoo-fr-ja", "");
 pref("browser.search.param.yahoo-f-CN", "");
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -987,27 +987,29 @@ DownloadsPlacesView.prototype = {
     if (!aDocumentFragment) {
       this._ensureVisibleElementsAreActive();
       goUpdateCommand("downloadsCmd_clearDownloads");
     }
   },
 
   _removeElement: function DPV__removeElement(aElement) {
     // If the element was selected exclusively, select its next
-    // sibling first, if any.
-    if (aElement.nextSibling &&
+    // sibling first, if not, try for previous sibling, if any.
+    if ((aElement.nextSibling || aElement.previousSibling) &&
         this._richlistbox.selectedItems &&
-        this._richlistbox.selectedItems.length > 0 &&
+        this._richlistbox.selectedItems.length == 1 &&
         this._richlistbox.selectedItems[0] == aElement) {
-      this._richlistbox.selectItem(aElement.nextSibling);
+      this._richlistbox.selectItem(aElement.nextSibling ||
+                                   aElement.previousSibling);
     }
 
     if (this._lastSessionDownloadElement == aElement)
       this._lastSessionDownloadElement = aElement.previousSibling;
 
+    this._richlistbox.removeItemFromSelection(aElement);
     this._richlistbox.removeChild(aElement);
     this._ensureVisibleElementsAreActive();
     goUpdateCommand("downloadsCmd_clearDownloads");
   },
 
   _removeHistoryDownloadFromView:
   function DPV__removeHistoryDownloadFromView(aPlacesNode) {
     let downloadURI = aPlacesNode.uri;
@@ -1456,17 +1458,21 @@ DownloadsPlacesView.prototype = {
             .getService(Ci.nsIDownloadHistory)
             .removeAllDownloads();
         }
         // There may be no selection or focus change as a result
         // of these change, and we want the command updated immediately.
         goUpdateCommand("downloadsCmd_clearDownloads");
         break;
       default: {
-        let selectedElements = this._richlistbox.selectedItems;
+        // Slicing the array to get a freezed list of selected items. Otherwise,
+        // the selectedItems array is live and doCommand may alter the selection
+        // while we are trying to do one particular action, like removing items
+        // from history.
+        let selectedElements = this._richlistbox.selectedItems.slice();
         for (let element of selectedElements) {
           element._shell.doCommand(aCommand);
         }
       }
     }
   },
 
   onEvent: function() { },
--- a/browser/components/places/tests/unit/bookmarks.glue.json
+++ b/browser/components/places/tests/unit/bookmarks.glue.json
@@ -1,1 +1,1 @@
-{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Unsorted Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]},]}
+{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Unsorted Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]}]}
--- a/browser/fuel/test/browser_Bookmarks.js
+++ b/browser/fuel/test/browser_Bookmarks.js
@@ -73,23 +73,23 @@ function test() {
   testFolder.annotations.remove("testing/folder");
   ok(!testFolder.annotations.has("testing/folder"), "Checking existence of removed annotation");
   is(gLastFolderAction, "testing/folder", "Check event handler for removing annotation");
 
   testFolder.events.addListener("addchild", onFolderAddChild);
   testFolder.events.addListener("removechild", onFolderRemoveChild);
 
   // test adding a bookmark
-  var testBookmark = testFolder.addBookmark("Mozilla", url("http://www.mozilla.com/"));
+  var testBookmark = testFolder.addBookmark("Mozilla", url("https://www.mozilla.org/"));
   ok(testBookmark, "Check bookmark creation");
   ok(testBookmark.parent, "Check parent after bookmark creation");
   is(gLastFolderAction, "addchild", "Check event handler for adding a child to a folder");
   is(testBookmark.type, "bookmark", "Check 'bookmark.type' after creation");
   is(testBookmark.title, "Mozilla", "Check 'bookmark.title' after creation");
-  is(testBookmark.uri.spec, "http://www.mozilla.com/", "Check 'bookmark.uri' after creation");
+  is(testBookmark.uri.spec, "https://www.mozilla.org/", "Check 'bookmark.uri' after creation");
 
   is(testFolder.children.length, 1, "Check test folder child count after adding a child bookmark");
 
   // test modifying a bookmark
   testBookmark.events.addListener("change", onBookmarkChange);
   testBookmark.description = "mozcorp";
   is(testBookmark.description, "mozcorp", "Check setting 'bookmark.description'");
   is(gLastBookmarkAction, "bookmarkProperties/description", "Check event handler for setting 'bookmark.description'");
@@ -170,17 +170,17 @@ function test() {
   is(gLastFolderAction, "remove", "Check event handler for removing child folder");
   rootKidCount--;
   is(root.children.length, rootKidCount, "Check root folder child count after removing a child folder");
 
   // test moving between folders
   var testFolderA = root.addFolder("folder-a");
   var testFolderB = root.addFolder("folder-b");
 
-  var testMove = testFolderA.addBookmark("Mozilla", url("http://www.mozilla.com/"));
+  var testMove = testFolderA.addBookmark("Mozilla", url("https://www.mozilla.org/"));
   testMove.events.addListener("move", onBookmarkMove);
   is(testMove.parent.title, "folder-a", "Checking for new parent before moving bookmark");
 
   testMove.parent = testFolderB;
   is(testMove.parent.title, "folder-b", "Checking for new parent after moving bookmark");
   is(gLastBookmarkAction, "move", "Checking for event handler after moving bookmark");
 
   // test moving a folder
--- a/browser/locales/en-US/chrome/browser/aboutDialog.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -31,17 +31,17 @@
 <!ENTITY contribute.end             "">
 
 <!-- LOCALIZATION NOTE (bottomLinks.license): This is a link title that links to about:license. -->
 <!ENTITY bottomLinks.license        "Licensing Information">
 
 <!-- LOCALIZATION NOTE (bottomLinks.rights): This is a link title that links to about:rights. -->
 <!ENTITY bottomLinks.rights         "End-User Rights">
 
-<!-- LOCALIZATION NOTE (bottomLinks.privacy): This is a link title that links to http://www.mozilla.com/legal/privacy/. -->
+<!-- LOCALIZATION NOTE (bottomLinks.privacy): This is a link title that links to https://www.mozilla.org/legal/privacy/. -->
 <!ENTITY bottomLinks.privacy        "Privacy Policy">
 
 <!-- LOCALIZATION NOTE (update.checkingForUpdates): try to make the localized text short (see bug 596813 for screenshots). -->
 <!ENTITY update.checkingForUpdates  "Checking for updates…">
 <!-- LOCALIZATION NOTE (update.checkingAddonCompat): try to make the localized text short (see bug 596813 for screenshots). -->
 <!ENTITY update.checkingAddonCompat "Checking Add-on compatibility…">
 <!-- LOCALIZATION NOTE (update.noUpdatesFound): try to make the localized text short (see bug 596813 for screenshots). -->
 <!ENTITY update.noUpdatesFound      "&brandShortName; is up to date">
--- a/browser/locales/en-US/chrome/browser/aboutRobots.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutRobots.dtd
@@ -1,16 +1,14 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!-- These strings are used in the about:robots page, which ties in with the
-     robots theme used in the Firefox 3 Beta 2/3 first run pages...
-         https://www.mozilla.com/en-US/firefox/3.0b2/firstrun/
-         https://www.mozilla.com/en-US/firefox/3.0b3/firstrun/
+     robots theme used in the Firefox 3 Beta 2/3 first run pages.
      They're just meant to be fun and whimsical, with references to some geeky
      but well-known robots in movies and books. Be creative with translations! -->
 
 <!-- Nonsense line from the movie "The Day The Earth Stood Still". No translation needed. -->
 <!ENTITY robots.pagetitle  "Gort! Klaatu barada nikto!">
 <!-- Movie: Logan's Run... Box (cybog): "Welcome Humans! I am ready for you." -->
 <!ENTITY robots.errorTitleText "Welcome Humans!">
 <!-- Movie: The Day The Earth Stood Still. Spoken by Klaatu. -->
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -428,33 +428,33 @@ identity.newIdentity.description = Enter
 identity.next.label = Next
 identity.next.accessKey = n
 # LOCALIZATION NOTE: shown in the popup notification when a user successfully logs into a website
 # LOCALIZATION NOTE (identity.loggedIn.description): %S is the user's identity (e.g. user@example.com)
 identity.loggedIn.description = Signed in as: %S
 identity.loggedIn.signOut.label = Sign Out
 identity.loggedIn.signOut.accessKey = O
 
-# LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message, getUserMedia.shareCameraAndMicrophone.message, getUserMedia.sharingCamera.message, getUserMedia.sharingMicrophone.message, getUserMedia.sharingCameraAndMicrophone.message): %S is the website origin (e.g. www.mozilla.org)
+# LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message, getUserMedia.shareCameraAndMicrophone.message): %S is the website origin (e.g. www.mozilla.org)
 # LOCALIZATION NOTE (getUserMedia.shareSelectedDevices.label):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # The number of devices can be either one or two.
 getUserMedia.shareCamera.message = Would you like to share your camera with %S?
 getUserMedia.shareMicrophone.message = Would you like to share your microphone with %S?
 getUserMedia.shareCameraAndMicrophone.message = Would you like to share your camera and microphone with %S?
 getUserMedia.noVideo.label = No Video
 getUserMedia.noAudio.label = No Audio
 getUserMedia.shareSelectedDevices.label = Share Selected Device;Share Selected Devices
 getUserMedia.shareSelectedDevices.accesskey = S
 getUserMedia.denyRequest.label = Don't Share
 getUserMedia.denyRequest.accesskey = D
-getUserMedia.sharingCamera.message = You are currently sharing your camera with %S.
-getUserMedia.sharingMicrophone.message = You are currently sharing your microphone with %S.
-getUserMedia.sharingCameraAndMicrophone.message = You are currently sharing your camera and microphone with %S.
+getUserMedia.sharingCamera.message2 = You are currently sharing your camera with this page.
+getUserMedia.sharingMicrophone.message2 = You are currently sharing your microphone with this page.
+getUserMedia.sharingCameraAndMicrophone.message2 = You are currently sharing your camera and microphone with this page.
 
 # Mixed Content Blocker Doorhanger Notification
 # LOCALIZATION NOTE - %S is brandShortName
 mixedContentBlocked.message = %S has blocked content that isn't secure.
 mixedContentBlocked.keepBlockingButton.label = Keep Blocking
 mixedContentBlocked.keepBlockingButton.accesskey = B
 mixedContentBlocked.unblock.label = Disable Protection on This Page
 mixedContentBlocked.unblock.accesskey = D
--- a/browser/locales/en-US/profile/bookmarks.inc
+++ b/browser/locales/en-US/profile/bookmarks.inc
@@ -9,32 +9,32 @@
 
 #define bookmarks_title Bookmarks
 #define bookmarks_heading Bookmarks
 
 #define bookmarks_toolbarfolder Bookmarks Toolbar Folder
 #define bookmarks_toolbarfolder_description Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar
 
 # LOCALIZATION NOTE (getting_started):
-# link title for http://en-US.www.mozilla.com/en-US/firefox/central/
+# link title for https://www.mozilla.org/en-US/firefox/central/
 #define getting_started Getting Started
 
 # LOCALIZATION NOTE (firefox_heading):
 # Firefox links folder name
 #define firefox_heading Mozilla Firefox
 
 # LOCALIZATION NOTE (firefox_help):
-# link title for http://en-US.www.mozilla.com/en-US/firefox/help/
+# link title for https://www.mozilla.org/en-US/firefox/help/
 #define firefox_help Help and Tutorials
 
 # LOCALIZATION NOTE (firefox_customize):
-# link title for http://en-US.www.mozilla.com/en-US/firefox/customize/
+# link title for https://www.mozilla.org/en-US/firefox/customize/
 #define firefox_customize Customize Firefox
 
 # LOCALIZATION NOTE (firefox_community):
-# link title for http://en-US.www.mozilla.com/en-US/firefox/community/
+# link title for https://www.mozilla.org/en-US/contribute/
 #define firefox_community Get Involved
 
 # LOCALIZATION NOTE (firefox_about):
-# link title for http://en-US.www.mozilla.com/en-US/about/
+# link title for https://www.mozilla.org/en-US/about/
 #define firefox_about About Us
 
 #unfilter emptyLines
--- a/browser/locales/generic/extract-bookmarks.py
+++ b/browser/locales/generic/extract-bookmarks.py
@@ -27,36 +27,36 @@ template = '''#filter emptyLines
 
 #define bookmarks_title %s
 #define bookmarks_heading %s
 
 #define bookmarks_toolbarfolder %s
 #define bookmarks_toolbarfolder_description %s
 
 # LOCALIZATION NOTE (getting_started):
-# link title for http://www.mozilla.com/en-US/firefox/central/
+# link title for https://www.mozilla.org/en-US/firefox/central/
 #define getting_started %s
 
 # LOCALIZATION NOTE (firefox_heading):
 # Firefox links folder name
 #define firefox_heading %s
 
 # LOCALIZATION NOTE (firefox_help):
-# link title for http://www.mozilla.com/en-US/firefox/help/
+# link title for https://www.mozilla.org/en-US/firefox/help/
 #define firefox_help %s
 
 # LOCALIZATION NOTE (firefox_customize):
-# link title for http://www.mozilla.com/en-US/firefox/customize/
+# link title for https://www.mozilla.org/en-US/firefox/customize/
 #define firefox_customize %s
 
 # LOCALIZATION NOTE (firefox_community):
-# link title for http://www.mozilla.com/en-US/firefox/community/
+# link title for https://www.mozilla.org/en-US/contribute/
 #define firefox_community %s
 
 # LOCALIZATION NOTE (firefox_about):
-# link title for http://www.mozilla.com/en-US/about/
+# link title for https://www.mozilla.org/en-US/about/
 #define firefox_about %s
 
 #unfilter emptyLines'''
 
 strings = tuple(e.val for e in p if ll.search(e.key))
 
 print codecs.utf_8_encode(template % strings)[0]
--- a/browser/locales/generic/profile/bookmarks.html.in
+++ b/browser/locales/generic/profile/bookmarks.html.in
@@ -10,18 +10,18 @@
 <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
 <TITLE>@bookmarks_title@</TITLE>
 <H1>@bookmarks_heading@</H1>
 
 <DL><p>
     <DT><H3 PERSONAL_TOOLBAR_FOLDER="true" ID="rdf:#$FvPhC3">@bookmarks_toolbarfolder@</H3>
 <DD>@bookmarks_toolbarfolder_description@
     <DL><p>
-        <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/central/" ID="rdf:#$GvPhC3">@getting_started@</A>
+        <DT><A HREF="https://www.mozilla.org/@AB_CD@/firefox/central/" ID="rdf:#$GvPhC3">@getting_started@</A>
     </DL><p>
     <DT><H3 ID="rdf:#$ZvPhC3">@firefox_heading@</H3>
     <DL><p>
-        <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/help/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$22iCK1">@firefox_help@</A>
-        <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/customize/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$32iCK1">@firefox_customize@</A>
-        <DT><A HREF="http://www.mozilla.com/@AB_CD@/firefox/community/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$42iCK1">@firefox_community@</A>
-        <DT><A HREF="http://www.mozilla.com/@AB_CD@/about/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$52iCK1">@firefox_about@</A>
+        <DT><A HREF="https://www.mozilla.org/@AB_CD@/firefox/help/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$22iCK1">@firefox_help@</A>
+        <DT><A HREF="https://www.mozilla.org/@AB_CD@/firefox/customize/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$32iCK1">@firefox_customize@</A>
+        <DT><A HREF="https://www.mozilla.org/@AB_CD@/contribute/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$42iCK1">@firefox_community@</A>
+        <DT><A HREF="https://www.mozilla.org/@AB_CD@/about/" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==" ID="rdf:#$52iCK1">@firefox_about@</A>
     </DL><p>
 </DL><p>
--- a/browser/metro/base/content/WebProgress.js
+++ b/browser/metro/base/content/WebProgress.js
@@ -59,25 +59,29 @@ const WebProgress = {
         this._progressStep();
         break;
       }
     }
   },
 
   _securityChange: function _securityChange(aJson, aTab) {
     let state = aJson.state;
-    let identityBox = document.getElementById("identity-box-inner");
     let nsIWebProgressListener = Ci.nsIWebProgressListener;
 
     if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
-      aTab._identityState = identityBox.className = "verifiedIdentity";
+      aTab._identityState = "verifiedIdentity";
     } else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
-      aTab._identityState = identityBox.className = "verifiedDomain";
+      aTab._identityState = "verifiedDomain";
     } else {
-      aTab._identityState = identityBox.className = "";
+      aTab._identityState = "";
+    }
+
+    if (aTab == Browser.selectedTab) {
+      let identityBox = document.getElementById("identity-box-inner");
+      identityBox.className = aTab._identityState;
     }
   },
 
   _locationChange: function _locationChange(aJson, aTab) {
     let spec = aJson.location;
     let location = spec.split("#")[0]; // Ignore fragment identifier changes.
 
     if (aTab == Browser.selectedTab)
--- a/browser/metro/base/content/pages/aboutRights.xhtml
+++ b/browser/metro/base/content/pages/aboutRights.xhtml
@@ -28,17 +28,17 @@
   <li>&rights.intro-point1a;<a href="http://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li>
   <!--
     Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
     Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
     Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace)
   -->
   <li>&rights.intro-point2-a;<a href="http://www.mozilla.org/foundation/trademarks/policy.html">&rights.intro-point2-b;</a>&rights.intro-point2-c;</li>
   <li>&rights.intro-point2.5;</li>
-  <li>&rights2.intro-point3a;<a href="http://www.mozilla.com/legal/privacy/">&rights2.intro-point3b;</a>&rights.intro-point3c;</li>
+  <li>&rights2.intro-point3a;<a href="https://www.mozilla.org/legal/privacy/">&rights2.intro-point3b;</a>&rights.intro-point3c;</li>
   <li>&rights2.intro-point4a;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b;</a>&rights.intro-point4c;</li>
 </ul>
 
 <div id="webservices-container">
   <a name="webservices"/>
   <h3>&rights2.webservices-header;</h3>
 
   <p>&rights2.webservices-a;<a href="about:rights#disabling-webservices" onclick="showDisablingServices();">&rights2.webservices-b;</a>&rights3.webservices-c;</p>
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -183,17 +183,17 @@ pref("xpinstall.enabled", false);
 pref("xpinstall.whitelist.add", "addons.mozilla.org");
 pref("extensions.autoupdate.enabled", false);
 pref("extensions.update.enabled", false);
 
 /* blocklist preferences */
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 pref("extensions.blocklist.url", "https://addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/");
-pref("extensions.blocklist.detailsURL", "https://www.mozilla.com/%LOCALE%/blocklist/");
+pref("extensions.blocklist.detailsURL", "https://www.mozilla.org/%LOCALE%/blocklist/");
 
 /* block popups by default, and notify the user about blocked popups */
 pref("dom.disable_open_during_load", true);
 pref("privacy.popups.showBrowserMessage", true);
 
 /* disable opening windows with the dialog feature */
 pref("dom.disable_window_open_dialog_feature", true);
 
@@ -378,17 +378,17 @@ pref("dom.ipc.plugins.enabled", true);
 pref("dom.ipc.content.nice", 1);
 
 // product URLs
 // The breakpad report server to link to in about:crashes
 pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/");
 // TODO: This is not the correct article for metro!!!
 pref("app.sync.tutorialURL", "https://support.mozilla.org/kb/sync-firefox-between-desktop-and-mobile");
 pref("app.support.baseURL", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/");
-pref("app.privacyURL", "http://www.mozilla.com/legal/privacy/");
+pref("app.privacyURL", "https://www.mozilla.org/legal/privacy/");
 pref("app.creditsURL", "http://www.mozilla.org/credits/");
 pref("app.channelURL", "http://www.mozilla.org/%LOCALE%/firefox/channel/");
 
 // Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
 pref("security.alternate_certificate_error_page", "certerror");
 
 pref("security.warn_viewing_mixed", false); // Warning is disabled.  See Bug 616712.
 
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -52,32 +52,34 @@ this.webrtcUI = {
         });
       }
     }
     return activeStreams;
   }
 }
 
 function getBrowserForWindowId(aWindowID) {
-  let contentWindow = Services.wm.getOuterWindowWithId(aWindowID);
-  return contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIWebNavigation)
-                      .QueryInterface(Ci.nsIDocShell)
-                      .chromeEventHandler;
+  return getBrowserForWindow(Services.wm.getOuterWindowWithId(aWindowID));
+}
+
+function getBrowserForWindow(aContentWindow) {
+  return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIWebNavigation)
+                       .QueryInterface(Ci.nsIDocShell)
+                       .chromeEventHandler;
 }
 
 function handleRequest(aSubject, aTopic, aData) {
   let {windowID: windowID, callID: callID} = JSON.parse(aData);
 
-  let browser = getBrowserForWindowId(windowID);
   let params = aSubject.QueryInterface(Ci.nsIMediaStreamOptions);
 
-  browser.ownerDocument.defaultView.navigator.mozGetUserMediaDevices(
+  Services.wm.getMostRecentWindow(null).navigator.mozGetUserMediaDevices(
     function (devices) {
-      prompt(browser, callID, params.audio, params.video || params.picture, devices);
+      prompt(windowID, callID, params.audio, params.video || params.picture, devices);
     },
     function (error) {
       // bug 827146 -- In the future, the UI should catch NO_DEVICES_FOUND
       // and allow the user to plug in a device, instead of immediately failing.
       denyRequest(callID, error);
     }
   );
 }
@@ -86,17 +88,17 @@ function denyRequest(aCallID, aError) {
   let msg = null;
   if (aError) {
     msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
     msg.data = aError;
   }
   Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aCallID);
 }
 
-function prompt(aBrowser, aCallID, aAudioRequested, aVideoRequested, aDevices) {
+function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices) {
   let audioDevices = [];
   let videoDevices = [];
   for (let device of aDevices) {
     device = device.QueryInterface(Ci.nsIMediaDevice);
     switch (device.type) {
       case "audio":
         if (aAudioRequested)
           audioDevices.push(device);
@@ -115,18 +117,20 @@ function prompt(aBrowser, aCallID, aAudi
     requestType = "Microphone";
   else if (videoDevices.length)
     requestType = "Camera";
   else {
     denyRequest(aCallID, "NO_DEVICES_FOUND");
     return;
   }
 
-  let host = aBrowser.contentDocument.documentURIObject.asciiHost;
-  let chromeDoc = aBrowser.ownerDocument;
+  let contentWindow = Services.wm.getOuterWindowWithId(aWindowID);
+  let host = contentWindow.document.documentURIObject.asciiHost;
+  let browser = getBrowserForWindow(contentWindow);
+  let chromeDoc = browser.ownerDocument;
   let chromeWin = chromeDoc.defaultView;
   let stringBundle = chromeWin.gNavigatorBundle;
   let message = stringBundle.getFormattedString("getUserMedia.share" + requestType + ".message",
                                                 [ host ]);
 
   function listDevices(menupopup, devices) {
     while (menupopup.lastChild)
       menupopup.removeChild(menupopup.lastChild);
@@ -190,17 +194,17 @@ function prompt(aBrowser, aCallID, aAudi
     accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
     callback: function () {
       denyRequest(aCallID);
     }
   }];
 
   let options = null;
 
-  chromeWin.PopupNotifications.show(aBrowser, "webRTC-shareDevices", message,
+  chromeWin.PopupNotifications.show(browser, "webRTC-shareDevices", message,
                                     "webRTC-shareDevices-notification-icon", mainAction,
                                     secondaryActions, options);
 }
 
 function updateIndicators() {
   webrtcUI.showGlobalIndicator =
     MediaManagerService.activeMediaCaptureWindows.Count() > 0;
 
@@ -226,20 +230,18 @@ function showBrowserSpecificIndicator(aB
     captureState = "Microphone";
   } else {
     Cu.reportError("showBrowserSpecificIndicator: got neither video nor audio access");
     return;
   }
 
   let chromeWin = aBrowser.ownerDocument.defaultView;
   let stringBundle = chromeWin.gNavigatorBundle;
-  let host = aBrowser.contentDocument.documentURIObject.asciiHost;
 
-  let message = stringBundle.getFormattedString("getUserMedia.sharing" + captureState + ".message",
-                                                [ host ]);
+  let message = stringBundle.getString("getUserMedia.sharing" + captureState + ".message2");
   let mainAction = null;
   let secondaryActions = null;
   let options = {
     dismissed: true
   };
   chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
                                     "webRTC-sharingDevices-notification-icon", mainAction,
                                     secondaryActions, options);
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -63,16 +63,17 @@ class Element;
 class EventHandlerNonNull;
 class OnErrorEventHandlerNonNull;
 template<typename T> class Optional;
 } // namespace dom
 } // namespace mozilla
 
 namespace JS {
 class Value;
+template<typename T> class Handle;
 }
 
 #define NODE_FLAG_BIT(n_) (1U << (n_))
 
 enum {
   // This bit will be set if the node has a listener manager.
   NODE_HAS_LISTENERMANAGER =              NODE_FLAG_BIT(0),
 
@@ -807,16 +808,17 @@ public:
   using mozilla::dom::EventTarget::RemoveEventListener;
   using nsIDOMEventTarget::AddEventListener;
   virtual void AddEventListener(const nsAString& aType,
                                 nsIDOMEventListener* aListener,
                                 bool aUseCapture,
                                 const mozilla::dom::Nullable<bool>& aWantsUntrusted,
                                 mozilla::ErrorResult& aRv) MOZ_OVERRIDE;
   using nsIDOMEventTarget::AddSystemEventListener;
+  virtual nsIDOMWindow* GetOwnerGlobal() MOZ_OVERRIDE;
 
   /**
    * Adds a mutation observer to be notified when this node, or any of its
    * descendants, are modified. The node will hold a weak reference to the
    * observer, which means that it is the responsibility of the observer to
    * remove itself in case it dies before the node.  If an observer is added
    * while observers are being notified, it may also be notified.  In general,
    * adding observers while inside a notification is not a good idea.  An
@@ -1575,17 +1577,18 @@ public:
 #endif
   void GetLocalName(nsAString& aLocalName)
   {
     aLocalName = mNodeInfo->LocalName();
   }
   // HasAttributes is defined inline in Element.h.
   bool HasAttributes() const;
   nsDOMAttributeMap* GetAttributes();
-  JS::Value SetUserData(JSContext* aCx, const nsAString& aKey, JS::Value aData,
+  JS::Value SetUserData(JSContext* aCx, const nsAString& aKey,
+                        JS::Handle<JS::Value> aData,
                         nsIDOMUserDataHandler* aHandler,
                         mozilla::ErrorResult& aError);
   JS::Value GetUserData(JSContext* aCx, const nsAString& aKey,
                         mozilla::ErrorResult& aError);
 
   // Helper method to remove this node from its parent. This is not exposed
   // through WebIDL.
   // Only call this if the node has a parent node.
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -333,18 +333,19 @@ GetParamsForMessage(JSContext* aCx,
   }
   JS_ClearPendingException(aCx);
 
   // Not clonable, try JSON
   //XXX This is ugly but currently structured cloning doesn't handle
   //    properly cases when interface is implemented in JS and used
   //    as a dictionary.
   nsAutoString json;
-  JS::Value v = aObject;
-  NS_ENSURE_TRUE(JS_Stringify(aCx, &v, nullptr, JSVAL_NULL, JSONCreator, &json), false);
+  JS::Rooted<JS::Value> v(aCx, aObject);
+  NS_ENSURE_TRUE(JS_Stringify(aCx, v.address(), nullptr, JSVAL_NULL,
+                              JSONCreator, &json), false);
   NS_ENSURE_TRUE(!json.IsEmpty(), false);
 
   JS::Rooted<JS::Value> val(aCx, JS::NullValue());
   NS_ENSURE_TRUE(JS_ParseJSON(aCx, static_cast<const jschar*>(json.get()),
                               json.Length(), &val), false);
 
   return WriteStructuredClone(aCx, val, aBuffer, aClosure);
 }
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -99,16 +99,17 @@
 #include "nsCSSRuleProcessor.h"
 #include "nsCSSParser.h"
 #include "HTMLLegendElement.h"
 #include "nsWrapperCacheInlines.h"
 #include "WrapperFactory.h"
 #include "DocumentType.h"
 #include <algorithm>
 #include "nsDOMEvent.h"
+#include "nsGlobalWindow.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsINode::nsSlots::~nsSlots()
 {
   if (mChildNodes) {
     mChildNodes->DropReference();
@@ -672,21 +673,23 @@ nsINode::SetUserData(const nsAString &aK
   }
 
   oldData.swap(*aResult);
 
   return NS_OK;
 }
 
 JS::Value
-nsINode::SetUserData(JSContext* aCx, const nsAString& aKey, JS::Value aData,
+nsINode::SetUserData(JSContext* aCx, const nsAString& aKey,
+                     JS::Handle<JS::Value> aData,
                      nsIDOMUserDataHandler* aHandler, ErrorResult& aError)
 {
   nsCOMPtr<nsIVariant> data;
-  aError = nsContentUtils::XPConnect()->JSValToVariant(aCx, &aData,
+  JS::Rooted<JS::Value> dataVal(aCx, aData);
+  aError = nsContentUtils::XPConnect()->JSValToVariant(aCx, dataVal.address(),
                                                        getter_AddRefs(data));
   if (aError.Failed()) {
     return JS::UndefinedValue();
   }
 
   nsCOMPtr<nsIVariant> oldData;
   aError = SetUserData(aKey, data, aHandler, getter_AddRefs(oldData));
   if (aError.Failed()) {
@@ -1158,16 +1161,24 @@ nsINode::GetListenerManager(bool aCreate
 }
 
 nsIScriptContext*
 nsINode::GetContextForEventHandlers(nsresult* aRv)
 {
   return nsContentUtils::GetContextForEventHandlers(this, aRv);
 }
 
+nsIDOMWindow*
+nsINode::GetOwnerGlobal()
+{
+  bool dummy;
+  return nsPIDOMWindow::GetOuterFromCurrentInner(
+    static_cast<nsGlobalWindow*>(OwnerDoc()->GetScriptHandlingObject(dummy)));
+}
+
 bool
 nsINode::UnoptimizableCCNode() const
 {
   const uintptr_t problematicFlags = (NODE_IS_ANONYMOUS |
                                       NODE_IS_IN_ANONYMOUS_SUBTREE |
                                       NODE_IS_NATIVE_ANONYMOUS_ROOT |
                                       NODE_MAY_BE_IN_BINDING_MNGR |
                                       NODE_IS_INSERTION_PARENT);
--- a/content/events/public/EventTarget.h
+++ b/content/events/public/EventTarget.h
@@ -9,16 +9,17 @@
 #include "nsIDOMEventTarget.h"
 #include "nsWrapperCache.h"
 #include "nsIDOMEventListener.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Nullable.h"
 #include "nsIAtom.h"
 
 class nsDOMEvent;
+class nsIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class EventListener;
 class EventHandlerNonNull;
 
 // IID for the dom::EventTarget interface
@@ -59,16 +60,21 @@ public:
     nsCOMPtr<nsIAtom> type = do_GetAtom(aType);
     return SetEventHandler(type, aHandler, rv);
   }
 
   // Note, for an event 'foo' aType will be 'onfoo'.
   virtual void EventListenerAdded(nsIAtom* aType) {}
   virtual void EventListenerRemoved(nsIAtom* aType) {}
 
+  // Returns an outer window that corresponds to the inner window this event
+  // target is associated with.  Will return null if the inner window is not the
+  // current inner or if there is no window around at all.
+  virtual nsIDOMWindow* GetOwnerGlobal() = 0;
+
 protected:
   EventHandlerNonNull* GetEventHandler(nsIAtom* aType);
   void SetEventHandler(nsIAtom* aType, EventHandlerNonNull* aHandler,
                        ErrorResult& rv);
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(EventTarget, NS_EVENTTARGET_IID)
 
--- a/content/events/src/nsDOMDataContainerEvent.cpp
+++ b/content/events/src/nsDOMDataContainerEvent.cpp
@@ -55,17 +55,18 @@ nsDOMDataContainerEvent::SetData(const n
   NS_ENSURE_STATE(!mEvent->mFlags.mIsBeingDispatched);
   NS_ENSURE_STATE(mData.IsInitialized());
   mData.Put(aKey, aData);
   return NS_OK;
 }
 
 void
 nsDOMDataContainerEvent::SetData(JSContext* aCx, const nsAString& aKey,
-                                 JS::Value aVal, mozilla::ErrorResult& aRv)
+                                 JS::Handle<JS::Value> aVal,
+                                 mozilla::ErrorResult& aRv)
 {
   if (!nsContentUtils::XPConnect()) {
     aRv = NS_ERROR_FAILURE;
     return;
   }
   nsCOMPtr<nsIVariant> val;
   nsresult rv =
     nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(val));
--- a/content/events/src/nsDOMDataContainerEvent.h
+++ b/content/events/src/nsDOMDataContainerEvent.h
@@ -34,18 +34,18 @@ public:
 
   already_AddRefed<nsIVariant> GetData(const nsAString& aKey)
   {
     nsCOMPtr<nsIVariant> val;
     GetData(aKey, getter_AddRefs(val));
     return val.forget();
   }
 
-  void SetData(JSContext* aCx, const nsAString& aKey, JS::Value aVal,
-               mozilla::ErrorResult& aRv);
+  void SetData(JSContext* aCx, const nsAString& aKey,
+               JS::Handle<JS::Value> aVal, mozilla::ErrorResult& aRv);
 
 private:
   static PLDHashOperator
     TraverseEntry(const nsAString& aKey, nsIVariant *aDataItem, void* aUserArg);
 
   nsInterfaceHashtable<nsStringHashKey, nsIVariant> mData;
 };
 
--- a/content/events/src/nsDOMEventTargetHelper.h
+++ b/content/events/src/nsDOMEventTargetHelper.h
@@ -90,16 +90,20 @@ public:
   nsresult SetEventHandler(nsIAtom* aType,
                            JSContext* aCx,
                            const JS::Value& aValue);
   using mozilla::dom::EventTarget::SetEventHandler;
   void GetEventHandler(nsIAtom* aType,
                        JSContext* aCx,
                        JS::Value* aValue);
   using mozilla::dom::EventTarget::GetEventHandler;
+  virtual nsIDOMWindow* GetOwnerGlobal() MOZ_OVERRIDE
+  {
+    return nsPIDOMWindow::GetOuterFromCurrentInner(GetOwner());
+  }
 
   nsresult CheckInnerWindowCorrectness()
   {
     NS_ENSURE_STATE(!mHasOrHasHadOwnerWindow || mOwnerWindow);
     if (mOwnerWindow) {
       NS_ASSERTION(mOwnerWindow->IsInnerWindow(), "Should have inner window here!\n");
       nsPIDOMWindow* outer = mOwnerWindow->GetOuterWindow();
       if (!outer || outer->GetCurrentInnerWindow() != mOwnerWindow) {
--- a/content/events/src/nsDOMMessageEvent.h
+++ b/content/events/src/nsDOMMessageEvent.h
@@ -49,17 +49,17 @@ public:
     nsCOMPtr<nsIDOMWindow> ret = mSource;
     return ret.forget();
   }
 
   void InitMessageEvent(JSContext* aCx,
                         const nsAString& aType,
                         bool aCanBubble,
                         bool aCancelable,
-                        JS::Value aData,
+                        JS::Handle<JS::Value> aData,
                         const nsAString& aOrigin,
                         const nsAString& aLastEventId,
                         nsIDOMWindow* aSource,
                         mozilla::ErrorResult& aRv)
   {
     aRv = InitMessageEvent(aType, aCanBubble, aCancelable, aData,
                            aOrigin, aLastEventId, aSource);
   }
--- a/content/events/src/nsDOMNotifyAudioAvailableEvent.h
+++ b/content/events/src/nsDOMNotifyAudioAvailableEvent.h
@@ -43,18 +43,18 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
     return mozilla::dom::NotifyAudioAvailableEventBinding::Wrap(aCx, aScope, this);
   }
 
   JSObject* GetFrameBuffer(JSContext* aCx, mozilla::ErrorResult& aRv)
   {
-    JS::Value dummy;
-    aRv = GetFrameBuffer(aCx, &dummy);
+    JS::Rooted<JS::Value> dummy(aCx);
+    aRv = GetFrameBuffer(aCx, dummy.address());
     return mCachedArray;
   }
 
   float Time()
   {
     return mTime;
   }
 
--- a/content/html/content/src/HTMLCanvasElement.cpp
+++ b/content/html/content/src/HTMLCanvasElement.cpp
@@ -719,17 +719,17 @@ HTMLCanvasElement::GetContextHelper(cons
   ctx.forget(aContext);
   return NS_OK;
 }
 
 nsresult
 HTMLCanvasElement::GetContext(const nsAString& aContextId,
                               nsISupports** aContext)
 {
-  return GetContext(aContextId, JS::UndefinedValue(), nullptr, aContext);
+  return GetContext(aContextId, JS::UndefinedHandleValue, nullptr, aContext);
 }
 
 NS_IMETHODIMP
 HTMLCanvasElement::GetContext(const nsAString& aContextId,
                               const JS::Value& aContextOptions,
                               JSContext* aCx,
                               nsISupports **aContext)
 {
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1438,17 +1438,18 @@ MediaStreamGraphImpl::AppendMessage(Cont
       mLifecycleState > LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
     // The graph control loop is not running and main thread cleanup has
     // happened. From now on we can't append messages to mCurrentTaskMessageQueue,
     // because that will never be processed again, so just RunDuringShutdown
     // this message.
     // This should only happen during forced shutdown.
     aMessage->RunDuringShutdown();
     delete aMessage;
-    if (IsEmpty()) {
+    if (IsEmpty() &&
+        mLifecycleState >= LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION) {
       if (gGraph == this) {
         gGraph = nullptr;
       }
       delete this;
     } else if (!mRealtime) {
       // Make sure to mark the graph as not doing non-realtime processing,
       // because otherwise AppendMessage will try to ensure that the graph
       // is running, and we will never manage to release our resources.
new file mode 100644
--- /dev/null
+++ b/content/media/test/crashtests/868504.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+    AudioContext().createBufferSource().playbackRate.linearRampToValueAtTime(0, -1);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/crashtests/876024-1.html
@@ -0,0 +1,5 @@
+<script>
+o1 = new window.AudioContext(2, 8, 44100);
+o4 = o1.createBiquadFilter();
+o4.detune.setValueAtTime(0.0843, 1.7976931348623157e+308);
+</script>
new file mode 100644
--- /dev/null
+++ b/content/media/test/crashtests/876024-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+  var bufferSource = AudioContext().createScriptProcessor().context.createBufferSource();
+  bufferSource.noteGrainOn(0, 0, 0);
+  bufferSource.noteOff(562949953421313);
+}
+
+</script></head>
+
+<body onload="boom();"></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/crashtests/877820.html
@@ -0,0 +1,4 @@
+<script>
+o1 = new window.OfflineAudioContext(2, 5, 0.39);
+window.location.reload();
+</script>
\ No newline at end of file
--- a/content/media/test/crashtests/crashtests.list
+++ b/content/media/test/crashtests/crashtests.list
@@ -10,21 +10,25 @@ load 492286-1.xhtml
 load 576612-1.html
 skip-if(Android||B2G) load 691096-1.html # Android sound API can't handle playing large number of sounds at once, bug 852821 for B2G
 load 752784-1.html
 skip-if(Android||B2G) HTTP load 795892-1.html # load failed, bug 833371 for B2G
 skip-if(Android||B2G) load 789075-1.html # load failed, bug 833371 for B2G
 load 844563.html
 load 846612.html
 load 852838.html
+load 868504.html
 load 874869.html
 load 874915.html
 load 874934.html
 load 874952.html
 load 875144.html
 load 875596.html
 load 875911.html
+load 876024-1.html
+load 876024-2.html
 load 876118.html
 load 876207.html
 load 876215.html
 load 876249.html
 load 876252.html
 load 876834.html
+load 877820.html
--- a/content/media/webaudio/AudioBufferSourceNode.cpp
+++ b/content/media/webaudio/AudioBufferSourceNode.cpp
@@ -475,16 +475,22 @@ AudioBufferSourceNode::WrapObject(JSCont
 {
   return AudioBufferSourceNodeBinding::Wrap(aCx, aScope, this);
 }
 
 void
 AudioBufferSourceNode::Start(double aWhen, double aOffset,
                              const Optional<double>& aDuration, ErrorResult& aRv)
 {
+  if (!WebAudioUtils::IsTimeValid(aWhen) ||
+      (aDuration.WasPassed() && !WebAudioUtils::IsTimeValid(aDuration.Value()))) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return;
+  }
+
   if (mStartCalled) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   mStartCalled = true;
 
   AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
   if (!ns) {
@@ -562,16 +568,21 @@ AudioBufferSourceNode::SendOffsetAndDura
     aStream->SetInt32Parameter(OFFSET, offsetTicks);
   }
   aStream->SetInt32Parameter(DURATION, NS_lround(endOffset*rate) - offsetTicks);
 }
 
 void
 AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv, bool aShuttingDown)
 {
+  if (!WebAudioUtils::IsTimeValid(aWhen)) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return;
+  }
+
   if (!mStartCalled) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (!mBuffer || aShuttingDown) {
     // We don't have a buffer, so the stream is never marked as finished.
     // This can also happen if the AudioContext is being shut down.
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -101,17 +101,17 @@ AudioContext::Constructor(const GlobalOb
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   if (aNumberOfChannels == 0 ||
       aNumberOfChannels > WebAudioUtils::MaxChannelCount ||
       aLength == 0 ||
-      aSampleRate <= 0.0f ||
+      aSampleRate <= 1.0f ||
       aSampleRate >= TRACK_RATE_MAX) {
     // The DOM binding protects us against infinity and NaN
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
 
   nsRefPtr<AudioContext> object = new AudioContext(window,
                                                    true,
--- a/content/media/webaudio/AudioParam.h
+++ b/content/media/webaudio/AudioParam.h
@@ -67,40 +67,61 @@ public:
         WebAudioUtils::FuzzyEqual(GetValue(), aValue)) {
       return;
     }
     AudioParamTimeline::SetValue(aValue);
     mCallback(mNode);
   }
   void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
   {
+    if (!WebAudioUtils::IsTimeValid(aStartTime)) {
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
     AudioParamTimeline::SetValueAtTime(aValue, aStartTime, aRv);
     mCallback(mNode);
   }
   void LinearRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
+    if (!WebAudioUtils::IsTimeValid(aEndTime)) {
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
     AudioParamTimeline::LinearRampToValueAtTime(aValue, aEndTime, aRv);
     mCallback(mNode);
   }
   void ExponentialRampToValueAtTime(float aValue, double aEndTime, ErrorResult& aRv)
   {
+    if (!WebAudioUtils::IsTimeValid(aEndTime)) {
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
     AudioParamTimeline::ExponentialRampToValueAtTime(aValue, aEndTime, aRv);
     mCallback(mNode);
   }
   void SetTargetAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
   {
+    if (!WebAudioUtils::IsTimeValid(aStartTime) ||
+        !WebAudioUtils::IsTimeValid(aTimeConstant)) {
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
     AudioParamTimeline::SetTargetAtTime(aTarget, aStartTime, aTimeConstant, aRv);
     mCallback(mNode);
   }
   void SetTargetValueAtTime(float aTarget, double aStartTime, double aTimeConstant, ErrorResult& aRv)
   {
     SetTargetAtTime(aTarget, aStartTime, aTimeConstant, aRv);
   }
-  void CancelScheduledValues(double aStartTime)
+  void CancelScheduledValues(double aStartTime, ErrorResult& aRv)
   {
+    if (!WebAudioUtils::IsTimeValid(aStartTime)) {
+      aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+      return;
+    }
     AudioParamTimeline::CancelScheduledValues(aStartTime);
     mCallback(mNode);
   }
 
   float DefaultValue() const
   {
     return mDefaultValue;
   }
--- a/content/media/webaudio/DelayNode.cpp
+++ b/content/media/webaudio/DelayNode.cpp
@@ -111,17 +111,17 @@ public:
   {
     if (aNumberOfChannels == 0) {
       return false;
     }
     if (mBuffer.Length() == 0) {
       if (!mBuffer.SetLength(aNumberOfChannels)) {
         return false;
       }
-      const int32_t numFrames = NS_lround(mMaxDelay) * aSampleRate;
+      const int32_t numFrames = NS_lround(mMaxDelay * aSampleRate);
       for (uint32_t channel = 0; channel < aNumberOfChannels; ++channel) {
         if (!mBuffer[channel].SetLength(numFrames)) {
           return false;
         }
         memset(mBuffer[channel].Elements(), 0, numFrames * sizeof(float));
       }
     } else if (mBuffer.Length() != aNumberOfChannels) {
       // TODO: Handle changes in the channel count
--- a/content/media/webaudio/WebAudioUtils.h
+++ b/content/media/webaudio/WebAudioUtils.h
@@ -18,17 +18,17 @@ namespace mozilla {
 
 class AudioNodeStream;
 
 namespace dom {
 
 struct WebAudioUtils {
   // This is an arbitrary large number used to protect against OOMs.
   // We can adjust it later if needed.
-  static const uint32_t MaxChannelCount = 10000;
+  static const uint32_t MaxChannelCount = 32;
 
   static bool FuzzyEqual(float v1, float v2)
   {
     using namespace std;
     return fabsf(v1 - v2) < 1e-7f;
   }
   static bool FuzzyEqual(double v1, double v2)
   {
@@ -99,16 +99,21 @@ struct WebAudioUtils {
     }
   }
 
   static double DiscreteTimeConstantForSampleRate(double timeConstant, double sampleRate)
   {
     return 1.0 - std::exp(-1.0 / (sampleRate * timeConstant));
   }
 
+  static bool IsTimeValid(double aTime)
+  {
+    return aTime >= 0 &&  aTime <= (MEDIA_TIME_MAX >> MEDIA_TIME_FRAC_BITS);
+  }
+
   /**
    * Convert a stream position into the time coordinate of the destination
    * stream.
    */
   static double StreamPositionToDestinationTime(TrackTicks aSourcePosition,
                                                 AudioNodeStream* aSource,
                                                 AudioNodeStream* aDestination);
 
--- a/content/media/webaudio/test/Makefile.in
+++ b/content/media/webaudio/test/Makefile.in
@@ -48,16 +48,17 @@ MOCHITEST_FILES := \
   test_badConnect.html \
   test_biquadFilterNode.html \
   test_channelMergerNode.html \
   test_channelMergerNodeWithVolume.html \
   test_channelSplitterNode.html \
   test_channelSplitterNodeWithVolume.html \
   test_currentTime.html \
   test_delayNode.html \
+  test_delayNodeSmallMaxDelay.html \
   test_delayNodeWithGain.html \
   test_delayNodeWithGainAlternate.html \
   test_dynamicsCompressorNode.html \
   test_gainNode.html \
   test_gainNodeInLoop.html \
   test_mediaDecoding.html \
   test_mixingRules.html \
   test_nodeToParamConnection.html \
--- a/content/media/webaudio/test/test_OfflineAudioContext.html
+++ b/content/media/webaudio/test/test_OfflineAudioContext.html
@@ -27,19 +27,19 @@ addLoadEvent(function() {
     new OfflineAudioContext(2, 100, 0);
   }, DOMException.NOT_SUPPORTED_ERR);
   expectException(function() {
     new OfflineAudioContext(2, 100, -1);
   }, DOMException.NOT_SUPPORTED_ERR);
   expectException(function() {
     new OfflineAudioContext(0, 100, 44100);
   }, DOMException.NOT_SUPPORTED_ERR);
-  new OfflineAudioContext(10000, 100, 44100);
+  new OfflineAudioContext(32, 100, 44100);
   expectException(function() {
-    new OfflineAudioContext(10001, 100, 44100);
+    new OfflineAudioContext(33, 100, 44100);
   }, DOMException.NOT_SUPPORTED_ERR);
   expectException(function() {
     new OfflineAudioContext(2, 0, 44100);
   }, DOMException.NOT_SUPPORTED_ERR);
 
   var src = ctx.createBufferSource();
   src.buffer = buf;
   src.start(0);
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_delayNodeSmallMaxDelay.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test DelayNode</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+  length: 2048,
+  numberOfChannels: 1,
+  createGraph: function(context) {
+    var source = context.createBufferSource();
+
+    var delay = context.createDelay(0.02);
+
+    source.buffer = this.buffer;
+
+    source.connect(delay);
+
+    source.start(0);
+    return delay;
+  },
+  createExpectedBuffers: function(context) {
+    var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+    this.buffer = expectedBuffer;
+    return expectedBuffer;
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/media/wmf/WMFReader.cpp
+++ b/content/media/wmf/WMFReader.cpp
@@ -43,20 +43,23 @@ WMFReader::WMFReader(AbstractMediaDecode
   : MediaDecoderReader(aDecoder),
     mSourceReader(nullptr),
     mAudioChannels(0),
     mAudioBytesPerSample(0),
     mAudioRate(0),
     mVideoWidth(0),
     mVideoHeight(0),
     mVideoStride(0),
+    mAudioFrameSum(0),
+    mAudioFrameOffset(0),
     mHasAudio(false),
     mHasVideo(false),
     mCanSeek(false),
     mUseHwAccel(false),
+    mMustRecaptureAudioPosition(true),
     mIsMP3Enabled(WMFDecoder::IsMP3Supported())
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
   MOZ_COUNT_CTOR(WMFReader);
 }
 
 WMFReader::~WMFReader()
 {
@@ -600,16 +603,41 @@ WMFReader::ReadMetadata(VideoInfo* aInfo
 static int64_t
 GetSampleDuration(IMFSample* aSample)
 {
   int64_t duration = 0;
   aSample->GetSampleDuration(&duration);
   return HNsToUsecs(duration);
 }
 
+HRESULT
+HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames)
+{
+  MOZ_ASSERT(aOutFrames);
+  const int64_t HNS_PER_S = USECS_PER_S * 10;
+  CheckedInt<int64_t> i = aHNs;
+  i *= aRate;
+  i /= HNS_PER_S;
+  NS_ENSURE_TRUE(i.isValid(), E_FAIL);
+  *aOutFrames = i.value();
+  return S_OK;
+}
+
+HRESULT
+FramesToUsecs(int64_t aSamples, uint32_t aRate, int64_t* aOutUsecs)
+{
+  MOZ_ASSERT(aOutUsecs);
+  CheckedInt<int64_t> i = aSamples;
+  i *= USECS_PER_S;
+  i /= aRate;
+  NS_ENSURE_TRUE(i.isValid(), E_FAIL);
+  *aOutUsecs = i.value();
+  return S_OK;
+}
+
 bool
 WMFReader::DecodeAudioData()
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   HRESULT hr;
   hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM,
                                  0, // control flags
@@ -655,21 +683,43 @@ WMFReader::DecodeAudioData()
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
 
   uint32_t numFrames = currentLength / mAudioBytesPerSample / mAudioChannels;
   NS_ASSERTION(sizeof(AudioDataValue) == mAudioBytesPerSample, "Size calculation is wrong");
   nsAutoArrayPtr<AudioDataValue> pcmSamples(new AudioDataValue[numFrames * mAudioChannels]);
   memcpy(pcmSamples.get(), data, currentLength);
   buffer->Unlock();
 
-  int64_t offset = mDecoder->GetResource()->Tell();
-  int64_t timestamp = HNsToUsecs(timestampHns);
-  int64_t duration = GetSampleDuration(sample);
+  // We calculate the timestamp and the duration based on the number of audio
+  // frames we've already played. We don't trust the timestamp stored on the
+  // IMFSample, as sometimes it's wrong, possibly due to buggy encoders?
 
-  mAudioQueue.Push(new AudioData(offset,
+  // If this sample block comes after a discontinuity (i.e. a gap or seek)
+  // reset the frame counters, and capture the timestamp. Future timestamps
+  // will be offset from this block's timestamp.
+  UINT32 discontinuity = false;
+  sample->GetUINT32(MFSampleExtension_Discontinuity, &discontinuity);
+  if (mMustRecaptureAudioPosition || discontinuity) {
+    mAudioFrameSum = 0;
+    hr = HNsToFrames(timestampHns, mAudioRate, &mAudioFrameOffset);
+    NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+    mMustRecaptureAudioPosition = false;
+  }
+
+  int64_t timestamp;
+  hr = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, mAudioRate, &timestamp);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+  mAudioFrameSum += numFrames;
+
+  int64_t duration;
+  hr = FramesToUsecs(numFrames, mAudioRate, &duration);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), false);
+
+  mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
                                  timestamp,
                                  duration,
                                  numFrames,
                                  pcmSamples.forget(),
                                  mAudioChannels));
 
   #ifdef LOG_SAMPLE_DECODE
   LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u",
@@ -896,17 +946,17 @@ WMFReader::DecodeVideoFrame(bool &aKeyfr
   NS_ENSURE_TRUE(SUCCEEDED(hr) && v, false);
 
   parsed++;
   decoded++;
   mVideoQueue.Push(v);
 
   #ifdef LOG_SAMPLE_DECODE
   LOG("Decoded video sample timestamp=%lld duration=%lld stride=%d height=%u flags=%u",
-      timestamp, duration, stride, mVideoHeight, flags);
+      timestamp, duration, mVideoStride, mVideoHeight, flags);
   #endif
 
   if ((flags & MF_SOURCE_READERF_ENDOFSTREAM)) {
     // End of stream.
     mVideoQueue.Finish();
     LOG("End of video stream");
     return false;
   }
@@ -925,16 +975,22 @@ WMFReader::Seek(int64_t aTargetUs,
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   if (!mCanSeek) {
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = ResetDecode();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Mark that we must recapture the audio frame count from the next sample.
+  // WMF doesn't set a discontinuity marker when we seek to time 0, so we
+  // must remember to recapture the audio frame offset and reset the frame
+  // sum on the next audio packet we decode.
+  mMustRecaptureAudioPosition = true;
+
   AutoPropVar var;
   HRESULT hr = InitPropVariantFromInt64(UsecsToHNs(aTargetUs), &var);
   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
   hr = mSourceReader->SetCurrentPosition(GUID_NULL, var);
   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
   return DecodeToTarget(aTargetUs);
--- a/content/media/wmf/WMFReader.h
+++ b/content/media/wmf/WMFReader.h
@@ -86,16 +86,27 @@ private:
   uint32_t mAudioChannels;
   uint32_t mAudioBytesPerSample;
   uint32_t mAudioRate;
 
   uint32_t mVideoWidth;
   uint32_t mVideoHeight;
   uint32_t mVideoStride;
 
+  // The offset, in audio frames, at which playback started since the
+  // last discontinuity.
+  int64_t mAudioFrameOffset;
+  // The number of audio frames that we've played since the last
+  // discontinuity.
+  int64_t mAudioFrameSum;
+  // True if we need to re-initialize mAudioFrameOffset and mAudioFrameSum
+  // from the next audio packet we decode. This happens after a seek, since
+  // WMF doesn't mark a stream as having a discontinuity after a seek(0).
+  bool mMustRecaptureAudioPosition;
+
   bool mHasAudio;
   bool mHasVideo;
   bool mCanSeek;
   bool mUseHwAccel;
 
   // We can't call WMFDecoder::IsMP3Supported() on non-main threads, since it
   // checks a pref, so we cache its value in mIsMP3Enabled and use that on
   // the decode thread.
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7067,18 +7067,19 @@ nsGlobalWindow::FinalClose()
   //   along with the accompanying "domwindowclosed" notification. But _only_ if
   //   |win|'s JSContext is not at the top of the stack. If it is, the close
   //   _must not_ happen immediately.
   //
   // * If |win|'s JSContext is at the top of the stack, we must complete _two_
   //   round-trips to the event loop before the call to ReallyCloseWindow. This
   //   allows setTimeout handlers that are set after FinalClose() is called to
   //   run before the window is torn down.
-  bool indirect = nsContentUtils::GetCurrentJSContext() ==
-                  GetContextInternal()->GetNativeContext();
+  bool indirect = GetContextInternal() && // Occasionally null. See bug 877390.
+                  (nsContentUtils::GetCurrentJSContext() ==
+                   GetContextInternal()->GetNativeContext());
   if ((!indirect && nsContentUtils::IsCallerChrome()) ||
       NS_FAILED(nsCloseEvent::PostCloseEvent(this, indirect))) {
     ReallyCloseWindow();
   } else {
     mHavePendingClose = true;
   }
 
   return NS_OK;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -387,16 +387,24 @@ public:
   // nsIDOMEventTarget
   NS_DECL_NSIDOMEVENTTARGET
   using mozilla::dom::EventTarget::RemoveEventListener;
   virtual void AddEventListener(const nsAString& aType,
                                 nsIDOMEventListener* aListener,
                                 bool aUseCapture,
                                 const mozilla::dom::Nullable<bool>& aWantsUntrusted,
                                 mozilla::ErrorResult& aRv) MOZ_OVERRIDE;
+  virtual nsIDOMWindow* GetOwnerGlobal() MOZ_OVERRIDE
+  {
+    if (IsOuterWindow()) {
+      return this;
+    }
+
+    return GetOuterFromCurrentInner(this);
+  }
 
   // nsITouchEventReceiver
   NS_DECL_NSITOUCHEVENTRECEIVER
 
   // nsIInlineEventHandlers
   NS_DECL_NSIINLINEEVENTHANDLERS
 
   // nsPIDOMWindow
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -53,18 +53,18 @@ class nsPIWindowRoot;
 
 namespace mozilla {
 namespace dom {
 class AudioContext;
 }
 }
 
 #define NS_PIDOMWINDOW_IID \
-{ 0x7202842a, 0x0e24, 0x46dc, \
-  { 0xb2, 0x25, 0xd2, 0x9d, 0x28, 0xda, 0x87, 0xd8 } }
+{ 0xc7f20d00, 0xed38, 0x4d60, \
+ { 0x90, 0xf6, 0x3e, 0xde, 0x7b, 0x71, 0xc3, 0xb3 } }
 
 class nsPIDOMWindow : public nsIDOMWindowInternal
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOW_IID)
 
   virtual nsPIDOMWindow* GetPrivateRoot() = 0;
 
@@ -640,16 +640,32 @@ public:
    * Like nsIDOMWindow::Open, except that we don't navigate to the given URL.
    */
   virtual nsresult
   OpenNoNavigate(const nsAString& aUrl, const nsAString& aName,
                  const nsAString& aOptions, nsIDOMWindow **_retval) = 0;
 
   void AddAudioContext(mozilla::dom::AudioContext* aAudioContext);
 
+  // Given an inner window, return its outer if the inner is the current inner.
+  // Otherwise (argument null or not an inner or not current) return null.
+  static nsPIDOMWindow* GetOuterFromCurrentInner(nsPIDOMWindow* aInner)
+  {
+    if (!aInner) {
+      return nullptr;
+    }
+
+    nsPIDOMWindow* outer = aInner->GetOuterWindow();
+    if (!outer || outer->GetCurrentInnerWindow() != aInner) {
+      return nullptr;
+    }
+
+    return outer;
+  }
+
   // WebIDL-ish APIs
   nsPerformance* GetPerformance();
 
 protected:
   // The nsPIDOMWindow constructor. The aOuterWindow argument should
   // be null if and only if the created window itself is an outer
   // window. In all other cases aOuterWindow should be the outer
   // window for the inner window that is being created.
--- a/dom/base/nsPIWindowRoot.h
+++ b/dom/base/nsPIWindowRoot.h
@@ -10,20 +10,19 @@
 #include "nsISupports.h"
 #include "mozilla/dom/EventTarget.h"
 
 class nsPIDOMWindow;
 class nsIControllers;
 class nsIController;
 struct JSContext;
 
-// 426C1B56-E38A-435E-B291-BE1557F2A0A2
 #define NS_IWINDOWROOT_IID \
-{ 0xc89780f2, 0x8905, 0x417f, \
-  { 0xa6, 0x62, 0xf6, 0xc, 0xa6, 0xd7, 0xc, 0x91 } }
+{ 0x3f71f50c, 0xa7e0, 0x43bc, \
+ { 0xac, 0x25, 0x4d, 0xbb, 0x88, 0x7b, 0x21, 0x09 } }
 
 class nsPIWindowRoot : public mozilla::dom::EventTarget
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWINDOWROOT_IID)
 
   virtual nsPIDOMWindow* GetWindow()=0;
 
@@ -32,13 +31,14 @@ public:
   virtual void SetPopupNode(nsIDOMNode* aNode) = 0;
 
   virtual nsresult GetControllerForCommand(const char *aCommand,
                                            nsIController** aResult) = 0;
   virtual nsresult GetControllers(nsIControllers** aResult) = 0;
 
   virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) = 0;
   virtual mozilla::dom::EventTarget* GetParentTarget() = 0;
+  virtual nsIDOMWindow* GetOwnerGlobal() MOZ_OVERRIDE = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIWindowRoot, NS_IWINDOWROOT_IID)
 
 #endif // nsPIWindowRoot_h__
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -174,16 +174,22 @@ nsWindowRoot::PreHandleEvent(nsEventChai
 }
 
 nsresult
 nsWindowRoot::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
 {
   return NS_OK;
 }
 
+nsIDOMWindow*
+nsWindowRoot::GetOwnerGlobal()
+{
+  return GetWindow();
+}
+
 nsPIDOMWindow*
 nsWindowRoot::GetWindow()
 {
   return mWindow;
 }
 
 nsresult
 nsWindowRoot::GetControllers(nsIControllers** aResult)
--- a/dom/base/nsWindowRoot.h
+++ b/dom/base/nsWindowRoot.h
@@ -45,16 +45,17 @@ public:
   virtual nsIDOMNode* GetPopupNode();
   virtual void SetPopupNode(nsIDOMNode* aNode);
 
   virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget)
   {
     mParent = aTarget;
   }
   virtual mozilla::dom::EventTarget* GetParentTarget() { return mParent; }
+  virtual nsIDOMWindow* GetOwnerGlobal() MOZ_OVERRIDE;
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsWindowRoot,
                                                          nsIDOMEventTarget)
 
 protected:
   // Members
   nsPIDOMWindow* mWindow; // [Weak]. The window will hold on to us and let go when it dies.
   nsRefPtr<nsEventListenerManager> mListenerManager; // [Strong]. We own the manager, which owns event listeners attached
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -594,18 +594,16 @@ DOMInterfaces = {
 {
     'nativeType': 'JSObject',
     'workers': True,
     'skipGen': True
 }],
 
 'MediaStreamList': {
     'headerFile': 'MediaStreamList.h',
-    'wrapperCache': False,
-    'nativeOwnership': 'owned',
     'resultNotAddRefed': [ '__indexedGetter' ],
     'binaryNames': { '__indexedGetter': 'IndexedGetter' }
 },
 
 'MediaStreamTrack': {
     'concrete': False
 },
 
@@ -1208,16 +1206,27 @@ DOMInterfaces = {
     'implicitJSContext': [ 'constructor' ]
 },
 
 'WheelEvent': {
     'headerFile': 'DOMWheelEvent.h',
     'nativeType': 'mozilla::dom::DOMWheelEvent',
 },
 
+'WindowProxy': [
+{
+    'nativeType': 'nsIDOMWindow',
+    'concrete': False
+},
+{
+    # We need a worker descriptor for WindowProxy because EventTarget exists in
+    # workers.  But it's an external interface, so it'll just map to JSObject*.
+    'workers': True
+}],
+
 'XMLHttpRequest': [
 {
     'nativeType': 'nsXMLHttpRequest',
     'implicitJSContext': [ 'constructor', ],
     'resultNotAddRefed': [ 'upload', 'responseXML' ]
 },
 {
     'workers': True,
@@ -1611,14 +1620,13 @@ addExternalIface('SVGAnimatedNumber')
 addExternalIface('SVGAnimatedString')
 addExternalIface('SVGLength')
 addExternalIface('SVGNumber')
 addExternalIface('TouchList', headerFile='nsIDOMTouchEvent.h')
 addExternalIface('URI', nativeType='nsIURI', headerFile='nsIURI.h',
                  notflattened=True)
 addExternalIface('UserDataHandler')
 addExternalIface('Window')
-addExternalIface('WindowProxy', nativeType='nsIDOMWindow')
 addExternalIface('XPathResult', nativeType='nsISupports')
 addExternalIface('XPathExpression')
 addExternalIface('XPathNSResolver')
 addExternalIface('XULCommandDispatcher')
 addExternalIface('DataTransfer', notflattened=True)
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -5099,17 +5099,17 @@ class CGGenericMethod(CGAbstractBindingM
         args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
                 Argument('JS::Value*', 'vp')]
         CGAbstractBindingMethod.__init__(self, descriptor, 'genericMethod', args)
 
     def generate_code(self):
         return CGIndenter(CGGeneric(
             "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
             "MOZ_ASSERT(info->type == JSJitInfo::Method);\n"
-            "JSJitMethodOp method = (JSJitMethodOp)info->op;\n"
+            "JSJitMethodOp method = info->method;\n"
             "return method(cx, obj, self, argc, vp);"))
 
 class CGSpecializedMethod(CGAbstractStaticMethod):
     """
     A class for generating the C++ code for a specialized method that the JIT
     can call with lower overhead.
     """
     def __init__(self, descriptor, method):
@@ -5235,17 +5235,17 @@ class CGGenericGetter(CGAbstractBindingM
             unwrapFailureCode = None
         CGAbstractBindingMethod.__init__(self, descriptor, name, args,
                                          unwrapFailureCode)
 
     def generate_code(self):
         return CGIndenter(CGGeneric(
             "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
             "MOZ_ASSERT(info->type == JSJitInfo::Getter);\n"
-            "JSJitPropertyOp getter = info->op;\n"
+            "JSJitGetterOp getter = info->getter;\n"
             "return getter(cx, obj, self, vp);"))
 
 class CGSpecializedGetter(CGAbstractStaticMethod):
     """
     A class for generating the code for a specialized attribute getter
     that the JIT can call with lower overhead.
     """
     def __init__(self, descriptor, attr):
@@ -5319,17 +5319,17 @@ class CGGenericSetter(CGAbstractBindingM
     def generate_code(self):
         return CGIndenter(CGGeneric(
                 "if (argc == 0) {\n"
                 '  return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "%s attribute setter");\n'
                 "}\n"
                 "JS::Value* argv = JS_ARGV(cx, vp);\n"
                 "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
                 "MOZ_ASSERT(info->type == JSJitInfo::Setter);\n"
-                "JSJitPropertyOp setter = info->op;\n"
+                "JSJitSetterOp setter = info->setter;\n"
                 "if (!setter(cx, obj, self, argv)) {\n"
                 "  return false;\n"
                 "}\n"
                 "*vp = JSVAL_VOID;\n"
                 "return true;" % self.descriptor.interface.identifier.name))
 
 class CGSpecializedSetter(CGAbstractStaticMethod):
     """
@@ -5426,53 +5426,57 @@ class CGMemberJITInfo(CGThing):
         depth = "PrototypeTraits<%s>::Depth" % protoID
         failstr = toStringBool(infallible)
         conststr = toStringBool(constant)
         purestr = toStringBool(pure)
         returnType = reduce(CGMemberJITInfo.getSingleReturnType, returnTypes,
                             "")
         return ("\n"
                 "static const JSJitInfo %s = {\n"
-                "  %s,\n"
+                "  { %s },\n"
                 "  %s,\n"
                 "  %s,\n"
                 "  JSJitInfo::%s,\n"
                 "  %s,  /* isInfallible. False in setters. */\n"
                 "  %s,  /* isConstant. Only relevant for getters. */\n"
                 "  %s,  /* isPure.  Only relevant for getters. */\n"
                 "  %s   /* returnType.  Only relevant for getters/methods. */\n"
                 "};\n" % (infoName, opName, protoID, depth, opType, failstr,
                           conststr, purestr, returnType))
 
     def define(self):
         if self.member.isAttr():
             getterinfo = ("%s_getterinfo" % self.member.identifier.name)
-            getter = ("(JSJitPropertyOp)get_%s" % self.member.identifier.name)
+            # We need the cast here because JSJitGetterOp has a "void* self"
+            # while we have the right type.
+            getter = ("(JSJitGetterOp)get_%s" % self.member.identifier.name)
             getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True)
             getterconst = self.member.getExtendedAttribute("Constant")
             getterpure = getterconst or self.member.getExtendedAttribute("Pure")
             assert (getterinfal or (not getterconst and not getterpure))
 
             getterinfal = getterinfal and infallibleForMember(self.member, self.member.type, self.descriptor)
             result = self.defineJitInfo(getterinfo, getter, "Getter",
                                         getterinfal, getterconst, getterpure,
                                         [self.member.type])
             if not self.member.readonly or self.member.getExtendedAttribute("PutForwards") is not None:
                 setterinfo = ("%s_setterinfo" % self.member.identifier.name)
-                setter = ("(JSJitPropertyOp)set_%s" % self.member.identifier.name)
+                # Actually a JSJitSetterOp, but JSJitGetterOp is first in the
+                # union.
+                setter = ("(JSJitGetterOp)set_%s" % self.member.identifier.name)
                 # Setters are always fallible, since they have to do a typed unwrap.
                 result += self.defineJitInfo(setterinfo, setter, "Setter",
                                              False, False, False,
                                              [BuiltinTypes[IDLBuiltinType.Types.void]])
             return result
         if self.member.isMethod():
             methodinfo = ("%s_methodinfo" % self.member.identifier.name)
             name = CppKeywords.checkMethodName(self.member.identifier.name)
-            # Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition.
-            method = ("(JSJitPropertyOp)%s" % name)
+            # Actually a JSJitMethodOp, but JSJitGetterOp is first in the union.
+            method = ("(JSJitGetterOp)%s" % name)
 
             # Methods are infallible if they are infallible, have no arguments
             # to unwrap, and have a return type that's infallible to wrap up for
             # return.
             sigs = self.member.signatures()
             if len(sigs) != 1:
                 # Don't handle overloading.  If there's more than one signature,
                 # one of them must take arguments.
@@ -7507,16 +7511,22 @@ class CGDescriptor(CGThing):
             not descriptor.workers):
             if descriptor.interface.getExtendedAttribute("PrefControlled") is not None:
                 cgThings.append(CGPrefEnabledNative(descriptor))
             elif descriptor.interface.getExtendedAttribute("Pref") is not None:
                 cgThings.append(CGPrefEnabled(descriptor))
 
         if descriptor.concrete:
             if descriptor.proxy:
+                if descriptor.nativeOwnership != 'nsisupports':
+                    raise TypeError("We don't support non-nsISupports native classes for "
+                                    "proxy-based bindings yet (" + descriptor.name + ")")
+                if not descriptor.wrapperCache:
+                    raise TypeError("We need a wrappercache to support expandos for proxy-based "
+                                    "bindings (" + descriptor.name + ")")
                 cgThings.append(CGProxyIsProxy(descriptor))
                 cgThings.append(CGProxyUnwrap(descriptor))
                 cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor))
                 cgThings.append(CGDOMJSProxyHandler(descriptor))
                 cgThings.append(CGIsMethod(descriptor))
             else:
                 cgThings.append(CGDOMJSClass(descriptor))
 
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_TypedArray_h
 #define mozilla_dom_TypedArray_h
 
 #include "jsfriendapi.h"
+#include "js/RootingAPI.h"
 
 namespace mozilla {
 namespace dom {
 
 /*
  * Various typed array classes for argument conversion.  We have a base class
  * that has a way of initializing a TypedArray from an existing typed array, and
  * a subclass of the base class that supports creation of a relevant typed array
@@ -60,17 +61,17 @@ template<typename T,
 struct TypedArray : public TypedArray_base<T,UnboxArray> {
   TypedArray(JSObject* obj) :
     TypedArray_base<T,UnboxArray>(obj)
   {}
 
   static inline JSObject*
   Create(JSContext* cx, nsWrapperCache* creator, uint32_t length,
          const T* data = NULL) {
-    JSObject* creatorWrapper;
+    JS::Rooted<JSObject*> creatorWrapper(cx);
     Maybe<JSAutoCompartment> ac;
     if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) {
       ac.construct(cx, creatorWrapper);
     }
     JSObject* obj = CreateNew(cx, length);
     if (!obj) {
       return NULL;
     }
--- a/dom/interfaces/json/nsIJSON.idl
+++ b/dom/interfaces/json/nsIJSON.idl
@@ -8,19 +8,19 @@
 interface nsIInputStream;
 interface nsIOutputStream;
 interface nsIScriptGlobalObject;
 
 [ptr] native JSValPtr(jsval);
 [ptr] native JSContext(JSContext);
 
 /**
- * Encode and decode JSON text.
+ * Don't use this!  Use JSON.parse and JSON.stringify directly.
  */
-[scriptable, uuid(43845d58-1054-47fb-8be3-970b3f7bd7ea)]
+[scriptable, uuid(083aebb0-7790-43b2-ae81-9e404e626236)]
 interface nsIJSON : nsISupports
 {
   /**
    * New users should use JSON.stringify!
    * The encode() method is only present for backward compatibility.
    * encode() is not a conforming JSON stringify implementation!
    */
   [deprecated,implicit_jscontext,optional_argc]
@@ -49,34 +49,9 @@ interface nsIJSON : nsISupports
   [implicit_jscontext]
   jsval decodeFromStream(in nsIInputStream stream,
                          in long contentLength);
 
   [noscript] AString  encodeFromJSVal(in JSValPtr value, in JSContext cx);
 
   // Make sure you GCroot the result of this function before using it.
   [noscript] jsval    decodeToJSVal(in AString str, in JSContext cx);
-
-
-  /*
-   * Decode a JSON string, but also accept some strings in non-JSON format, as
-   * the decoding methods here did previously before tightening.
-   *
-   * This method is provided only as a temporary transition path for users of
-   * the old code who depended on the ability to decode leniently; new users
-   * should use JSON.parse.
-   *
-   * This method must only be called from script.
-   *
-   * @param str the string to parse
-   */
-  [implicit_jscontext]
-  jsval legacyDecode(in AString str);
-
-  /* Identical to legacyDecode, but decode the contents of stream. */
-  [implicit_jscontext]
-  jsval legacyDecodeFromStream(in nsIInputStream stream,
-                               in long contentLength);
-
-  /* Identical to legacyDecode, but decode into a jsval. */
-  // Make sure you GCroot the result of this function before using it.
-  [noscript] jsval    legacyDecodeToJSVal(in AString str, in JSContext cx);
 };
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -349,17 +349,17 @@ TabChild::Observe(nsISupports *aSubject,
         mLastMetrics.mZoom = gfxSize(1.0, 1.0);
         mLastMetrics.mViewport =
             gfx::Rect(0, 0,
                       kDefaultViewportSize.width, kDefaultViewportSize.height);
         mLastMetrics.mCompositionBounds = nsIntRect(nsIntPoint(0, 0),
                                                     mInnerSize);
         mLastMetrics.mResolution =
           AsyncPanZoomController::CalculateResolution(mLastMetrics);
-        mLastMetrics.mScrollOffset = gfx::Point(0, 0);
+        mLastMetrics.mScrollOffset = CSSPoint(0, 0);
         utils->SetResolution(mLastMetrics.mResolution.width,
                              mLastMetrics.mResolution.height);
 
         HandlePossibleViewportChange();
       }
     }
   } else if (!strcmp(aTopic, DETECT_SCROLLABLE_SUBFRAME)) {
     nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
@@ -1447,17 +1447,17 @@ TabChild::DispatchMessageManagerMessage(
     // content manipulate the frame state.
     nsRefPtr<nsFrameMessageManager> mm =
       static_cast<nsFrameMessageManager*>(mTabChildGlobal->mMessageManager.get());
     mm->ReceiveMessage(static_cast<EventTarget*>(mTabChildGlobal),
                        aMessageName, false, &cloneData, JS::NullPtr(), nullptr);
 }
 
 static void
-ScrollWindowTo(nsIDOMWindow* aWindow, const mozilla::gfx::Point& aPoint)
+ScrollWindowTo(nsIDOMWindow* aWindow, const CSSPoint& aPoint)
 {
     nsGlobalWindow* window = static_cast<nsGlobalWindow*>(aWindow);
     nsIScrollableFrame* sf = window->GetScrollFrame();
 
     if (sf) {
         sf->ScrollToCSSPixelsApproximate(aPoint);
     }
 }
--- a/dom/messages/SystemMessageInternal.js
+++ b/dom/messages/SystemMessageInternal.js
@@ -238,34 +238,50 @@ SystemMessageInternal.prototype = {
       let target = aTargets[index];
       if (target.target === aTarget) {
         return index;
       }
     }
     return -1;
   },
 
-  _removeTargetFromListener: function _removeTargetFromListener(aTarget, aManifest, aRemoveListener) {
+  _isEmptyObject: function _isEmptyObject(aObj) {
+    for (let name in aObj) {
+      return false;
+    }
+    return true;
+  },
+
+  _removeTargetFromListener: function _removeTargetFromListener(aTarget,
+                                                                aManifest,
+                                                                aRemoveListener,
+                                                                aUri) {
     let targets = this._listeners[aManifest];
     if (!targets) {
       return false;
     }
 
     let index = this._findTargetIndex(targets, aTarget);
     if (index === -1) {
       return false;
     }
 
     if (aRemoveListener) {
       debug("remove the listener for " + aManifest);
       delete this._listeners[aManifest];
       return true;
     }
 
-    if (--targets[index].winCount === 0) {
+    let target = targets[index];
+    if (aUri && target.winCounts[aUri] !== undefined &&
+        --target.winCounts[aUri] === 0) {
+      delete target.winCounts[aUri];
+    }
+
+    if (this._isEmptyObject(target.winCounts)) {
       if (targets.length === 1) {
         // If it's the only one, get rid of this manifest entirely.
         debug("remove the listener for " + aManifest);
         delete this._listeners[aManifest];
       } else {
         // If more than one left, remove this one and leave the rest.
         targets.splice(index, 1);
       }
@@ -292,47 +308,55 @@ SystemMessageInternal.prototype = {
 
     switch(aMessage.name) {
       case "SystemMessageManager:AskReadyToRegister":
         return true;
         break;
       case "SystemMessageManager:Register":
       {
         debug("Got Register from " + msg.uri + " @ " + msg.manifest);
+        let uri = msg.uri;
         let targets, index;
         if (!(targets = this._listeners[msg.manifest])) {
+          let winCounts = {};
+          winCounts[uri] = 1;
           this._listeners[msg.manifest] = [{ target: aMessage.target,
-                                             uri: msg.uri,
-                                             winCount: 1 }];
+                                             winCounts: winCounts }];
         } else if ((index = this._findTargetIndex(targets, aMessage.target)) === -1) {
+          let winCounts = {};
+          winCounts[uri] = 1;
           targets.push({ target: aMessage.target,
-                         uri: msg.uri,
-                         winCount: 1 });
+                         winCounts: winCounts });
         } else {
-          targets[index].winCount++;
+          let winCounts = targets[index].winCounts;
+          if (winCounts[uri] === undefined) {
+            winCounts[uri] = 1;
+          } else {
+            winCounts[uri]++;
+          }
         }
 
         debug("listeners for " + msg.manifest + " innerWinID " + msg.innerWindowID);
         break;
       }
       case "child-process-shutdown":
       {
         debug("Got child-process-shutdown from " + aMessage.target);
         for (let manifest in this._listeners) {
           // See if any processes in this manifest have this target.
-          if (this._removeTargetFromListener(aMessage.target, manifest, true)) {
+          if (this._removeTargetFromListener(aMessage.target, manifest, true, null)) {
             break;
           }
         }
         break;
       }
       case "SystemMessageManager:Unregister":
       {
         debug("Got Unregister from " + aMessage.target + "innerWinID " + msg.innerWindowID);
-        this._removeTargetFromListener(aMessage.target, msg.manifest, false);
+        this._removeTargetFromListener(aMessage.target, msg.manifest, false, msg.uri);
         break;
       }
       case "SystemMessageManager:GetPendingMessages":
       {
         debug("received SystemMessageManager:GetPendingMessages " + msg.type +
           " for " + msg.uri + " @ " + msg.manifest);
 
         // This is a sync call used to return the pending messages for a page.
@@ -516,17 +540,17 @@ SystemMessageInternal.prototype = {
                                            uri: aPageURI })
 
     let targets = this._listeners[aManifestURI];
     if (targets) {
       for (let index = 0; index < targets.length; ++index) {
         let target = targets[index];
         // We only need to send the system message to the targets which match
         // the manifest URL and page URL of the destination of system message.
-        if (target.uri != aPageURI) {
+        if (target.winCounts[aPageURI] === undefined) {
           continue;
         }
 
         appPageIsRunning = true;
         // We need to acquire a CPU wake lock for that page and expect that
         // we'll receive a "SystemMessageManager:HandleMessagesDone" message
         // when the page finishes handling the system message. At that point,
         // we'll release the lock we acquired.
--- a/dom/messages/SystemMessageManager.js
+++ b/dom/messages/SystemMessageManager.js
@@ -139,16 +139,17 @@ SystemMessageManager.prototype = {
     if (this._isInBrowserElement) {
       debug("the app loaded in the browser doesn't need to unregister " +
             "the manifest for listening to the system messages");
       return;
     }
 
     cpmm.sendAsyncMessage("SystemMessageManager:Unregister",
                           { manifest: this._manifest,
+                            uri: this._uri,
                             innerWindowID: this.innerWindowID });
   },
 
   // Possible messages:
   //
   //   - SystemMessageManager:Message
   //     This one will only be received when the child process is alive when
   //     the message is initially sent.
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -396,35 +396,34 @@ nsJSON::DecodeToJSVal(const nsAString &s
   return NS_OK;
 }
 
 nsresult
 nsJSON::DecodeInternal(JSContext* cx,
                        nsIInputStream *aStream,
                        int32_t aContentLength,
                        bool aNeedsConverter,
-                       JS::Value* aRetval,
-                       DecodingMode mode /* = STRICT */)
+                       JS::Value* aRetval)
 {
   // Consume the stream
   nsCOMPtr<nsIChannel> jsonChannel;
   if (!mURI) {
     NS_NewURI(getter_AddRefs(mURI), NS_LITERAL_CSTRING("about:blank"), 0, 0 );
     if (!mURI)
       return NS_ERROR_OUT_OF_MEMORY;
   }
 
   nsresult rv =
     NS_NewInputStreamChannel(getter_AddRefs(jsonChannel), mURI, aStream,
                              NS_LITERAL_CSTRING("application/json"));
   if (!jsonChannel || NS_FAILED(rv))
     return NS_ERROR_FAILURE;
 
   nsRefPtr<nsJSONListener> jsonListener =
-    new nsJSONListener(cx, aRetval, aNeedsConverter, mode);
+    new nsJSONListener(cx, aRetval, aNeedsConverter);
 
   //XXX this stream pattern should be consolidated in netwerk
   rv = jsonListener->OnStartRequest(jsonChannel, nullptr);
   if (NS_FAILED(rv)) {
     jsonChannel->Cancel(rv);
     return rv;
   }
 
@@ -463,74 +462,34 @@ nsJSON::DecodeInternal(JSContext* cx,
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = jsonListener->OnStopRequest(jsonChannel, nullptr, status);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-
-NS_IMETHODIMP
-nsJSON::LegacyDecode(const nsAString& json, JSContext* cx, JS::Value* aRetval)
-{
-  const PRUnichar *data;
-  uint32_t len = NS_StringGetData(json, &data);
-  nsCOMPtr<nsIInputStream> stream;
-  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
-                                      (const char*) data,
-                                      len * sizeof(PRUnichar),
-                                      NS_ASSIGNMENT_DEPEND);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return DecodeInternal(cx, stream, len, false, aRetval, LEGACY);
-}
-
-NS_IMETHODIMP
-nsJSON::LegacyDecodeFromStream(nsIInputStream *aStream, int32_t aContentLength,
-                               JSContext* cx, JS::Value* aRetval)
-{
-  return DecodeInternal(cx, aStream, aContentLength, true, aRetval, LEGACY);
-}
-
-NS_IMETHODIMP
-nsJSON::LegacyDecodeToJSVal(const nsAString &str, JSContext *cx, JS::Value *result)
-{
-  JS::RootedValue reviver(cx, JS::NullValue()), value(cx);
-
-  JS::StableCharPtr chars(static_cast<const jschar*>(PromiseFlatString(str).get()),
-                          str.Length());
-  if (!js::ParseJSONWithReviver(cx, chars, str.Length(), reviver,
-                                &value, LEGACY)) {
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  *result = value;
-  return NS_OK;
-}
-
 nsresult
 NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult)
 {
   nsJSON* json = new nsJSON();
   if (!json)
     return NS_ERROR_OUT_OF_MEMORY;
 
   NS_ADDREF(json);
   *aResult = json;
 
   return NS_OK;
 }
 
 nsJSONListener::nsJSONListener(JSContext *cx, JS::Value *rootVal,
-                               bool needsConverter,
-                               DecodingMode mode /* = STRICT */)
+                               bool needsConverter)
   : mNeedsConverter(needsConverter), 
     mCx(cx),
-    mRootVal(rootVal),
-    mDecodingMode(mode)
+    mRootVal(rootVal)
 {
 }
 
 nsJSONListener::~nsJSONListener()
 {
 }
 
 NS_INTERFACE_MAP_BEGIN(nsJSONListener)
@@ -563,20 +522,19 @@ nsJSONListener::OnStopRequest(nsIRequest
     rv = ProcessBytes(nullptr, 0);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   JS::RootedValue reviver(mCx, JS::NullValue()), value(mCx);
 
   JS::StableCharPtr chars(reinterpret_cast<const jschar*>(mBufferedChars.Elements()),
                           mBufferedChars.Length());
-  JSBool ok = js::ParseJSONWithReviver(mCx, chars,
-                                       (uint32_t) mBufferedChars.Length(),
-                                       reviver, &value,
-                                       mDecodingMode);
+  JSBool ok = JS_ParseJSONWithReviver(mCx, chars.get(),
+                                      uint32_t(mBufferedChars.Length()),
+                                      reviver, value.address());
 
   *mRootVal = value;
   mBufferedChars.TruncateLength(0);
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsJSONListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
--- a/dom/src/json/nsJSON.h
+++ b/dom/src/json/nsJSON.h
@@ -2,17 +2,16 @@
 /* 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 nsJSON_h__
 #define nsJSON_h__
 
 #include "jsapi.h"
-#include "json.h"
 #include "nsIJSON.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIOutputStream.h"
 #include "nsIUnicodeEncoder.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsIRequestObserver.h"
 #include "nsIStreamListener.h"
@@ -56,41 +55,38 @@ protected:
   nsresult EncodeInternal(JSContext* cx,
                           const JS::Value& val,
                           nsJSONWriter* writer);
 
   nsresult DecodeInternal(JSContext* cx,
                           nsIInputStream* aStream,
                           int32_t aContentLength,
                           bool aNeedsConverter,
-                          JS::Value* aRetVal,
-                          DecodingMode mode = STRICT);
+                          JS::Value* aRetVal);
   nsCOMPtr<nsIURI> mURI;
 };
 
 nsresult
 NS_NewJSON(nsISupports* aOuter, REFNSIID aIID, void** aResult);
 
 class nsJSONListener : public nsIStreamListener
 {
 public:
-  nsJSONListener(JSContext *cx, JS::Value *rootVal, bool needsConverter,
-                 DecodingMode mode);
+  nsJSONListener(JSContext *cx, JS::Value *rootVal, bool needsConverter);
   virtual ~nsJSONListener();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
 
 protected:
   bool mNeedsConverter;
   JSContext *mCx;
   JS::Value *mRootVal;
   nsCOMPtr<nsIUnicodeDecoder> mDecoder;
   nsCString mSniffBuffer;
   nsTArray<PRUnichar> mBufferedChars;
-  DecodingMode mDecodingMode;
   nsresult ProcessBytes(const char* aBuffer, uint32_t aByteLength);
   nsresult ConsumeConverted(const char* aBuffer, uint32_t aByteLength);
   nsresult Consume(const PRUnichar *data, uint32_t len);
 };
 
 #endif
--- a/dom/webidl/AudioParam.webidl
+++ b/dom/webidl/AudioParam.webidl
@@ -29,16 +29,17 @@ interface AudioParam {
     void setTargetAtTime(float target, double startTime, double timeConstant);
 
     // Sets an array of arbitrary parameter values starting at time for the given duration. 
     // The number of values will be scaled to fit into the desired duration. 
     [Throws]
     void setValueCurveAtTime(Float32Array values, double startTime, double duration);
 
     // Cancels all scheduled parameter changes with times greater than or equal to startTime. 
+    [Throws]
     void cancelScheduledValues(double startTime);
 
 };
 
 /*
  * The origin of this IDL file is
  * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AlternateNames
  */
--- a/dom/webidl/EventTarget.webidl
+++ b/dom/webidl/EventTarget.webidl
@@ -32,8 +32,16 @@ interface EventTarget {
 // implement on* properties.
 partial interface EventTarget {
   [ChromeOnly, Throws]
   void setEventHandler(DOMString type, EventHandler handler);
 
   [ChromeOnly]
   EventHandler getEventHandler(DOMString type);
 };
+
+// Mozilla extension to make firing events on event targets from
+// chrome easier.  This returns the window which can be used to create
+// events to fire at this EventTarget, or null if there isn't one.
+partial interface EventTarget {
+  [ChromeOnly]
+  readonly attribute WindowProxy? ownerGlobal;
+};
--- a/dom/workers/EventTarget.h
+++ b/dom/workers/EventTarget.h
@@ -69,13 +69,19 @@ public:
   {
     rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
   }
 
   JSObject* GetEventHandler(JSContext*, const nsAString& aType)
   {
     return nullptr;
   }
+
+  JSObject* GetOwnerGlobal() const
+  {
+    // We have no windows
+    return nullptr;
+  }
 };
 
 END_WORKERS_NAMESPACE
 
 #endif // mozilla_dom_workers_eventtarget_h__
--- a/dom/workers/test/test_xhr_implicit_cancel.html
+++ b/dom/workers/test/test_xhr_implicit_cancel.html
@@ -19,23 +19,23 @@ Tests of DOM Worker Threads XHR(Bug 4504
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
   var worker = new Worker("xhr_implicit_cancel_worker.js");
 
   worker.onmessage = function(event) {
-    is(event.target, worker);
+    is(event.target, worker, "Expected event target for message");
     ok(true, "Worker didn't have an error");
     SimpleTest.finish();
   };
 
   worker.onerror = function(event) {
-    is(event.target, worker);
+    is(event.target, worker, "Expected event target for error");
     ok(false, "Worker had an error:" + event.data);
     SimpleTest.finish();
   }
 
   SimpleTest.waitForExplicitFinish();
 
 </script>
 </pre>
--- a/gfx/2d/BaseRect.h
+++ b/gfx/2d/BaseRect.h
@@ -413,16 +413,32 @@ struct BaseRect {
    * edge of the rectangle.
    */
   Point ClampPoint(const Point& aPoint) const
   {
     return Point(std::max(x, std::min(XMost(), aPoint.x)),
                  std::max(y, std::min(YMost(), aPoint.y)));
   }
 
+  /**
+   * Clamp aRect to this rectangle. This returns aRect after it is forced
+   * inside the bounds of this rectangle. It will attempt to retain the size
+   * but will shrink the dimensions that don't fit.
+   */
+  Sub ClampRect(const Sub& aRect) const
+  {
+    Sub rect(std::max(aRect.x, x),
+             std::max(aRect.y, y),
+             std::min(aRect.width, width),
+             std::min(aRect.height, height));
+    rect.x = std::min(rect.XMost(), XMost()) - rect.width;
+    rect.y = std::min(rect.YMost(), YMost()) - rect.height;
+    return rect;
+  }
+
 private:
   // Do not use the default operator== or operator!= !
   // Use IsEqualEdges or IsEqualInterior explicitly.
   bool operator==(const Sub& aRect) const { return false; }
   bool operator!=(const Sub& aRect) const { return false; }
 };
 
 }
--- a/gfx/2d/Point.h
+++ b/gfx/2d/Point.h
@@ -31,16 +31,27 @@ template<class units>
 struct PointTyped :
   public BasePoint< Float, PointTyped<units> >,
   public units {
   typedef BasePoint< Float, PointTyped<units> > Super;
 
   PointTyped() : Super() {}
   PointTyped(Float aX, Float aY) : Super(aX, aY) {}
   PointTyped(const IntPointTyped<units>& point) : Super(float(point.x), float(point.y)) {}
+
+  // XXX When all of the code is ported, the following functions to convert to and from
+  // unknown types should be removed.
+
+  static PointTyped<units> FromUnknownPoint(const PointTyped<UnknownUnits>& pt) {
+    return PointTyped<units>(pt.x, pt.y);
+  }
+
+  PointTyped<UnknownUnits> ToUnknownPoint() const {
+    return PointTyped<UnknownUnits>(this->x, this->y);
+  }
 };
 typedef PointTyped<UnknownUnits> Point;
 
 template<class units>
 struct IntSizeTyped :
   public BaseSize< int32_t, IntSizeTyped<units> >,
   public units {
   typedef BaseSize< int32_t, IntSizeTyped<units> > Super;
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -5,16 +5,17 @@
 
 #ifndef GFX_FRAMEMETRICS_H
 #define GFX_FRAMEMETRICS_H
 
 #include "gfxPoint.h"
 #include "gfxTypes.h"
 #include "nsRect.h"
 #include "mozilla/gfx/Rect.h"
+#include "Units.h"
 
 namespace mozilla {
 namespace layers {
 
 /**
  * The viewport and displayport metrics for the painted frame at the
  * time of a layer-tree transaction.  These metrics are especially
  * useful for shadow layers, because the metrics values are updated
@@ -185,17 +186,17 @@ public:
   // It is required that the rect:
   // { x = mScrollOffset.x, y = mScrollOffset.y,
   //   width = mCompositionBounds.x / mResolution.width,
   //   height = mCompositionBounds.y / mResolution.height }
   // Be within |mScrollableRect|.
   //
   // This is valid for any layer, but is always relative to this frame and
   // not any parents, regardless of parent transforms.
-  gfx::Point mScrollOffset;
+  mozilla::CSSPoint mScrollOffset;
 
   // A unique ID assigned to each scrollable frame (unless this is
   // ROOT_SCROLL_ID, in which case it is not unique).
   ViewID mScrollId;
 
   // The scrollable bounds of a frame. This is determined by reflow.
   // For the top-level |window|,
   // { x = window.scrollX, y = window.scrollY, // could be 0, 0
--- a/gfx/layers/LayersLogging.cpp
+++ b/gfx/layers/LayersLogging.cpp
@@ -90,18 +90,19 @@ nsACString&
 AppendToString(nsACString& s, const nsIntPoint& p,
                const char* pfx, const char* sfx)
 {
   s += pfx;
   s += nsPrintfCString("(x=%d, y=%d)", p.x, p.y);
   return s += sfx;
 }
 
+template<class T>
 nsACString&
-AppendToString(nsACString& s, const Point& p,
+AppendToString(nsACString& s, const PointTyped<T>& p,
                const char* pfx, const char* sfx)
 {
   s += pfx;
   s += nsPrintfCString("(x=%f, y=%f)", p.x, p.y);
   return s += sfx;
 }
 
 nsACString&
--- a/gfx/layers/LayersLogging.h
+++ b/gfx/layers/LayersLogging.h
@@ -39,18 +39,19 @@ AppendToString(nsACString& s, const gfxR
 nsACString&
 AppendToString(nsACString& s, const gfx3DMatrix& m,
                const char* pfx="", const char* sfx="");
 
 nsACString&
 AppendToString(nsACString& s, const nsIntPoint& p,
                const char* pfx="", const char* sfx="");
 
+template<class T>
 nsACString&
-AppendToString(nsACString& s, const mozilla::gfx::Point& p,
+AppendToString(nsACString& s, const mozilla::gfx::PointTyped<T>& p,
                const char* pfx="", const char* sfx="");
 
 nsACString&
 AppendToString(nsACString& s, const nsIntRect& r,
                const char* pfx="", const char* sfx="");
 
 nsACString&
 AppendToString(nsACString& s, const mozilla::gfx::Rect& r,
--- a/gfx/layers/client/ClientTiledThebesLayer.cpp
+++ b/gfx/layers/client/ClientTiledThebesLayer.cpp
@@ -13,17 +13,17 @@ namespace mozilla {
 namespace layers {
 
 
 ClientTiledThebesLayer::ClientTiledThebesLayer(ClientLayerManager* const aManager)
   : ThebesLayer(aManager, static_cast<ClientLayer*>(this))
   , mContentClient()
 {
   MOZ_COUNT_CTOR(ClientTiledThebesLayer);
-  mPaintData.mLastScrollOffset = gfx::Point(0, 0);
+  mPaintData.mLastScrollOffset = CSSPoint(0, 0);
   mPaintData.mFirstPaint = true;
 }
 
 ClientTiledThebesLayer::~ClientTiledThebesLayer()
 {
   MOZ_COUNT_DTOR(ClientTiledThebesLayer);
 }
 
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -268,17 +268,17 @@ BasicTiledLayerBuffer::ValidateTile(Basi
     aTile = ValidateTileInternal(aTile, aTileOrigin, *rect);
   }
 
   return aTile;
 }
 
 static nsIntRect
 RoundedTransformViewportBounds(const gfx::Rect& aViewport,
-                               const gfx::Point& aScrollOffset,
+                               const CSSPoint& aScrollOffset,
                                const gfxSize& aResolution,
                                float aScaleX,
                                float aScaleY,
                                const gfx3DMatrix& aTransform)
 {
   gfxRect transformedViewport(aViewport.x - (aScrollOffset.x * aResolution.width),
                               aViewport.y - (aScrollOffset.y * aResolution.height),
                               aViewport.width, aViewport.height);
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -68,18 +68,18 @@ struct BasicTiledLayerTile {
   }
 };
 
 /**
  * This struct stores all the data necessary to perform a paint so that it
  * doesn't need to be recalculated on every repeated transaction.
  */
 struct BasicTiledLayerPaintData {
-  gfx::Point mScrollOffset;
-  gfx::Point mLastScrollOffset;
+  CSSPoint mScrollOffset;
+  CSSPoint mLastScrollOffset;
   gfx3DMatrix mTransformScreenToLayer;
   nsIntRect mLayerCriticalDisplayPort;
   gfxSize mResolution;
   nsIntRect mCompositionBounds;
   uint16_t mLowPrecisionPaintCount;
   bool mFirstPaint : 1;
   bool mPaintFinished : 1;
 };
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -823,17 +823,17 @@ void AsyncPanZoomController::CancelAnima
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
 }
 
 void AsyncPanZoomController::ScrollBy(const gfx::Point& aOffset) {
   gfx::Point newOffset(mFrameMetrics.mScrollOffset.x + aOffset.x,
                        mFrameMetrics.mScrollOffset.y + aOffset.y);
   FrameMetrics metrics(mFrameMetrics);
-  metrics.mScrollOffset = newOffset;
+  metrics.mScrollOffset = CSSPoint::FromUnknownPoint(newOffset);
   mFrameMetrics = metrics;
 }
 
 void AsyncPanZoomController::SetPageRect(const gfx::Rect& aCSSPageRect) {
   FrameMetrics metrics = mFrameMetrics;
   gfx::Rect pageSize = aCSSPageRect;
   gfxFloat resolution = CalculateResolution(mFrameMetrics).width;
 
@@ -935,17 +935,17 @@ const gfx::Rect AsyncPanZoomController::
       scrollableRect.width = compositionBounds.width;
   }
   if (scrollableRect.height < compositionBounds.height) {
       scrollableRect.y = std::max(0.f,
                                   scrollableRect.y - (compositionBounds.height - scrollableRect.height));
       scrollableRect.height = compositionBounds.height;
   }
 
-  gfx::Point scrollOffset = aFrameMetrics.mScrollOffset;
+  CSSPoint scrollOffset = aFrameMetrics.mScrollOffset;
 
   gfx::Rect displayPort(0, 0,
                         compositionBounds.width * gXStationarySizeMultiplier,
                         compositionBounds.height * gYStationarySizeMultiplier);
 
   // If there's motion along an axis of movement, and it's above a threshold,
   // then we want to paint a larger area in the direction of that motion so that
   // it's less likely to checkerboard.
@@ -982,17 +982,17 @@ const gfx::Rect AsyncPanZoomController::
   if (scrollOffset.y + compositionBounds.height > scrollableRect.height) {
     scrollOffset.y -= compositionBounds.height + scrollOffset.y - scrollableRect.height;
   } else if (scrollOffset.y < scrollableRect.y) {
     scrollOffset.y = scrollableRect.y;
   }
 
   gfx::Rect shiftedDisplayPort = displayPort;
   shiftedDisplayPort.MoveBy(scrollOffset.x, scrollOffset.y);
-  displayPort = shiftedDisplayPort.Intersect(scrollableRect);
+  displayPort = scrollableRect.ClampRect(shiftedDisplayPort);
   displayPort.MoveBy(-scrollOffset.x, -scrollOffset.y);
 
   return displayPort;
 }
 
 /*static*/ gfxSize
 AsyncPanZoomController::CalculateIntrinsicScale(const FrameMetrics& aMetrics)
 {
@@ -1050,18 +1050,18 @@ void AsyncPanZoomController::RequestCont
   }
 
   mFrameMetrics.mDisplayPort =
     CalculatePendingDisplayPort(mFrameMetrics,
                                 GetVelocityVector(),
                                 GetAccelerationVector(),
                                 estimatedPaintDuration);
 
-  gfx::Point oldScrollOffset = mLastPaintRequestMetrics.mScrollOffset,
-             newScrollOffset = mFrameMetrics.mScrollOffset;
+  CSSPoint oldScrollOffset = mLastPaintRequestMetrics.mScrollOffset,
+           newScrollOffset = mFrameMetrics.mScrollOffset;
 
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   gfx::Rect oldDisplayPort = mLastPaintRequestMetrics.mDisplayPort;
   gfx::Rect newDisplayPort = mFrameMetrics.mDisplayPort;
 
   oldDisplayPort.MoveBy(oldScrollOffset.x, oldScrollOffset.y);
   newDisplayPort.MoveBy(newScrollOffset.x, newScrollOffset.y);
@@ -1152,22 +1152,22 @@ bool AsyncPanZoomController::SampleConte
       double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
 
       gfxFloat startZoom = mStartZoomToMetrics.mZoom.width;
       gfxFloat endZoom = mEndZoomToMetrics.mZoom.width;
       gfxFloat sampledZoom = (endZoom * sampledPosition +
                               startZoom * (1 - sampledPosition));
       mFrameMetrics.mZoom = gfxSize(sampledZoom, sampledZoom);
 
-      mFrameMetrics.mScrollOffset = gfx::Point(
+      mFrameMetrics.mScrollOffset = CSSPoint::FromUnknownPoint(gfx::Point(
         mEndZoomToMetrics.mScrollOffset.x * sampledPosition +
           mStartZoomToMetrics.mScrollOffset.x * (1 - sampledPosition),
         mEndZoomToMetrics.mScrollOffset.y * sampledPosition +
           mStartZoomToMetrics.mScrollOffset.y * (1 - sampledPosition)
-      );
+      ));
 
       requestAnimationFrame = true;
 
       if (aSampleTime - mAnimationStartTime >= ZOOM_TO_DURATION) {
         // Bring the resolution in sync with the zoom.
         SetZoomAndResolution(mFrameMetrics.mZoom.width);
         mState = NOTHING;
         SendAsyncScrollEvent();
@@ -1186,17 +1186,17 @@ bool AsyncPanZoomController::SampleConte
     // during runtime, but we must wait for Gecko to repaint.
     localScale = CalculateResolution(mFrameMetrics);
 
     if (frame.IsScrollable()) {
       metricsScrollOffset = frame.GetScrollOffsetInLayerPixels();
     }
 
     scrollOffset = gfxPoint(mFrameMetrics.mScrollOffset.x, mFrameMetrics.mScrollOffset.y);
-    mCurrentAsyncScrollOffset = mFrameMetrics.mScrollOffset;
+    mCurrentAsyncScrollOffset = mFrameMetrics.mScrollOffset.ToUnknownPoint();
   }
 
   // Cancel the mAsyncScrollTimeoutTask because we will fire a
   // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again.
   if (mAsyncScrollTimeoutTask) {
     mAsyncScrollTimeoutTask->Cancel();
     mAsyncScrollTimeoutTask = nullptr;
   }
@@ -1340,17 +1340,17 @@ void AsyncPanZoomController::ZoomToRect(
 
   SetState(ANIMATING_ZOOM);
 
   {
     MonitorAutoLock mon(mMonitor);
 
     nsIntRect compositionBounds = mFrameMetrics.mCompositionBounds;
     gfx::Rect cssPageRect = mFrameMetrics.mScrollableRect;
-    gfx::Point scrollOffset = mFrameMetrics.mScrollOffset;
+    CSSPoint scrollOffset = mFrameMetrics.mScrollOffset;
     gfxSize resolution = CalculateResolution(mFrameMetrics);
     gfxSize currentZoom = mFrameMetrics.mZoom;
     float targetZoom;
     gfxFloat targetResolution;
 
     // The minimum zoom to prevent over-zoom-out.
     // If the zoom factor is lower than this (i.e. we are zoomed more into the page),
     // then the CSS content rect, in layers pixels, will be smaller than the
@@ -1414,17 +1414,18 @@ void AsyncPanZoomController::ZoomToRect(
       zoomToRect.y = zoomToRect.y > 0 ? zoomToRect.y : 0;
     }
     if (zoomToRect.x + rectAfterZoom.width > cssPageRect.width) {
       zoomToRect.x = cssPageRect.width - rectAfterZoom.width;
       zoomToRect.x = zoomToRect.x > 0 ? zoomToRect.x : 0;
     }
 
     mStartZoomToMetrics = mFrameMetrics;
-    mEndZoomToMetrics.mScrollOffset = gfx::Point(zoomToRect.x, zoomToRect.y);
+    mEndZoomToMetrics.mScrollOffset = CSSPoint::FromUnknownPoint(
+      gfx::Point(zoomToRect.x, zoomToRect.y));
 
     mAnimationStartTime = TimeStamp::Now();
 
     ScheduleComposite();
   }
 }
 
 void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -287,17 +287,17 @@ float Axis::GetCompositionEnd() {
   return GetOrigin() + GetCompositionLength();
 }
 
 float Axis::GetPageEnd() {
   return GetPageStart() + GetPageLength();
 }
 
 float Axis::GetOrigin() {
-  gfx::Point origin = mAsyncPanZoomController->GetFrameMetrics().mScrollOffset;
+  CSSPoint origin = mAsyncPanZoomController->GetFrameMetrics().mScrollOffset;
   return GetPointOffset(origin);
 }
 
 float Axis::GetCompositionLength() {
   const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics();
   gfx::Rect cssCompositedRect =
     AsyncPanZoomController::CalculateCompositedRectInCssPixels(metrics);
   return GetRectLength(cssCompositedRect);
@@ -329,17 +329,17 @@ bool Axis::ScaleWillOverscrollBothSides(
 }
 
 AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
   : Axis(aAsyncPanZoomController)
 {
 
 }
 
-float AxisX::GetPointOffset(const gfx::Point& aPoint)
+float AxisX::GetPointOffset(const CSSPoint& aPoint)
 {
   return aPoint.x;
 }
 
 float AxisX::GetRectLength(const gfx::Rect& aRect)
 {
   return aRect.width;
 }
@@ -350,17 +350,17 @@ float AxisX::GetRectOffset(const gfx::Re
 }
 
 AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController)
   : Axis(aAsyncPanZoomController)
 {
 
 }
 
-float AxisY::GetPointOffset(const gfx::Point& aPoint)
+float AxisY::GetPointOffset(const CSSPoint& aPoint)
 {
   return aPoint.y;
 }
 
 float AxisY::GetRectLength(const gfx::Rect& aRect)
 {
   return aRect.height;
 }
--- a/gfx/layers/ipc/Axis.h
+++ b/gfx/layers/ipc/Axis.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_layers_Axis_h
 #define mozilla_layers_Axis_h
 
 #include "nsGUIEvent.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/gfx/2D.h"
 #include "nsTArray.h"
+#include "Units.h"
 
 namespace mozilla {
 namespace layers {
 
 class AsyncPanZoomController;
 
 /**
  * Helper class to maintain each axis of movement (X,Y) for panning and zooming.
@@ -165,17 +166,17 @@ public:
 
   float GetOrigin();
   float GetCompositionLength();
   float GetPageStart();
   float GetPageLength();
   float GetCompositionEnd();
   float GetPageEnd();
 
-  virtual float GetPointOffset(const gfx::Point& aPoint) = 0;
+  virtual float GetPointOffset(const CSSPoint& aPoint) = 0;
   virtual float GetRectLength(const gfx::Rect& aRect) = 0;
   virtual float GetRectOffset(const gfx::Rect& aRect) = 0;
 
 protected:
   int32_t mPos;
   int32_t mStartPos;
   float mVelocity;
   // Acceleration is represented by an int, which is the power we raise a
@@ -186,25 +187,25 @@ protected:
   int32_t mAcceleration;
   AsyncPanZoomController* mAsyncPanZoomController;
   nsTArray<float> mVelocityQueue;
 };
 
 class AxisX : public Axis {
 public:
   AxisX(AsyncPanZoomController* mAsyncPanZoomController);
-  virtual float GetPointOffset(const gfx::Point& aPoint);
+  virtual float GetPointOffset(const CSSPoint& aPoint);
   virtual float GetRectLength(const gfx::Rect& aRect);
   virtual float GetRectOffset(const gfx::Rect& aRect);
 };
 
 class AxisY : public Axis {
 public:
   AxisY(AsyncPanZoomController* mAsyncPanZoomController);
-  virtual float GetPointOffset(const gfx::Point& aPoint);
+  virtual float GetPointOffset(const CSSPoint& aPoint);
   virtual float GetRectLength(const gfx::Rect& aRect);
   virtual float GetRectOffset(const gfx::Rect& aRect);
 };
 
 }
 }
 
 #endif
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -21,16 +21,17 @@
 #include "TiledLayerBuffer.h"
 #include "gfxPlatform.h"
 #include "CompositableHost.h"
 #include "mozilla/layers/ThebesLayerComposite.h"
 #include "mozilla/layers/ImageLayerComposite.h"
 #include "mozilla/layers/ColorLayerComposite.h"
 #include "mozilla/layers/ContainerLayerComposite.h"
 #include "mozilla/layers/CanvasLayerComposite.h"
+#include "mozilla/layers/PLayerTransaction.h"
 
 typedef std::vector<mozilla::layers::EditReply> EditReplyVector;
 
 using mozilla::layout::RenderFrameParent;
 
 namespace mozilla {
 namespace layers {
 
@@ -449,17 +450,39 @@ LayerTransactionParent::RecvGetOpacity(P
 bool
 LayerTransactionParent::RecvGetTransform(PLayerParent* aParent,
                                          gfx3DMatrix* aTransform)
 {
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return false;
   }
 
-  *aTransform = cast(aParent)->AsLayer()->GetLocalTransform();
+  // The following code recovers the untranslated transform
+  // from the shadow transform by undoing the translations in
+  // AsyncCompositionManager::SampleValue.
+  Layer* layer = cast(aParent)->AsLayer();
+  *aTransform = layer->GetLocalTransform();
+  float scale = 1;
+  gfxPoint3D scaledOrigin;
+  gfxPoint3D mozOrigin;
+  for (uint32_t i=0; i < layer->GetAnimations().Length(); i++) {
+    if (layer->GetAnimations()[i].data().type() == AnimationData::TTransformData) {
+      const TransformData& data = layer->GetAnimations()[i].data().get_TransformData();
+      scale = data.appUnitsPerDevPixel();
+      scaledOrigin =
+        gfxPoint3D(NS_round(NSAppUnitsToFloatPixels(data.origin().x, scale)),
+                   NS_round(NSAppUnitsToFloatPixels(data.origin().y, scale)),
+                   0.0f);
+      mozOrigin = data.mozOrigin();
+      break;
+    }
+  }
+
+  aTransform->Translate(-scaledOrigin);
+  *aTransform = nsLayoutUtils::ChangeMatrixBasis(-scaledOrigin - mozOrigin, *aTransform);
   return true;
 }
 
 void
 LayerTransactionParent::Attach(ShadowLayerParent* aLayerParent, CompositableParent* aCompositable)
 {
   LayerComposite* layer = aLayerParent->AsLayer()->AsLayerComposite();
   MOZ_ASSERT(layer);
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -783,20 +783,20 @@ struct ParamTraits<nsIntSize>
 
   static bool Read(const Message* msg, void** iter, paramType* result)
   {
     return (ReadParam(msg, iter, &result->width) &&
             ReadParam(msg, iter, &result->height));
   }
 };
 
-template<>
-struct ParamTraits<mozilla::gfx::Point>
+template<class T>
+struct ParamTraits< mozilla::gfx::PointTyped<T> >
 {
-  typedef mozilla::gfx::Point paramType;
+  typedef mozilla::gfx::PointTyped<T> paramType;
 
   static void Write(Message* msg, const paramType& param)
   {
     WriteParam(msg, param.x);
     WriteParam(msg, param.y);
   }
 
   static bool Read(const Message* msg, void** iter, paramType* result)
--- a/ipc/testshell/TestShellParent.cpp
+++ b/ipc/testshell/TestShellParent.cpp
@@ -95,21 +95,21 @@ TestShellCommandParent::RunCallback(cons
   JS::Rooted<JSObject*> global(mCx, JS_GetGlobalForObject(mCx, mCallback.ToJSObject()));
   NS_ENSURE_TRUE(global, JS_FALSE);
 
   JSAutoCompartment ac(mCx, global);
 
   JSString* str = JS_NewUCStringCopyN(mCx, aResponse.get(), aResponse.Length());
   NS_ENSURE_TRUE(str, JS_FALSE);
 
-  JS::Value argv[] = { STRING_TO_JSVAL(str) };
-  unsigned argc = ArrayLength(argv);
+  JS::Rooted<JS::Value> strVal(mCx, JS::StringValue(str));
 
-  JS::Value rval;
-  JSBool ok = JS_CallFunctionValue(mCx, global, mCallback, argc, argv, &rval);
+  JS::Rooted<JS::Value> rval(mCx);
+  JSBool ok = JS_CallFunctionValue(mCx, global, mCallback, 1, strVal.address(),
+				   rval.address());
   NS_ENSURE_TRUE(ok, JS_FALSE);
 
   return JS_TRUE;
 }
 
 void
 TestShellCommandParent::ReleaseCallback()
 {
--- a/js/public/CallArgs.h
+++ b/js/public/CallArgs.h
@@ -26,16 +26,17 @@
  * methods' implementations, potentially under time pressure.
  */
 
 #ifndef js_CallArgs_h___
 #define js_CallArgs_h___
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/TypeTraits.h"
 
 #include "jstypes.h"
 
 #include "js/RootingAPI.h"
 #include "js/Value.h"
 
 struct JSContext;
 class JSObject;
@@ -87,49 +88,69 @@ namespace JS {
  * part of the CallReceiver interface.  We will likely add them at some point.
  * Until then, you should probably continue using |vp| directly for these two
  * cases.
  *
  * CallReceiver is exposed publicly and used internally.  Not all parts of its
  * public interface are meant to be used by embedders!  See inline comments to
  * for details.
  */
-class MOZ_STACK_CLASS CallReceiver
+
+namespace detail {
+
+enum UsedRval { IncludeUsedRval, NoUsedRval };
+
+template<UsedRval WantUsedRval>
+class MOZ_STACK_CLASS UsedRvalBase;
+
+template<>
+class MOZ_STACK_CLASS UsedRvalBase<IncludeUsedRval>
 {
   protected:
-#ifdef DEBUG
     mutable bool usedRval_;
     void setUsedRval() const { usedRval_ = true; }
     void clearUsedRval() const { usedRval_ = false; }
-#else
+};
+
+template<>
+class MOZ_STACK_CLASS UsedRvalBase<NoUsedRval>
+{
+  protected:
     void setUsedRval() const {}
     void clearUsedRval() const {}
+};
+
+template<UsedRval WantUsedRval>
+class MOZ_STACK_CLASS CallReceiverBase : public UsedRvalBase<
+#ifdef DEBUG
+        WantUsedRval
+#else
+        NoUsedRval
 #endif
-
+    >
+{
+  protected:
     Value *argv_;
 
-    friend CallReceiver CallReceiverFromVp(Value *vp);
-    friend CallReceiver CallReceiverFromArgv(Value *argv);
-
   public:
     /*
      * Returns the function being called, as an object.  Must not be called
      * after rval() has been used!
      */
     JSObject &callee() const {
-        MOZ_ASSERT(!usedRval_);
+        MOZ_ASSERT(!this->usedRval_);
         return argv_[-2].toObject();
     }
 
     /*
      * Returns the function being called, as a value.  Must not be called after
      * rval() has been used!
      */
     HandleValue calleev() const {
-        MOZ_ASSERT(!usedRval_);
+        MOZ_ASSERT(!this->usedRval_);
         return HandleValue::fromMarkedLocation(&argv_[-2]);
     }
 
     /*
      * Returns the |this| value passed to the function.  This method must not
      * be called when the function is being called as a constructor via |new|.
      * The value may or may not be an object: it is the individual function's
      * responsibility to box the value if needed.
@@ -155,50 +176,59 @@ class MOZ_STACK_CLASS CallReceiver
      * build of SpiderMonkey, these methods will assert to aid debugging.)
      *
      * If the method you're implementing succeeds by returning true, you *must*
      * set this.  (SpiderMonkey doesn't currently assert this, but it will do
      * so eventually.)  You don't need to use or change this if your method
      * fails.
      */
     MutableHandleValue rval() const {
-        setUsedRval();
+        this->setUsedRval();
         return MutableHandleValue::fromMarkedLocation(&argv_[-2]);
     }
 
   public:
     // These methods are only intended for internal use.  Embedders shouldn't
     // use them!
 
     Value *base() const { return argv_ - 2; }
 
     Value *spAfterCall() const {
-        setUsedRval();
+        this->setUsedRval();
         return argv_ - 1;
     }
 
   public:
     // These methods are publicly exposed, but they are *not* to be used when
     // implementing a JSNative method and encapsulating access to |vp| within
     // it.  You probably don't want to use these!
 
     void setCallee(Value aCalleev) const {
-        clearUsedRval();
+        this->clearUsedRval();
         argv_[-2] = aCalleev;
     }
 
     void setThis(Value aThisv) const {
         argv_[-1] = aThisv;
     }
 
     MutableHandleValue mutableThisv() const {
         return MutableHandleValue::fromMarkedLocation(&argv_[-1]);
     }
 };
 
+} // namespace detail
+
+class MOZ_STACK_CLASS CallReceiver : public detail::CallReceiverBase<detail::IncludeUsedRval>
+{
+  private:
+    friend CallReceiver CallReceiverFromVp(Value *vp);
+    friend CallReceiver CallReceiverFromArgv(Value *argv);
+};
+
 MOZ_ALWAYS_INLINE CallReceiver
 CallReceiverFromArgv(Value *argv)
 {
     CallReceiver receiver;
     receiver.clearUsedRval();
     receiver.argv_ = argv;
     return receiver;
 }
@@ -228,71 +258,84 @@ CallReceiverFromVp(Value *vp)
  *       args.rval().set(JS::NumberValue(args.length() * args[0].toNumber()));
  *       return true;
  *   }
  *
  * CallArgs is exposed publicly and used internally.  Not all parts of its
  * public interface are meant to be used by embedders!  See inline comments to
  * for details.
  */
-class MOZ_STACK_CLASS CallArgs : public CallReceiver
+namespace detail {
+
+template<UsedRval WantUsedRval>
+class MOZ_STACK_CLASS CallArgsBase :
+        public mozilla::Conditional<WantUsedRval == detail::IncludeUsedRval,
+                                    CallReceiver,
+                                    CallReceiverBase<NoUsedRval> >::Type
 {
   protected:
     unsigned argc_;
 
+  public:
+    /* Returns the number of arguments. */
+    unsigned length() const { return argc_; }
+
+    /* Returns the i-th zero-indexed argument. */
+    Value &operator[](unsigned i) const {
+        MOZ_ASSERT(i < argc_);
+        return this->argv_[i];
+    }
+
+    /* Returns a mutable handle for the i-th zero-indexed argument. */
+    MutableHandleValue handleAt(unsigned i) const {
+        MOZ_ASSERT(i < argc_);
+        return MutableHandleValue::fromMarkedLocation(&this->argv_[i]);
+    }
+
+    /*
+     * Returns the i-th zero-indexed argument, or |undefined| if there's no
+     * such argument.
+     */
+    Value get(unsigned i) const {
+        return i < length() ? this->argv_[i] : UndefinedValue();
+    }
+
+    /*
+     * Returns true if the i-th zero-indexed argument is present and is not
+     * |undefined|.
+     */
+    bool hasDefined(unsigned i) const {
+        return i < argc_ && !this->argv_[i].isUndefined();
+    }
+
+  public:
+    // These methods are publicly exposed, but we're less sure of the interface
+    // here than we'd like (because they're hackish and drop assertions).  Try
+    // to avoid using these if you can.
+
+    Value *array() const { return this->argv_; }
+    Value *end() const { return this->argv_ + argc_; }
+};
+
+} // namespace detail
+
+class MOZ_STACK_CLASS CallArgs : public detail::CallArgsBase<detail::IncludeUsedRval>
+{
+  private:
     friend CallArgs CallArgsFromVp(unsigned argc, Value *vp);
     friend CallArgs CallArgsFromSp(unsigned argc, Value *sp);
 
     static CallArgs create(unsigned argc, Value *argv) {
         CallArgs args;
         args.clearUsedRval();
         args.argv_ = argv;
         args.argc_ = argc;
         return args;
     }
 
-  public:
-    /* Returns the number of arguments. */
-    unsigned length() const { return argc_; }
-
-    /* Returns the i-th zero-indexed argument. */
-    Value &operator[](unsigned i) const {
-        MOZ_ASSERT(i < argc_);
-        return argv_[i];
-    }
-
-    /* Returns a mutable handle for the i-th zero-indexed argument. */
-    MutableHandleValue handleAt(unsigned i) const {
-        MOZ_ASSERT(i < argc_);
-        return MutableHandleValue::fromMarkedLocation(&argv_[i]);
-    }
-
-    /*
-     * Returns the i-th zero-indexed argument, or |undefined| if there's no
-     * such argument.
-     */
-    Value get(unsigned i) const {
-        return i < length() ? argv_[i] : UndefinedValue();
-    }
-
-    /*
-     * Returns true if the i-th zero-indexed argument is present and is not
-     * |undefined|.
-     */
-    bool hasDefined(unsigned i) const {
-        return i < argc_ && !argv_[i].isUndefined();
-    }
-
-  public:
-    // These methods are publicly exposed, but we're less sure of the interface
-    // here than we'd like (because they're hackish and drop assertions).  Try
-    // to avoid using these if you can.
-
-    Value *array() const { return argv_; }
-    Value *end() const { return argv_ + argc_; }
 };
 
 MOZ_ALWAYS_INLINE CallArgs
 CallArgsFromVp(unsigned argc, Value *vp)
 {
     return CallArgs::create(argc, vp + 2);
 }
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -172,17 +172,17 @@ TryEvalJSON(JSContext *cx, JSScript *cal
         // appears in the provided string.  See bug 657367.
         for (const jschar *cp = &chars[1], *end = &chars[length - 2]; ; cp++) {
             if (*cp == 0x2028 || *cp == 0x2029)
                 break;
 
             if (cp == end) {
                 bool isArray = (chars[0] == '[');
                 JSONParser parser(cx, isArray ? chars : chars + 1U, isArray ? length : length - 2,
-                                  JSONParser::StrictJSON, JSONParser::NoError);
+                                  JSONParser::NoError);
                 RootedValue tmp(cx);
                 if (!parser.parse(&tmp))
                     return EvalJSON_Failure;
                 if (tmp.isUndefined())
                     return EvalJSON_NotJSON;
                 rval.set(tmp);
                 return EvalJSON_Success;
             }
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -13,95 +13,80 @@
 #include "vm/RegExpObject-inl.h"
 #include "vm/RegExpStatics-inl.h"
 
 using namespace js;
 using namespace js::types;
 
 using mozilla::ArrayLength;
 
-class RegExpMatchBuilder
+static inline bool
+DefinePropertyHelper(JSContext *cx, HandleObject obj, Handle<PropertyName*> name, HandleValue v)
 {
-    JSContext   * const cx;
-    RootedObject array;
-
-    bool setProperty(Handle<PropertyName*> name, HandleValue v) {
-        return !!baseops::DefineProperty(cx, array, name, v,
-                                         JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE);
-    }
-
-  public:
-    RegExpMatchBuilder(JSContext *cx, HandleObject array) : cx(cx), array(cx, array) {}
-
-    bool append(uint32_t index, HandleValue v) {
-        JS_ASSERT(!array->getOps()->getElement);
-        return !!baseops::DefineElement(cx, array, index, v, JS_PropertyStub, JS_StrictPropertyStub,
-                                        JSPROP_ENUMERATE);
-    }
-
-    bool setIndex(int index) {
-        RootedValue value(cx, Int32Value(index));
-        return setProperty(cx->names().index, value);
-    }
-
-    bool setInput(HandleString str) {
-        JS_ASSERT(str);
-        RootedValue value(cx, StringValue(str));
-        return setProperty(cx->names().input, value);
-    }
-};
+    return !!baseops::DefineProperty(cx, obj, name, v,
+                                     JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE);
+}
 
 bool
 js::CreateRegExpMatchResult(JSContext *cx, HandleString input_, const jschar *chars, size_t length,
                             MatchPairs &matches, MutableHandleValue rval)
 {
     RootedString input(cx, input_);
+    RootedValue undefinedValue(cx, UndefinedValue());
 
     /*
      * Create the (slow) result array for a match.
      *
      * Array contents:
      *  0:              matched string
      *  1..pairCount-1: paren matches
      *  input:          input string
      *  index:          start index for the match
      */
-    RootedObject array(cx, NewDenseEmptyArray(cx));
-    if (!array)
-        return false;
-
     if (!input) {
         input = js_NewStringCopyN<CanGC>(cx, chars, length);
         if (!input)
             return false;
     }
 
-    RegExpMatchBuilder builder(cx, array);
-    RootedValue undefinedValue(cx, UndefinedValue());
-
     size_t numPairs = matches.length();
     JS_ASSERT(numPairs > 0);
 
+    AutoValueVector elements(cx);
+    if (!elements.reserve(numPairs))
+        return false;
+
+    /* Accumulate a Value for each pair, in a rooted vector. */
     for (size_t i = 0; i < numPairs; ++i) {
         const MatchPair &pair = matches[i];
 
-        RootedString captured(cx);
         if (pair.isUndefined()) {
             JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
-            if (!builder.append(i, undefinedValue))
-                return false;
+            elements.infallibleAppend(undefinedValue);
         } else {
-            captured = js_NewDependentString(cx, input, pair.start, pair.length());
-            RootedValue value(cx, StringValue(captured));
-            if (!captured || !builder.append(i, value))
+            JSLinearString *str = js_NewDependentString(cx, input, pair.start, pair.length());
+            if (!str)
                 return false;
+            elements.infallibleAppend(StringValue(str));
         }
     }
 
-    if (!builder.setIndex(matches[0].start) || !builder.setInput(input))
+    /* Copy the rooted vector into the array object. */
+    RootedObject array(cx, NewDenseCopiedArray(cx, elements.length(), elements.begin()));
+    if (!array)
+        return false;
+
+    /* Set the |index| property. */
+    RootedValue index(cx, Int32Value(matches[0].start));
+    if (!DefinePropertyHelper(cx, array, cx->names().index, index))
+        return false;
+
+    /* Set the |input| property. */
+    RootedValue inputVal(cx, StringValue(input));
+    if (!DefinePropertyHelper(cx, array, cx->names().input, inputVal))
         return false;
 
     rval.setObject(*array);
     return true;
 }
 
 bool
 js::CreateRegExpMatchResult(JSContext *cx, HandleString string, MatchPairs &matches,
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -887,16 +887,84 @@ js::testingFunc_inParallelSection(JSCont
 {
     // If we were actually *in* a parallel section, then this function
     // would be inlined to TRUE in ion-generated code.
     JS_ASSERT(!InParallelSection());
     JS_SET_RVAL(cx, vp, JSVAL_FALSE);
     return true;
 }
 
+static JSObject *objectMetadataFunction = NULL;
+
+static JSObject *
+ShellObjectMetadataCallback(JSContext *cx)
+{
+    Value thisv = UndefinedValue();
+
+    Value rval;
+    if (!Invoke(cx, thisv, ObjectValue(*objectMetadataFunction), 0, NULL, &rval)) {
+        cx->clearPendingException();
+        return NULL;
+    }
+
+    return rval.isObject() ? &rval.toObject() : NULL;
+}
+
+static JSBool
+SetObjectMetadataCallback(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    args.rval().setUndefined();
+
+    if (argc == 0 || !args[0].isObject() || !args[0].toObject().isFunction()) {
+        if (objectMetadataFunction)
+            JS_RemoveObjectRoot(cx, &objectMetadataFunction);
+        objectMetadataFunction = NULL;
+        js::SetObjectMetadataCallback(cx, NULL);
+        return true;
+    }
+
+    if (!objectMetadataFunction && !JS_AddObjectRoot(cx, &objectMetadataFunction))
+        return false;
+
+    objectMetadataFunction = &args[0].toObject();
+    js::SetObjectMetadataCallback(cx, ShellObjectMetadataCallback);
+    return true;
+}
+
+static JSBool
+SetObjectMetadata(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (argc != 2 || !args[0].isObject() || !args[1].isObject()) {
+        JS_ReportError(cx, "Both arguments must be objects");
+        return false;
+    }
+
+    args.rval().setUndefined();
+
+    RootedObject obj(cx, &args[0].toObject());
+    RootedObject metadata(cx, &args[1].toObject());
+    return SetObjectMetadata(cx, obj, metadata);
+}
+
+static JSBool
+GetObjectMetadata(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (argc != 1 || !args[0].isObject()) {
+        JS_ReportError(cx, "Argument must be an object");
+        return false;
+    }
+
+    args.rval().setObjectOrNull(GetObjectMetadata(&args[0].toObject()));
+    return true;
+}
+
 #ifndef JS_ION
 JSBool
 js::IsAsmJSCompilationAvailable(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().set(BooleanValue(false));
     return true;
 }
@@ -1074,16 +1142,28 @@ static JSFunctionSpecWithHelp TestingFun
 "isAsmJSFunction(fn)",
 "  Returns whether the given value is a nested function in an asm.js module that has been\n"
 "  both compile- and link-time validated."),
 
     JS_FN_HELP("inParallelSection", testingFunc_inParallelSection, 0, 0,
 "inParallelSection()",
 "  True if this code is executing within a parallel section."),
 
+    JS_FN_HELP("setObjectMetadataCallback", SetObjectMetadataCallback, 1, 0,
+"setObjectMetadataCallback(fn)",
+"  Specify function to supply metadata for all newly created objects."),
+
+    JS_FN_HELP("setObjectMetadata", SetObjectMetadata, 2, 0,
+"setObjectMetadata(obj, metadataObj)",
+"  Change the metadata for an object."),
+
+    JS_FN_HELP("getObjectMetadata", GetObjectMetadata, 1, 0,
+"getObjectMetadata(obj)",
+"  Get the metadata for an object."),
+
     JS_FS_HELP_END
 };
 
 bool
 js::DefineTestingFunctions(JSContext *cx, HandleObject obj)
 {
     return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
 }
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -22,16 +22,18 @@ class ObjectElements;
 
 namespace gc {
 class MinorCollectionTracer;
 } /* namespace gc */
 
 namespace ion {
 class CodeGenerator;
 class MacroAssembler;
+class ICStubCompiler;
+class BaselineCompiler;
 }
 
 class Nursery
 {
   public:
     const static size_t Alignment = gc::ChunkSize;
     const static size_t NurserySize = gc::ChunkSize;
     const static size_t NurseryMask = NurserySize - 1;
@@ -184,14 +186,16 @@ class Nursery
 
     static void MinorGCCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind);
     static void MinorFallbackMarkingCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind);
     static void MinorFallbackFixupCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind);
 
     friend class gc::MinorCollectionTracer;
     friend class ion::CodeGenerator;
     friend class ion::MacroAssembler;
+    friend class ion::ICStubCompiler;
+    friend class ion::BaselineCompiler;
 };
 
 } /* namespace js */
 
 #endif /* JSGC_GENERATIONAL */
 #endif /* jsgc_nursery_h___ */
--- a/js/src/ion/BaselineCompiler.cpp
+++ b/js/src/ion/BaselineCompiler.cpp
@@ -18,16 +18,19 @@
 #include "jsopcodeinlines.h"
 
 using namespace js;
 using namespace js::ion;
 
 BaselineCompiler::BaselineCompiler(JSContext *cx, HandleScript script)
   : BaselineCompilerSpecific(cx, script),
     return_(new HeapLabel())
+#ifdef JSGC_GENERATIONAL
+    , postBarrierSlot_(new HeapLabel())
+#endif
 {
 }
 
 bool
 BaselineCompiler::init()
 {
     if (!analysis_.init())
         return false;
@@ -85,16 +88,21 @@ BaselineCompiler::compile()
 
     MethodStatus status = emitBody();
     if (status != Method_Compiled)
         return status;
 
     if (!emitEpilogue())
         return Method_Error;
 
+#ifdef JSGC_GENERATIONAL
+    if (!emitOutOfLinePostBarrierSlot())
+        return Method_Error;
+#endif
+
     if (masm.oom())
         return Method_Error;
 
     Linker linker(masm);
     IonCode *code = linker.newCode(cx, JSC::BASELINE_CODE);
     if (!code)
         return Method_Error;
 
@@ -259,16 +267,50 @@ BaselineCompiler::emitEpilogue()
 
     masm.mov(BaselineFrameReg, BaselineStackReg);
     masm.pop(BaselineFrameReg);
 
     masm.ret();
     return true;
 }
 
+#ifdef JSGC_GENERATIONAL
+// On input:
+//  R2.scratchReg() contains object being written to.
+//  R1.scratchReg() contains slot index being written to.
+//  Otherwise, baseline stack will be synced, so all other registers are usable as scratch.
+// This calls:
+//    void PostWriteBarrier(JSRuntime *rt, JSObject *obj);
+bool
+BaselineCompiler::emitOutOfLinePostBarrierSlot()
+{
+    masm.bind(postBarrierSlot_);
+
+    Register objReg = R2.scratchReg();
+    GeneralRegisterSet regs(GeneralRegisterSet::All());
+    regs.take(objReg);
+    regs.take(BaselineFrameReg);
+    Register scratch = regs.takeAny();
+#if defined(JS_CPU_ARM)
+    // On ARM, save the link register before calling.  It contains the return
+    // address.  The |masm.ret()| later will pop this into |pc| to return.
+    masm.push(lr);
+#endif
+
+    masm.setupUnalignedABICall(2, scratch);
+    masm.movePtr(ImmWord(cx->runtime), scratch);
+    masm.passABIArg(scratch);
+    masm.passABIArg(objReg);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PostWriteBarrier));
+
+    masm.ret();
+    return true;
+}
+#endif // JSGC_GENERATIONAL
+
 bool
 BaselineCompiler::emitIC(ICStub *stub, bool isForOp)
 {
     ICEntry *entry = allocateICEntry(stub, isForOp);
     if (!entry)
         return false;
 
     CodeOffsetLabel patchOffset;
@@ -1671,33 +1713,46 @@ BaselineCompiler::emit_JSOP_DELPROP()
         return false;
 
     masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1);
     frame.pop();
     frame.push(R1);
     return true;
 }
 
-Address
-BaselineCompiler::getScopeCoordinateAddress(Register reg)
+void
+BaselineCompiler::getScopeCoordinateObject(Register reg)
 {
     ScopeCoordinate sc(pc);
 
     masm.loadPtr(frame.addressOfScopeChain(), reg);
     for (unsigned i = sc.hops; i; i--)
         masm.extractObject(Address(reg, ScopeObject::offsetOfEnclosingScope()), reg);
-
+}
+
+Address
+BaselineCompiler::getScopeCoordinateAddressFromObject(Register objReg, Register reg)
+{
+    ScopeCoordinate sc(pc);
     Shape *shape = ScopeCoordinateToStaticScopeShape(cx, script, pc);
+
     Address addr;
     if (shape->numFixedSlots() <= sc.slot) {
-        masm.loadPtr(Address(reg, JSObject::offsetOfSlots()), reg);
+        masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), reg);
         return Address(reg, (sc.slot - shape->numFixedSlots()) * sizeof(Value));
     }
 
-    return Address(reg, JSObject::getFixedSlotOffset(sc.slot));
+    return Address(objReg, JSObject::getFixedSlotOffset(sc.slot));
+}
+
+Address
+BaselineCompiler::getScopeCoordinateAddress(Register reg)
+{
+    getScopeCoordinateObject(reg);
+    return getScopeCoordinateAddressFromObject(reg, reg);
 }
 
 bool
 BaselineCompiler::emit_JSOP_GETALIASEDVAR()
 {
     frame.syncStack(0);
 
     Address address = getScopeCoordinateAddress(R0.scratchReg());
@@ -1715,23 +1770,44 @@ bool
 BaselineCompiler::emit_JSOP_CALLALIASEDVAR()
 {
     return emit_JSOP_GETALIASEDVAR();
 }
 
 bool
 BaselineCompiler::emit_JSOP_SETALIASEDVAR()
 {
-    // Sync everything except the top value, so that we can use R0 as scratch
-    // (storeValue does not touch it if the top value is in R0).
-    frame.syncStack(1);
-
-    Address address = getScopeCoordinateAddress(R2.scratchReg());
+    // Keep rvalue in R0.
+    frame.popRegsAndSync(1);
+    Register objReg = R2.scratchReg();
+
+    getScopeCoordinateObject(objReg);
+    Address address = getScopeCoordinateAddressFromObject(objReg, R1.scratchReg());
     masm.patchableCallPreBarrier(address, MIRType_Value);
-    storeValue(frame.peek(-1), address, R0);
+    masm.storeValue(R0, address);
+    frame.push(R0);
+
+#ifdef JSGC_GENERATIONAL
+    // Fully sync the stack if post-barrier is needed.
+    // Scope coordinate object is already in R2.scratchReg().
+    frame.syncStack(0);
+
+    Nursery &nursery = cx->runtime->gcNursery;
+    Label skipBarrier;
+    Label isTenured;
+    masm.branchTestObject(Assembler::NotEqual, R0, &skipBarrier);
+    masm.branchPtr(Assembler::Below, objReg, ImmWord(nursery.start()), &isTenured);
+    masm.branchPtr(Assembler::Below, objReg, ImmWord(nursery.end()), &skipBarrier);
+
+    masm.bind(&isTenured);
+    masm.call(postBarrierSlot_);
+
+    masm.bind(&skipBarrier);
+#endif
+
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_NAME()
 {
     frame.syncStack(0);
 
--- a/js/src/ion/BaselineCompiler.h
+++ b/js/src/ion/BaselineCompiler.h
@@ -171,16 +171,19 @@ namespace ion {
     _(JSOP_RETURN)             \
     _(JSOP_STOP)               \
     _(JSOP_RETRVAL)
 
 class BaselineCompiler : public BaselineCompilerSpecific
 {
     FixedList<Label>            labels_;
     HeapLabel *                 return_;
+#ifdef JSGC_GENERATIONAL
+    HeapLabel *                 postBarrierSlot_;
+#endif
 
     // Native code offset right before the scope chain is initialized.
     CodeOffsetLabel prologueOffset_;
 
     Label *labelOf(jsbytecode *pc) {
         return &labels_[pc - script->code];
     }
 
@@ -190,16 +193,19 @@ class BaselineCompiler : public Baseline
 
     MethodStatus compile();
 
   private:
     MethodStatus emitBody();
 
     bool emitPrologue();
     bool emitEpilogue();
+#ifdef JSGC_GENERATIONAL
+    bool emitOutOfLinePostBarrierSlot();
+#endif
     bool emitIC(ICStub *stub, bool isForOp);
     bool emitOpIC(ICStub *stub) {
         return emitIC(stub, true);
     }
     bool emitNonOpIC(ICStub *stub) {
         return emitIC(stub, false);
     }
 
@@ -241,16 +247,18 @@ class BaselineCompiler : public Baseline
     bool emitInitElemGetterSetter();
 
     bool emitFormalArgAccess(uint32_t arg, bool get);
 
     bool emitEnterBlock();
 
     bool addPCMappingEntry(bool addIndexEntry);
 
+    void getScopeCoordinateObject(Register reg);
+    Address getScopeCoordinateAddressFromObject(Register objReg, Register reg);
     Address getScopeCoordinateAddress(Register reg);
 };
 
 } // namespace ion
 } // namespace js
 
 #endif
 
--- a/js/src/ion/BaselineHelpers.h
+++ b/js/src/ion/BaselineHelpers.h
@@ -6,18 +6,20 @@
 
 #if !defined(jsion_baseline_helpers_h__) && defined(JS_ION)
 #define jsion_baseline_helpers_h__
 
 #if defined(JS_CPU_X86)
 # include "x86/BaselineHelpers-x86.h"
 #elif defined(JS_CPU_X64)
 # include "x64/BaselineHelpers-x64.h"
+#elif defined(JS_CPU_ARM)
+# include "arm/BaselineHelpers-arm.h"
 #else
-# include "arm/BaselineHelpers-arm.h"
+# error "Unknown architecture!"
 #endif
 
 namespace js {
 namespace ion {
 
 } // namespace ion
 } // namespace js
 
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -633,16 +633,47 @@ ICStubCompiler::guardProfilingEnabled(Ma
                       Imm32(BaselineFrame::HAS_PUSHED_SPS_FRAME),
                       skip);
 
     // Check if profiling is enabled
     uint32_t *enabledAddr = cx->runtime->spsProfiler.addressOfEnabled();
     masm.branch32(Assembler::Equal, AbsoluteAddress(enabledAddr), Imm32(0), skip);
 }
 
+#ifdef JSGC_GENERATIONAL
+inline bool
+ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler &masm, Register obj, Register scratch,
+                                         GeneralRegisterSet saveRegs)
+{
+    Nursery &nursery = cx->runtime->gcNursery;
+
+    Label skipBarrier;
+    Label isTenured;
+    masm.branchPtr(Assembler::Below, obj, ImmWord(nursery.start()), &isTenured);
+    masm.branchPtr(Assembler::Below, obj, ImmWord(nursery.end()), &skipBarrier);
+    masm.bind(&isTenured);
+
+    // void PostWriteBarrier(JSRuntime *rt, JSObject *obj);
+#ifdef JS_CPU_ARM
+    saveRegs.add(BaselineTailCallReg);
+#endif
+    saveRegs = GeneralRegisterSet::Intersect(saveRegs, GeneralRegisterSet::Volatile());
+    masm.PushRegsInMask(saveRegs);
+    masm.setupUnalignedABICall(2, scratch);
+    masm.movePtr(ImmWord(cx->runtime), scratch);
+    masm.passABIArg(scratch);
+    masm.passABIArg(obj);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PostWriteBarrier));
+    masm.PopRegsInMask(saveRegs);
+
+    masm.bind(&skipBarrier);
+    return true;
+}
+#endif // JSGC_GENERATIONAL
+
 //
 // UseCount_Fallback
 //
 static bool
 IsTopFrameConstructing(JSContext *cx)
 {
     IonFrameIterator iter(cx);
     JS_ASSERT(iter.type() == IonFrame_Exit);
@@ -4319,56 +4350,75 @@ ICSetElem_Dense::Compiler::generateStubC
 
     // Unstow R0 and R1 (object and key)
     EmitUnstowICValues(masm, 2);
 
     // Reset register set.
     regs = availableGeneralRegs(2);
     scratchReg = regs.takeAny();
 
+    // Unbox object and key.
+    obj = masm.extractObject(R0, ExtractTemp0);
+    Register key = masm.extractInt32(R1, ExtractTemp1);
+
     // Load obj->elements in scratchReg.
     masm.loadPtr(Address(obj, JSObject::offsetOfElements()), scratchReg);
 
-    // Unbox key.
-    Register key = masm.extractInt32(R1, ExtractTemp1);
-
     // Bounds check.
     Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
     masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure);
 
     // Hole check.
     BaseIndex element(scratchReg, key, TimesEight);
     masm.branchTestMagic(Assembler::Equal, element, &failure);
 
+    // Failure is not possible now.  Free up registers.
+    regs.add(R0);
+    regs.add(R1);
+    regs.takeUnchecked(obj);
+    regs.takeUnchecked(key);
+    Address valueAddr(BaselineStackReg, ICStackValueOffset);
+
     // Convert int32 values to double if convertDoubleElements is set. In this
     // case the heap typeset is guaranteed to contain both int32 and double, so
     // it's okay to store a double.
-    Label convertDoubles, convertDoublesDone;
+    Label dontConvertDoubles;
     Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
-    masm.branchTest32(Assembler::NonZero, elementsFlags,
+    masm.branchTest32(Assembler::Zero, elementsFlags,
                       Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
-                      &convertDoubles);
-    masm.bind(&convertDoublesDone);
-
-    // It's safe to overwrite R0 now.
-    Address valueAddr(BaselineStackReg, ICStackValueOffset);
-    masm.loadValue(valueAddr, R0);
-    EmitPreBarrier(masm, element, MIRType_Value);
-    masm.storeValue(R0, element);
-    EmitReturnFromIC(masm);
-
-    // Convert to double and jump back. Note that double arrays are only
-    // created by IonMonkey, so if we have no floating-point support
-    // Ion is disabled and there should be no double arrays.
-    masm.bind(&convertDoubles);
+                      &dontConvertDoubles);
+    // Note that double arrays are only created by IonMonkey, so if we have no
+    // floating-point support Ion is disabled and there should be no double arrays.
     if (cx->runtime->jitSupportsFloatingPoint)
-        masm.convertInt32ValueToDouble(valueAddr, R0.scratchReg(), &convertDoublesDone);
+        masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles);
     else
         masm.breakpoint();
-    masm.jump(&convertDoublesDone);
+    masm.bind(&dontConvertDoubles);
+
+    // Don't overwrite R0 becuase |obj| might overlap with it, and it's needed
+    // for post-write barrier later.
+    ValueOperand tmpVal = regs.takeAnyValue();
+    masm.loadValue(valueAddr, tmpVal);
+    EmitPreBarrier(masm, element, MIRType_Value);
+    masm.storeValue(tmpVal, element);
+    regs.add(key);
+    regs.add(tmpVal);
+#ifdef JSGC_GENERATIONAL
+    Label skipBarrier;
+    masm.branchTestObject(Assembler::NotEqual, tmpVal, &skipBarrier);
+    {
+        Register r = regs.takeAny();
+        GeneralRegisterSet saveRegs;
+        emitPostWriteBarrierSlot(masm, obj, r, saveRegs);
+        regs.add(r);
+    }
+    masm.bind(&skipBarrier);
+#endif
+    EmitReturnFromIC(masm);
+
 
     // Failure case - fail but first unstow R0 and R1
     masm.bind(&failureUnstow);
     EmitUnstowICValues(masm, 2);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
@@ -4454,16 +4504,17 @@ ICSetElemDenseAddCompiler::generateStubC
                    &failureUnstow);
     regs.add(typeReg);
 
     // Shape guard objects on the proto chain.
     scratchReg = regs.takeAny();
     Register protoReg = regs.takeAny();
     for (size_t i = 0; i < protoChainDepth_; i++) {
         masm.loadObjProto(i == 0 ? obj : protoReg, protoReg);
+        masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow);
         masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseAddImpl<0>::offsetOfShape(i + 1)),
                      scratchReg);
         masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratchReg, &failureUnstow);
     }
     regs.add(protoReg);
     regs.add(scratchReg);
 
     // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
@@ -4476,67 +4527,84 @@ ICSetElemDenseAddCompiler::generateStubC
 
     // Unstow R0 and R1 (object and key)
     EmitUnstowICValues(masm, 2);
 
     // Reset register set.
     regs = availableGeneralRegs(2);
     scratchReg = regs.takeAny();
 
+    // Unbox obj and key.
+    obj = masm.extractObject(R0, ExtractTemp0);
+    Register key = masm.extractInt32(R1, ExtractTemp1);
+
     // Load obj->elements in scratchReg.
     masm.loadPtr(Address(obj, JSObject::offsetOfElements()), scratchReg);
 
-    // Unbox key.
-    Register key = masm.extractInt32(R1, ExtractTemp1);
-
     // Bounds check (key == initLength)
     Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
     masm.branch32(Assembler::NotEqual, initLength, key, &failure);
 
     // Capacity check.
     Address capacity(scratchReg, ObjectElements::offsetOfCapacity());
     masm.branch32(Assembler::BelowOrEqual, capacity, key, &failure);
 
+    // Failure is not possible now.  Free up registers.
+    regs.add(R0);
+    regs.add(R1);
+    regs.takeUnchecked(obj);
+    regs.takeUnchecked(key);
+
     // Increment initLength before write.
     masm.add32(Imm32(1), initLength);
 
     // If length is now <= key, increment length before write.
     Label skipIncrementLength;
     Address length(scratchReg, ObjectElements::offsetOfLength());
     masm.branch32(Assembler::Above, length, key, &skipIncrementLength);
     masm.add32(Imm32(1), length);
     masm.bind(&skipIncrementLength);
 
+    Address valueAddr(BaselineStackReg, ICStackValueOffset);
+
     // Convert int32 values to double if convertDoubleElements is set. In this
     // case the heap typeset is guaranteed to contain both int32 and double, so
     // it's okay to store a double.
-    Label convertDoubles, convertDoublesDone;
+    Label dontConvertDoubles;
     Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
-    masm.branchTest32(Assembler::NonZero, elementsFlags,
+    masm.branchTest32(Assembler::Zero, elementsFlags,
                       Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
-                      &convertDoubles);
-    masm.bind(&convertDoublesDone);
-
-    // Write the value.  No need for write barrier since we're not overwriting an old value.
-    // It's safe to overwrite R0 now.
-    BaseIndex element(scratchReg, key, TimesEight);
-    Address valueAddr(BaselineStackReg, ICStackValueOffset);
-    masm.loadValue(valueAddr, R0);
-    masm.storeValue(R0, element);
-    EmitReturnFromIC(masm);
-
-    // Convert to double and jump back. Note that double arrays are only
-    // created by IonMonkey, so if we have no floating-point support
-    // Ion is disabled and there should be no double arrays.
-    masm.bind(&convertDoubles);
+                      &dontConvertDoubles);
+    // Note that double arrays are only created by IonMonkey, so if we have no
+    // floating-point support Ion is disabled and there should be no double arrays.
     if (cx->runtime->jitSupportsFloatingPoint)
-        masm.convertInt32ValueToDouble(valueAddr, R0.scratchReg(), &convertDoublesDone);
+        masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles);
     else
         masm.breakpoint();
-    masm.jump(&convertDoublesDone);
+    masm.bind(&dontConvertDoubles);
+
+    // Write the value.  No need for pre-barrier since we're not overwriting an old value.
+    ValueOperand tmpVal = regs.takeAnyValue();
+    BaseIndex element(scratchReg, key, TimesEight);
+    masm.loadValue(valueAddr, tmpVal);
+    masm.storeValue(tmpVal, element);
+    regs.add(key);
+    regs.add(tmpVal);
+#ifdef JSGC_GENERATIONAL
+    Label skipBarrier;
+    masm.branchTestObject(Assembler::NotEqual, tmpVal, &skipBarrier);
+    {
+        Register r = regs.takeAny();
+        GeneralRegisterSet saveRegs;
+        emitPostWriteBarrierSlot(masm, obj, r, saveRegs);
+        regs.add(r);
+    }
+    masm.bind(&skipBarrier);
+#endif
+    EmitReturnFromIC(masm);
 
     // Failure case - fail but first unstow R0 and R1
     masm.bind(&failureUnstow);
     EmitUnstowICValues(masm, 2);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
@@ -6294,23 +6362,45 @@ ICSetProp_Native::Compiler::generateStub
 
     // Call the type-update stub.
     if (!callTypeUpdateIC(masm, sizeof(Value)))
         return false;
 
     // Unstow R0 and R1 (object and key)
     EmitUnstowICValues(masm, 2);
 
-    if (!isFixedSlot_)
-        masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), objReg);
+    regs.add(R0);
+    regs.takeUnchecked(objReg);
+
+    Register holderReg;
+    if (isFixedSlot_) {
+        holderReg = objReg;
+    } else {
+        holderReg = regs.takeAny();
+        masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), holderReg);
+    }
 
     // Perform the store.
     masm.load32(Address(BaselineStubReg, ICSetProp_Native::offsetOfOffset()), scratch);
-    EmitPreBarrier(masm, BaseIndex(objReg, scratch, TimesOne), MIRType_Value);
-    masm.storeValue(R1, BaseIndex(objReg, scratch, TimesOne));
+    EmitPreBarrier(masm, BaseIndex(holderReg, scratch, TimesOne), MIRType_Value);
+    masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne));
+    if (holderReg != objReg)
+        regs.add(holderReg);
+#ifdef JSGC_GENERATIONAL
+    Label skipBarrier;
+    masm.branchTestObject(Assembler::NotEqual, R1, &skipBarrier);
+    {
+        Register scr = regs.takeAny();
+        GeneralRegisterSet saveRegs;
+        saveRegs.add(R1);
+        emitPostWriteBarrierSlot(masm, objReg, scr, saveRegs);
+        regs.add(scr);
+    }
+    masm.bind(&skipBarrier);
+#endif
 
     // The RHS has to be in R0.
     masm.moveValue(R1, R0);
     EmitReturnFromIC(masm);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
@@ -6369,16 +6459,17 @@ ICSetPropNativeAddCompiler::generateStub
     EmitStowICValues(masm, 2);
 
     regs = availableGeneralRegs(1);
     scratch = regs.takeAny();
     Register protoReg = regs.takeAny();
     // Check the proto chain.
     for (size_t i = 0; i < protoChainDepth_; i++) {
         masm.loadObjProto(i == 0 ? objReg : protoReg, protoReg);
+        masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow);
         masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAddImpl<0>::offsetOfShape(i + 1)),
                      scratch);
         masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratch, &failureUnstow);
     }
 
     // Shape and type checks succeeded, ok to proceed.
 
     // Load RHS into R0 for TypeUpdate check.
@@ -6395,23 +6486,45 @@ ICSetPropNativeAddCompiler::generateStub
     scratch = regs.takeAny();
 
     // Changing object shape.  Write the object's new shape.
     Address shapeAddr(objReg, JSObject::offsetOfShape());
     EmitPreBarrier(masm, shapeAddr, MIRType_Shape);
     masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
     masm.storePtr(scratch, shapeAddr);
 
-    if (!isFixedSlot_)
-        masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), objReg);
+    Register holderReg;
+    regs.add(R0);
+    regs.takeUnchecked(objReg);
+    if (isFixedSlot_) {
+        holderReg = objReg;
+    } else {
+        holderReg = regs.takeAny();
+        masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), holderReg);
+    }
 
     // Perform the store.  No write barrier required since this is a new
     // initialization.
     masm.load32(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfOffset()), scratch);
-    masm.storeValue(R1, BaseIndex(objReg, scratch, TimesOne));
+    masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne));
+
+    if (holderReg != objReg)
+        regs.add(holderReg);
+
+#ifdef JSGC_GENERATIONAL
+    Label skipBarrier;
+    masm.branchTestObject(Assembler::NotEqual, R1, &skipBarrier);
+    {
+        Register scr = regs.takeAny();
+        GeneralRegisterSet saveRegs;
+        saveRegs.add(R1);
+        emitPostWriteBarrierSlot(masm, objReg, scr, saveRegs);
+    }
+    masm.bind(&skipBarrier);
+#endif
 
     // The RHS has to be in R0.
     masm.moveValue(R1, R0);
     EmitReturnFromIC(masm);
 
     // Failure case - jump to next stub
     masm.bind(&failureUnstow);
     EmitUnstowICValues(masm, 2);
@@ -8220,17 +8333,18 @@ ICSetProp_Native::Compiler::getStub(ICSt
     ICUpdatedStub *stub = ICSetProp_Native::New(space, getStubCode(), type, shape, offset_);
     if (!stub || !stub->initUpdatingChain(cx, space))
         return NULL;
     return stub;
 }
 
 ICSetProp_NativeAdd::ICSetProp_NativeAdd(IonCode *stubCode, HandleTypeObject type,
                                          size_t protoChainDepth,
-                                         HandleShape newShape, uint32_t offset)
+                                         HandleShape newShape,
+                                         uint32_t offset)
   : ICUpdatedStub(SetProp_NativeAdd, stubCode),
     type_(type),
     newShape_(newShape),
     offset_(offset)
 {
     JS_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH);
     extra_ = protoChainDepth;
 }
@@ -8245,17 +8359,18 @@ ICSetProp_NativeAddImpl<ProtoChainDepth>
 {
     JS_ASSERT(shapes->length() == NumShapes);
     for (size_t i = 0; i < NumShapes; i++)
         shapes_[i].init((*shapes)[i]);
 }
 
 ICSetPropNativeAddCompiler::ICSetPropNativeAddCompiler(JSContext *cx, HandleObject obj,
                                                        HandleShape oldShape,
-                                                       size_t protoChainDepth, bool isFixedSlot,
+                                                       size_t protoChainDepth,
+                                                       bool isFixedSlot,
                                                        uint32_t offset)
   : ICStubCompiler(cx, ICStub::SetProp_NativeAdd),
     obj_(cx, obj),
     oldShape_(cx, oldShape),
     protoChainDepth_(protoChainDepth),
     isFixedSlot_(isFixedSlot),
     offset_(offset)
 {
--- a/js/src/ion/BaselineIC.h
+++ b/js/src/ion/BaselineIC.h
@@ -1041,16 +1041,21 @@ class ICStubCompiler
             break;
           default:
             JS_NOT_REACHED("Invalid numInputs");
         }
 
         return regs;
     }
 
+#ifdef JSGC_GENERATIONAL
+    inline bool emitPostWriteBarrierSlot(MacroAssembler &masm, Register obj, Register scratch,
+                                         GeneralRegisterSet saveRegs);
+#endif
+
   public:
     virtual ICStub *getStub(ICStubSpace *space) = 0;
 
     ICStubSpace *getStubSpace(JSScript *script) {
         if (ICStub::CanMakeCalls(kind))
             return script->baselineScript()->fallbackStubSpace();
         return script->compartment()->ionCompartment()->optimizedStubSpace();
     }
@@ -4547,31 +4552,31 @@ class ICSetProp_Native : public ICUpdate
     static size_t offsetOfShape() {
         return offsetof(ICSetProp_Native, shape_);
     }
     static size_t offsetOfOffset() {
         return offsetof(ICSetProp_Native, offset_);
     }
 
     class Compiler : public ICStubCompiler {
-        HandleObject obj_;
+        RootedObject obj_;
         bool isFixedSlot_;
         uint32_t offset_;
 
       protected:
         virtual int32_t getKey() const {
             return static_cast<int32_t>(kind) | (static_cast<int32_t>(isFixedSlot_) << 16);
         }
 
         bool generateStubCode(MacroAssembler &masm);
 
       public:
         Compiler(JSContext *cx, HandleObject obj, bool isFixedSlot, uint32_t offset)
           : ICStubCompiler(cx, ICStub::SetProp_Native),
-            obj_(obj),
+            obj_(cx, obj),
             isFixedSlot_(isFixedSlot),
             offset_(offset)
         {}
 
         ICUpdatedStub *getStub(ICStubSpace *space);
     };
 };
 
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -1448,17 +1448,17 @@ CodeGenerator::visitCallDOMNative(LCallD
 
     masm.loadJSContext(argJSContext);
 
     masm.passABIArg(argJSContext);
     masm.passABIArg(argObj);
     masm.passABIArg(argPrivate);
     masm.passABIArg(argArgc);
     masm.passABIArg(argVp);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->jitInfo()->op));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->jitInfo()->method));
 
     if (target->jitInfo()->isInfallible) {
         masm.loadValue(Address(StackPointer, IonDOMMethodExitFrameLayout::offsetOfResult()),
                        JSReturnOperand);
     } else {
         // Test for failure.
         Label success, exception;
         masm.branchIfFalseBool(ReturnReg, &exception);
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -4948,17 +4948,17 @@ DOMCallNeedsBarrier(const JSJitInfo* jit
         return true;
 
     // JSVAL_TYPE_OBJECT doesn't tell us much; we still have to barrier on the
     // actual type of the object.
     if (jitinfo->returnType == JSVAL_TYPE_OBJECT)
         return true;
 
     // No need for a barrier if we're already expecting the type we'll produce.
-    return jitinfo->returnType == types->getKnownTypeTag();
+    return jitinfo->returnType != types->getKnownTypeTag();
 }
 
 bool
 IonBuilder::makeCall(HandleFunction target, CallInfo &callInfo, bool cloneAtCallsite)
 {
     MCall *call = makeCallHelper(target, callInfo, cloneAtCallsite);
     if (!call)
         return false;
@@ -7786,17 +7786,18 @@ IonBuilder::jsop_setprop(HandlePropertyN
     if (!TestCommonPropFunc(cx, objTypes, id, &commonSetter, false, &isDOM, NULL))
         return false;
     if (commonSetter) {
         // Setters can be called even if the property write needs a type
         // barrier, as calling the setter does not actually write any data
         // properties.
         RootedFunction setter(cx, commonSetter);
         if (isDOM && TestShouldDOMCall(cx, objTypes, setter, JSJitInfo::Setter)) {
-            MSetDOMProperty *set = MSetDOMProperty::New(setter->jitInfo()->op, obj, value);
+            JS_ASSERT(setter->jitInfo()->type == JSJitInfo::Setter);
+            MSetDOMProperty *set = MSetDOMProperty::New(setter->jitInfo()->setter, obj, value);
             if (!set)
                 return false;
 
             current->add(set);
             current->push(value);
 
             return resumeAfter(set);
         }
--- a/js/src/ion/IonMacroAssembler.cpp
+++ b/js/src/ion/IonMacroAssembler.cpp
@@ -428,16 +428,21 @@ MacroAssembler::newGCThing(const Registe
 
 #ifdef JS_GC_ZEAL
     // Don't execute the inline path if gcZeal is active.
     movePtr(ImmWord(zone->rt), result);
     loadPtr(Address(result, offsetof(JSRuntime, gcZeal_)), result);
     branch32(Assembler::NotEqual, result, Imm32(0), fail);
 #endif
 
+    // Don't execute the inline path if the compartment has an object metadata callback,
+    // as the metadata to use for the object may vary between executions of the op.
+    if (GetIonContext()->compartment->objectMetadataCallback)
+        jump(fail);
+
 #ifdef JSGC_GENERATIONAL
     Nursery &nursery = zone->rt->gcNursery;
     if (nursery.isEnabled() && allocKind <= gc::FINALIZE_OBJECT_LAST) {
         // Inline Nursery::allocate. No explicit check for nursery.isEnabled()
         // is needed, as the comparison with the nursery's end will always fail
         // in such cases.
         loadPtr(AbsoluteAddress(nursery.addressOfPosition()), result);
         addPtr(Imm32(thingSize), result);
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -366,16 +366,19 @@ class MDefinition : public MNode
         id_ = id;
     }
 
     uint32_t valueNumber() const;
     void setValueNumber(uint32_t vn);
     ValueNumberData *valueNumberData() {
         return valueNumber_;
     }
+    void clearValueNumberData() {
+        valueNumber_ = NULL;
+    }
     void setValueNumberData(ValueNumberData *vn) {
         JS_ASSERT(valueNumber_ == NULL);
         valueNumber_ = vn;
     }
 #define FLAG_ACCESSOR(flag) \
     bool is##flag() const {\
         return hasFlags(1 << flag);\
     }\
@@ -6581,34 +6584,34 @@ class MCallInitElementArray
         return this;
     }
 };
 
 class MSetDOMProperty
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >
 {
-    const JSJitPropertyOp func_;
-
-    MSetDOMProperty(const JSJitPropertyOp func, MDefinition *obj, MDefinition *val)
+    const JSJitSetterOp func_;
+
+    MSetDOMProperty(const JSJitSetterOp func, MDefinition *obj, MDefinition *val)
       : func_(func)
     {
         setOperand(0, obj);
         setOperand(1, val);
     }
 
   public:
     INSTRUCTION_HEADER(SetDOMProperty)
 
-    static MSetDOMProperty *New(const JSJitPropertyOp func, MDefinition *obj, MDefinition *val)
+    static MSetDOMProperty *New(const JSJitSetterOp func, MDefinition *obj, MDefinition *val)
     {
         return new MSetDOMProperty(func, obj, val);
     }
 
-    const JSJitPropertyOp fun() {
+    const JSJitSetterOp fun() {
         return func_;
     }
 
     MDefinition *object() {
         return getOperand(0);
     }
 
     MDefinition *value()
@@ -6626,16 +6629,17 @@ class MGetDOMProperty
     public ObjectPolicy<0>
 {
     const JSJitInfo *info_;
 
     MGetDOMProperty(const JSJitInfo *jitinfo, MDefinition *obj, MDefinition *guard)
       : info_(jitinfo)
     {
         JS_ASSERT(jitinfo);
+        JS_ASSERT(jitinfo->type == JSJitInfo::Getter);
 
         setOperand(0, obj);
 
         // Pin the guard as an operand if we want to hoist later
         setOperand(1, guard);
 
         // We are movable iff the jitinfo says we can be.
         if (jitinfo->isPure)
@@ -6652,18 +6656,18 @@ class MGetDOMProperty
   public:
     INSTRUCTION_HEADER(GetDOMProperty)
 
     static MGetDOMProperty *New(const JSJitInfo *info, MDefinition *obj, MDefinition *guard)
     {
         return new MGetDOMProperty(info, obj, guard);
     }
 
-    const JSJitPropertyOp fun() {
-        return info_->op;
+    const JSJitGetterOp fun() {
+        return info_->getter;
     }
     bool isInfallible() const {
         return info_->isInfallible;
     }
     bool isDomConstant() const {
         return info_->isConstant;
     }
     bool isDomPure() const {
--- a/js/src/ion/UnreachableCodeElimination.cpp
+++ b/js/src/ion/UnreachableCodeElimination.cpp
@@ -2,16 +2,17 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "UnreachableCodeElimination.h"
 #include "IonAnalysis.h"
 #include "AliasAnalysis.h"
+#include "ValueNumbering.h"
 
 using namespace js;
 using namespace ion;
 
 bool
 UnreachableCodeElimination::analyze()
 {
     // The goal of this routine is to eliminate code that is
@@ -58,30 +59,43 @@ UnreachableCodeElimination::removeUnmark
 
     // Pass 2: Remove unmarked blocks (see analyze() above).
     if (!removeUnmarkedBlocksAndClearDominators())
         return false;
     graph_.unmarkBlocks();
 
     AssertGraphCoherency(graph_);
 
-    IonSpewPass("UCEMidPoint");
+    IonSpewPass("UCE-mid-point");
 
     // Pass 3: Recompute dominators and tweak phis.
     BuildDominatorTree(graph_);
     if (redundantPhis_ && !EliminatePhis(mir_, graph_, ConservativeObservability))
         return false;
 
     // Pass 4: Rerun alias analysis
     if (rerunAliasAnalysis_) {
         AliasAnalysis analysis(mir_, graph_);
         if (!analysis.analyze())
             return false;
     }
 
+    // Pass 5: It's important for optimizations to re-run GVN (and in
+    // turn alias analysis) after UCE if we eliminated branches.
+    if (rerunAliasAnalysis_ && js_IonOptions.gvn) {
+        ValueNumberer gvn(mir_, graph_, js_IonOptions.gvnIsOptimistic);
+        if (!gvn.clear() || !gvn.analyze())
+            return false;
+        IonSpewPass("GVN-after-UCE");
+        AssertExtendedGraphCoherency(graph_);
+
+        if (mir_->shouldCancel("GVN-after-UCE"))
+            return false;
+    }
+
     return true;
 }
 
 bool
 UnreachableCodeElimination::prunePointlessBranchesAndMarkReachableBlocks()
 {
     Vector<MBasicBlock *, 16, SystemAllocPolicy> worklist;
 
--- a/js/src/ion/ValueNumbering.cpp
+++ b/js/src/ion/ValueNumbering.cpp
@@ -417,16 +417,34 @@ ValueNumberer::eliminateRedundancies()
 
 // Exported method, called by the compiler.
 bool
 ValueNumberer::analyze()
 {
     return computeValueNumbers() && eliminateRedundancies();
 }
 
+// Called by the compiler if we need to re-run GVN.
+bool
+ValueNumberer::clear()
+{
+    IonSpew(IonSpew_GVN, "Clearing value numbers");
+
+    // Clear the VN of every MDefinition
+    for (ReversePostorderIterator block(graph_.rpoBegin()); block != graph_.rpoEnd(); block++) {
+        if (mir->shouldCancel("Value Numbering (clearing)"))
+            return false;
+        for (MDefinitionIterator iter(*block); iter; iter++)
+            iter->clearValueNumberData();
+        block->lastIns()->clearValueNumberData();
+    }
+
+    return true;
+}
+
 uint32_t
 MDefinition::valueNumber() const
 {
     JS_ASSERT(block_);
     if (valueNumber_ == NULL)
         return 0;
     return valueNumber_->valueNumber();
 }
--- a/js/src/ion/ValueNumbering.h
+++ b/js/src/ion/ValueNumbering.h
@@ -81,16 +81,17 @@ class ValueNumberer
     MIRGraph &graph_;
     ValueMap values;
     bool pessimisticPass_;
     size_t count_;
 
   public:
     ValueNumberer(MIRGenerator *mir, MIRGraph &graph, bool optimistic);
     bool analyze();
+    bool clear();
 };
 
 class ValueNumberData : public TempObject {
 
     friend void ValueNumberer::breakClass(MDefinition*);
     friend MDefinition *ValueNumberer::findSplit(MDefinition*);
     uint32_t number;
     MDefinition *classNext;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/metadata-hook.js
@@ -0,0 +1,39 @@
+
+x = [1,2,3];
+setObjectMetadata(x, {y:0});
+assertEq(getObjectMetadata(x).y, 0);
+
+incallback = false;
+count = 0;
+
+setObjectMetadataCallback(function(obj) {
+    if (incallback)
+      return null;
+    incallback = true;
+    var res = {count:++count, location:Error().stack};
+    incallback = false;
+    return res;
+  });
+
+function Foo() {
+  this.x = 0;
+  this.y = 1;
+}
+
+function f() {
+  w = new Foo();
+  x = [1,2,3];
+  y = [2,3,5];
+  z = {a:0,b:1};
+}
+f();
+
+var wc = getObjectMetadata(w).count;
+var xc = getObjectMetadata(x).count;
+var yc = getObjectMetadata(y).count;
+var zc = getObjectMetadata(z).count;
+
+assertEq(xc > wc, true);
+assertEq(yc > xc, true);
+assertEq(zc > yc, true);
+assertEq(/\.js/.test(getObjectMetadata(x).location), true);
deleted file mode 100644
--- a/js/src/jit-test/tests/ion/bug725062.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// |jit-test| error: InternalError
-function rec(x, self) {
-    if (a = parseLegacyJSON("[1 , ]").length)
-        self(x - 001 , self);
-    self(NaN, self);
-}
-rec(1, rec);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -127,29 +127,16 @@ js::StringIsArrayIndex(JSLinearString *s
         JS_ASSERT(index <= MAX_ARRAY_INDEX);
         *indexp = index;
         return true;
     }
 
     return false;
 }
 
-Shape *
-js::GetDenseArrayShape(JSContext *cx, HandleObject globalObj)
-{
-    JS_ASSERT(globalObj);
-
-    JSObject *proto = globalObj->global().getOrCreateArrayPrototype(cx);
-    if (!proto)
-        return NULL;
-
-    return EmptyShape::getInitialShape(cx, &ArrayClass, proto, proto->getParent(),
-                                       gc::FINALIZE_OBJECT0);
-}
-
 bool
 DoubleIndexToId(JSContext *cx, double index, MutableHandleId id)
 {
     if (index == uint32_t(index))
         return IndexToId(cx, uint32_t(index), id);
 
     return ValueToId<CanGC>(cx, DoubleValue(index), id);
 }
@@ -2816,17 +2803,18 @@ js_InitArrayClass(JSContext *cx, HandleO
     RootedObject proto(cx, global->getOrCreateObjectPrototype(cx));
     if (!proto)
         return NULL;
 
     RootedTypeObject type(cx, proto->getNewType(cx, &ArrayClass));
     if (!type)
         return NULL;
 
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &ArrayClass, TaggedProto(proto), proto->getParent(),
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &ArrayClass, TaggedProto(proto),
+                                                      proto->getParent(), NewObjectMetadata(cx),
                                                       gc::FINALIZE_OBJECT0));
 
     RootedObject arrayProto(cx, JSObject::createArray(cx, gc::FINALIZE_OBJECT4, gc::TenuredHeap, shape, type, 0));
     if (!arrayProto || !JSObject::setSingletonType(cx, arrayProto) || !AddLengthProperty(cx, arrayProto))
         return NULL;
 
     RootedFunction ctor(cx);
     ctor = global->createConstructor(cx, js_Array, cx->names().Array, 1);
@@ -2885,16 +2873,17 @@ NewArray(JSContext *cx, uint32_t length,
     gc::AllocKind allocKind = GuessArrayGCKind(length);
     JS_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayClass));
     allocKind = GetBackgroundAllocKind(allocKind);
 
     NewObjectCache &cache = cx->runtime->newObjectCache;
 
     NewObjectCache::EntryIndex entry = -1;
     if (newKind == GenericObject &&
+        !cx->compartment->objectMetadataCallback &&
         cache.lookupGlobal(&ArrayClass, cx->global(), allocKind, &entry))
     {
         RootedObject obj(cx, cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, &ArrayClass)));
         if (obj) {
             /* Fixup the elements pointer and length, which may be incorrect. */
             obj->setFixedElements();
             JSObject::setArrayLength(cx, obj, length);
             if (allocateCapacity && !EnsureNewArrayElements(cx, obj, length))
@@ -2914,17 +2903,17 @@ NewArray(JSContext *cx, uint32_t length,
     if (!type)
         return NULL;
 
     /*
      * Get a shape with zero fixed slots, regardless of the size class.
      * See JSObject::createArray.
      */
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &ArrayClass, TaggedProto(proto),
-                                                      cx->global(), gc::FINALIZE_OBJECT0));
+                                                      cx->global(), NewObjectMetadata(cx), gc::FINALIZE_OBJECT0));
     if (!shape)
         return NULL;
 
     RootedObject obj(cx, JSObject::createArray(cx, allocKind, GetInitialHeap(newKind, &ArrayClass),
                                                shape, type, length));
     if (!obj)
         return NULL;
 
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -82,20 +82,16 @@ WouldDefinePastNonwritableLength(JSConte
 
 /*
  * Canonicalize |vp| to a uint32_t value potentially suitable for use as an
  * array length.
  */
 extern bool
 CanonicalizeArrayLengthValue(JSContext *cx, HandleValue v, uint32_t *canonicalized);
 
-/* Get the common shape used by all dense arrays with a prototype at globalObj. */
-extern Shape *
-GetDenseArrayShape(JSContext *cx, HandleObject globalObj);
-
 extern JSBool
 GetLengthProperty(JSContext *cx, HandleObject obj, uint32_t *lengthp);
 
 extern JSBool
 SetLengthProperty(JSContext *cx, HandleObject obj, double length);
 
 extern JSBool
 ObjectMayHaveExtraIndexedProperties(JSObject *obj);
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -105,16 +105,19 @@ NewObjectCache::fillType(EntryIndex entr
 {
     JS_ASSERT(obj->type() == type);
     return fill(entry, clasp, type, kind, obj);
 }
 
 inline JSObject *
 NewObjectCache::newObjectFromHit(JSContext *cx, EntryIndex entry_, js::gc::InitialHeap heap)
 {
+    // The new object cache does not account for metadata attached via callbacks.
+    JS_ASSERT(!cx->compartment->objectMetadataCallback);
+
     JS_ASSERT(unsigned(entry_) < mozilla::ArrayLength(entries));
     Entry *entry = &entries[entry_];
 
     JSObject *obj = js_NewGCObject<NoGC>(cx, entry->kind, heap);
     if (obj) {
         copyCachedToObject(obj, reinterpret_cast<JSObject *>(&entry->templateObject), entry->kind);
         Probes::createObject(cx, obj);
         return obj;
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -37,16 +37,17 @@ JSCompartment::JSCompartment(Zone *zone)
     principals(NULL),
     isSystem(false),
     marked(true),
     global_(NULL),
     enterCompartmentDepth(0),
     lastCodeRelease(0),
     analysisLifoAlloc(ANALYSIS_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     data(NULL),
+    objectMetadataCallback(NULL),
     lastAnimationTime(0),
     regExps(rt),
     propertyTree(thisForCtor()),
     gcIncomingGrayPointers(NULL),
     gcLiveArrayBuffers(NULL),
     gcWeakMapList(NULL),
     debugModeBits(rt->debugMode ? DebugFromC : 0),
     rngState(0),
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -177,16 +177,18 @@ struct JSCompartment
 
     bool                         activeAnalysis;
 
     /* Type information about the scripts and objects in this compartment. */
     js::types::TypeCompartment   types;
 
     void                         *data;
 
+    js::ObjectMetadataCallback   objectMetadataCallback;
+
   private:
     js::WrapperMap               crossCompartmentWrappers;
 
   public:
     /* Last time at which an animation was played for a global in this compartment. */
     int64_t                      lastAnimationTime;
 
     js::RegExpCompartment        regExps;
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1048,16 +1048,38 @@ js::AutoCTypesActivityCallback::AutoCTyp
   : cx(cx), callback(cx->runtime->ctypesActivityCallback), endType(endType)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
     if (callback)
         callback(cx, beginType);
 }
 
+JS_FRIEND_API(void)
+js::SetObjectMetadataCallback(JSContext *cx, ObjectMetadataCallback callback)
+{
+    // Clear any jitcode in the runtime, which behaves differently depending on
+    // whether there is a creation callback.
+    ReleaseAllJITCode(cx->runtime->defaultFreeOp());
+
+    cx->compartment->objectMetadataCallback = callback;
+}
+
+JS_FRIEND_API(bool)
+js::SetObjectMetadata(JSContext *cx, JSHandleObject obj, JSHandleObject metadata)
+{
+    return JSObject::setMetadata(cx, obj, metadata);
+}
+
+JS_FRIEND_API(JSObject *)
+js::GetObjectMetadata(JSObject *obj)
+{
+    return obj->getMetadata();
+}
+
 JS_FRIEND_API(JSBool)
 js_DefineOwnProperty(JSContext *cx, JSObject *objArg, jsid idArg,
                      const js::PropertyDescriptor& descriptor, JSBool *bp)
 {
     RootedObject obj(cx, objArg);
     RootedId id(cx, idArg);
     JS_ASSERT(cx->runtime->heapState == js::Idle);
     CHECK_REQUEST(cx);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -9,16 +9,18 @@
 
 #include "mozilla/GuardObjects.h"
 
 #include "jsclass.h"
 #include "jscpucfg.h"
 #include "jspubtd.h"
 #include "jsprvtd.h"
 
+#include "js/CallArgs.h"
+
 /*
  * This macro checks if the stack pointer has exceeded a given limit. If
  * |tolerance| is non-zero, it returns true only if the stack pointer has
  * exceeded the limit by more than |tolerance| bytes.
  */
 #if JS_STACK_GROWTH_DIRECTION > 0
 # define JS_CHECK_STACK_SIZE_WITH_TOLERANCE(limit, sp, tolerance)  \
     ((uintptr_t)(sp) < (limit)+(tolerance))
@@ -317,18 +319,19 @@ GetAnyCompartmentInZone(JS::Zone *zone);
 namespace shadow {
 
 struct TypeObject {
     Class       *clasp;
     JSObject    *proto;
 };
 
 struct BaseShape {
-    js::Class   *clasp;
-    JSObject    *parent;
+    js::Class *clasp;
+    JSObject *parent;
+    JSObject *_1;
     JSCompartment *compartment;
 };
 
 class Shape {
 public:
     shadow::BaseShape *base;
     jsid              _1;
     uint32_t          slotInfo;
@@ -1428,35 +1431,111 @@ JS_GetDataViewByteLength(JSObject *obj);
  * it would pass such a test: it is a data view or a wrapper of a data view,
  * and the unwrapping will succeed. If cx is NULL, then DEBUG builds may be
  * unable to assert when unwrapping should be disallowed.
  */
 JS_FRIEND_API(void *)
 JS_GetDataViewData(JSObject *obj);
 
 /*
+ * A class, expected to be passed by value, which represents the CallArgs for a
+ * JSJitGetterOp.
+ */
+class JSJitGetterCallArgs : protected JS::MutableHandleValue
+{
+  public:
+    explicit JSJitGetterCallArgs(const JS::CallArgs& args)
+      : JS::MutableHandleValue(args.rval())
+    {}
+
+    JS::MutableHandleValue rval() {
+        return *this;
+    }
+};
+
+/*
+ * A class, expected to be passed by value, which represents the CallArgs for a
+ * JSJitSetterOp.
+ */
+class JSJitSetterCallArgs : protected JS::MutableHandleValue
+{
+  public:
+    explicit JSJitSetterCallArgs(const JS::CallArgs& args)
+      : JS::MutableHandleValue(args.handleAt(0))
+    {}
+
+    JS::MutableHandleValue handleAt(unsigned i) {
+        MOZ_ASSERT(i == 0);
+        return *this;
+    }
+
+    unsigned length() const { return 1; }
+
+    // Add get() or maybe hasDefined() as needed
+};
+
+/*
+ * A class, expected to be passed by reference, which represents the CallArgs
+ * for a JSJitMethodOp.
+ */
+class JSJitMethodCallArgs : protected JS::detail::CallArgsBase<JS::detail::NoUsedRval>
+{
+  private:
+    typedef JS::detail::CallArgsBase<JS::detail::NoUsedRval> Base;
+
+  public:
+    explicit JSJitMethodCallArgs(const JS::CallArgs& args) {
+        argv_ = args.array();
+        argc_ = args.length();
+    }
+
+    JS::MutableHandleValue rval() const {
+        return Base::rval();
+    }
+
+    unsigned length() const { return Base::length(); }
+
+    JS::MutableHandleValue handleAt(unsigned i) const {
+        return Base::handleAt(i);
+    }
+
+    bool hasDefined(unsigned i) const {
+        return Base::hasDefined(i);
+    }
+
+    // Add get() as needed
+};
+
+/*
  * This struct contains metadata passed from the DOM to the JS Engine for JIT
  * optimizations on DOM property accessors. Eventually, this should be made
  * available to general JSAPI users, but we are not currently ready to do so.
  */
 typedef bool
-(* JSJitPropertyOp)(JSContext *cx, JSHandleObject thisObj,
-                    void *specializedThis, JS::Value *vp);
+(* JSJitGetterOp)(JSContext *cx, JSHandleObject thisObj,
+                  void *specializedThis, JS::Value *vp);
+typedef bool
+(* JSJitSetterOp)(JSContext *cx, JSHandleObject thisObj,
+                  void *specializedThis, JS::Value *vp);
 typedef bool
 (* JSJitMethodOp)(JSContext *cx, JSHandleObject thisObj,
                   void *specializedThis, unsigned argc, JS::Value *vp);
 
 struct JSJitInfo {
     enum OpType {
         Getter,
         Setter,
         Method
     };
 
-    JSJitPropertyOp op;
+    union {
+        JSJitGetterOp getter;
+        JSJitSetterOp setter;
+        JSJitMethodOp method;
+    };
     uint32_t protoID;
     uint32_t depth;
     OpType type;
     bool isInfallible;      /* Is op fallible? False in setters. */
     bool isConstant;        /* Getting a construction-time constant? */
     bool isPure;            /* As long as no non-pure DOM things happen, will
                                keep returning the same value for the given
                                "this" object" */
@@ -1613,16 +1692,36 @@ class JS_FRIEND_API(AutoCTypesActivityCa
 
 #ifdef DEBUG
 extern JS_FRIEND_API(void)
 assertEnteredPolicy(JSContext *cx, JSObject *obj, jsid id);
 #else
 inline void assertEnteredPolicy(JSContext *cx, JSObject *obj, jsid id) {};
 #endif
 
+typedef JSObject *
+(* ObjectMetadataCallback)(JSContext *cx);
+
+/*
+ * Specify a callback to invoke when creating each JS object in the current
+ * compartment, which may return a metadata object to associate with the
+ * object. Objects with different metadata have different shape hierarchies,
+ * so for efficiency, objects should generally try to share metadata objects.
+ */
+JS_FRIEND_API(void)
+SetObjectMetadataCallback(JSContext *cx, ObjectMetadataCallback callback);
+
+/* Manipulate the metadata associated with an object. */
+
+JS_FRIEND_API(bool)
+SetObjectMetadata(JSContext *cx, JSHandleObject obj, JSHandleObject metadata);
+
+JS_FRIEND_API(JSObject *)
+GetObjectMetadata(JSObject *obj);
+
 } /* namespace js */
 
 extern JS_FRIEND_API(JSBool)
 js_DefineOwnProperty(JSContext *cx, JSObject *objArg, jsid idArg,
                      const js::PropertyDescriptor& descriptor, JSBool *bp);
 
 extern JS_FRIEND_API(JSBool)
 js_ReportIsNotFunction(JSContext *cx, const JS::Value& v);
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -373,17 +373,17 @@ static inline PropertyIteratorObject *
 NewPropertyIteratorObject(JSContext *cx, unsigned flags)
 {
     if (flags & JSITER_ENUMERATE) {
         RootedTypeObject type(cx, cx->compartment->getNewType(cx, &PropertyIteratorObject::class_, NULL));
         if (!type)
             return NULL;
 
         Class *clasp = &PropertyIteratorObject::class_;
-        RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, NULL, NULL,
+        RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, NULL, NULL, NewObjectMetadata(cx),
                                                           ITERATOR_FINALIZE_KIND));
         if (!shape)
             return NULL;
 
         JSObject *obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND,
                                          GetInitialHeap(GenericObject, clasp), shape, type);
         if (!obj)
             return NULL;
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -169,16 +169,34 @@ ComputeAccurateBinaryBaseInteger(const j
         }
         value += bit2 & (bit | sticky);
         value *= factor;
     }
 
     return value;
 }
 
+double
+js::ParseDecimalNumber(const JS::TwoByteChars chars)
+{
+    MOZ_ASSERT(chars.length() > 0);
+    uint64_t dec = 0;
+    RangedPtr<jschar> s = chars.start(), end = chars.end();
+    do {
+        jschar c = *s;
+        MOZ_ASSERT('0' <= c && c <= '9');
+        uint8_t digit = c - '0';
+        uint64_t next = dec * 10 + digit;
+        MOZ_ASSERT(next < DOUBLE_INTEGRAL_PRECISION_LIMIT,
+                   "next value won't be an integrally-precise double");
+        dec = next;
+    } while (++s < end);
+    return static_cast<double>(dec);
+}
+
 bool
 js::GetPrefixInteger(JSContext *cx, const jschar *start, const jschar *end, int base,
                      const jschar **endp, double *dp)
 {
     JS_ASSERT(start <= end);
     JS_ASSERT(2 <= base && base <= 36);
 
     const jschar *s = start;
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -104,16 +104,25 @@ NumberToCString(JSContext *cx, ToCString
 
 /*
  * The largest positive integer such that all positive integers less than it
  * may be precisely represented using the IEEE-754 double-precision format.
  */
 const double DOUBLE_INTEGRAL_PRECISION_LIMIT = uint64_t(1) << 53;
 
 /*
+ * Parse a decimal number encoded in |chars|.  The decimal number must be
+ * sufficiently small that it will not overflow the integrally-precise range of
+ * the double type -- that is, the number will be smaller than
+ * DOUBLE_INTEGRAL_PRECISION_LIMIT
+ */
+extern double
+ParseDecimalNumber(const JS::TwoByteChars chars);
+
+/*
  * Compute the positive integer of the given base described immediately at the
  * start of the range [start, end) -- no whitespace-skipping, no magical
  * leading-"0" octal or leading-"0x" hex behavior, no "+"/"-" parsing, just
  * reading the digits of the integer.  Return the index one past the end of the
  * digits of the integer in *endp, and return the integer itself in *dp.  If
  * base is 10 or a power of two the returned integer is the closest possible
  * double; otherwise extremely large integers may be slightly inaccurate.
  *
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1110,16 +1110,17 @@ JSObject::sealOrFreeze(JSContext *cx, Ha
          * hierarchy mirroring the original one, which can be shared if many
          * objects with the same structure are sealed/frozen. If we use the
          * generic path below then any non-empty object will be converted to
          * dictionary mode.
          */
         RootedShape last(cx, EmptyShape::getInitialShape(cx, obj->getClass(),
                                                          obj->getTaggedProto(),
                                                          obj->getParent(),
+                                                         obj->getMetadata(),
                                                          obj->numFixedSlots(),
                                                          obj->lastProperty()->getObjectFlags()));
         if (!last)
             return false;
 
         /* Get an in order list of the shapes in this object. */
         AutoShapeVector shapes(cx);
         for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) {
@@ -1241,17 +1242,17 @@ NewObject(JSContext *cx, Class *clasp, t
     JS_ASSERT(clasp != &ArrayClass);
     JS_ASSERT_IF(clasp == &FunctionClass,
                  kind == JSFunction::FinalizeKind || kind == JSFunction::ExtendedFinalizeKind);
     JS_ASSERT_IF(parent, &parent->global() == cx->compartment->maybeGlobal());
 
     RootedTypeObject type(cx, type_);
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(type->proto),
-                                                      parent, kind));
+                                                      parent, NewObjectMetadata(cx), kind));
     if (!shape)
         return NULL;
 
     gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
     JSObject *obj = JSObject::create(cx, kind, heap, shape, type);
     if (!obj)
         return NULL;
 
@@ -1282,17 +1283,19 @@ js::NewObjectWithGivenProto(JSContext *c
     RootedObject parent(cx, parent_);
 
     if (CanBeFinalizedInBackground(allocKind, clasp))
         allocKind = GetBackgroundAllocKind(allocKind);
 
     NewObjectCache &cache = cx->runtime->newObjectCache;
 
     NewObjectCache::EntryIndex entry = -1;
-    if (proto.isObject() && newKind == GenericObject &&
+    if (proto.isObject() &&
+        newKind == GenericObject &&
+        !cx->compartment->objectMetadataCallback &&
         (!parent || parent == proto.toObject()->getParent()) && !proto.toObject()->isGlobal())
     {
         if (cache.lookupProto(clasp, proto.toObject(), allocKind, &entry)) {
             JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp));
             if (obj)
                 return obj;
         }
     }
@@ -1340,17 +1343,21 @@ js::NewObjectWithClassProtoCommon(JSCont
      * stored in an immutable slot on the global (except for ClearScope, which
      * will flush the new object cache).
      */
     JSProtoKey protoKey = GetClassProtoKey(clasp);
 
     NewObjectCache &cache = cx->runtime->newObjectCache;
 
     NewObjectCache::EntryIndex entry = -1;
-    if (parentArg->isGlobal() && protoKey != JSProto_Null && newKind == GenericObject) {
+    if (parentArg->isGlobal() &&
+        protoKey != JSProto_Null &&
+        newKind == GenericObject &&
+        !cx->compartment->objectMetadataCallback)
+    {
         if (cache.lookupGlobal(clasp, &parentArg->asGlobal(), allocKind, &entry)) {
             JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, clasp));
             if (obj)
                 return obj;
         }
     }
 
     RootedObject parent(cx, parentArg);
@@ -1382,17 +1389,20 @@ js::NewObjectWithType(JSContext *cx, Han
 
     JS_ASSERT(allocKind <= gc::FINALIZE_OBJECT_LAST);
     if (CanBeFinalizedInBackground(allocKind, &ObjectClass))
         allocKind = GetBackgroundAllocKind(allocKind);
 
     NewObjectCache &cache = cx->runtime->newObjectCache;
 
     NewObjectCache::EntryIndex entry = -1;
-    if (parent == type->proto->getParent() && newKind == GenericObject) {
+    if (parent == type->proto->getParent() &&
+        newKind == GenericObject &&
+        !cx->compartment->objectMetadataCallback)
+    {
         if (cache.lookupType(&ObjectClass, type, allocKind, &entry)) {
             JSObject *obj = cache.newObjectFromHit(cx, entry, GetInitialHeap(newKind, &ObjectClass));
             if (obj)
                 return obj;
         }
     }
 
     JSObject *obj = NewObject(cx, &ObjectClass, type, parent, allocKind, newKind);
@@ -1488,20 +1498,23 @@ CreateThisForFunctionWithType(JSContext 
     if (type->newScript) {
         /*
          * Make an object with the type's associated finalize kind and shape,
          * which reflects any properties that will definitely be added to the
          * object before it is read from.
          */
         gc::AllocKind kind = type->newScript->allocKind;
         RootedObject res(cx, NewObjectWithType(cx, type, parent, kind, newKind));
-        if (res) {
-            RootedShape shape(cx, type->newScript->shape);
-            JS_ALWAYS_TRUE(JSObject::setLastProperty(cx, res, shape));
-        }
+        if (!res)
+            return NULL;
+        RootedObject metadata(cx, res->getMetadata());
+        RootedShape shape(cx, type->newScript->shape);
+        JS_ALWAYS_TRUE(JSObject::setLastProperty(cx, res, shape));
+        if (metadata && !JSObject::setMetadata(cx, res, metadata))
+            return NULL;
         return res;
     }
 
     gc::AllocKind allocKind = NewObjectGCKind(&ObjectClass);
     return NewObjectWithType(cx, type, parent, allocKind, newKind);
 }
 
 JSObject *
@@ -1854,26 +1867,26 @@ JSObject::ReserveForTradeGuts(JSContext 
      * invariant that objects with the same shape have the same number of
      * inline slots. The fixed slots will be updated in place during TradeGuts.
      * Non-native objects need to be reshaped according to the new count.
      */
     if (a->isNative()) {
         if (!a->generateOwnShape(cx))
             return false;
     } else {
-        reserved.newbshape = EmptyShape::getInitialShape(cx, aClass, aProto, a->getParent(),
+        reserved.newbshape = EmptyShape::getInitialShape(cx, aClass, aProto, a->getParent(), a->getMetadata(),
                                                          b->tenuredGetAllocKind());
         if (!reserved.newbshape)
             return false;
     }
     if (b->isNative()) {
         if (!b->generateOwnShape(cx))
             return false;
     } else {
-        reserved.newashape = EmptyShape::getInitialShape(cx, bClass, bProto, b->getParent(),
+        reserved.newashape = EmptyShape::getInitialShape(cx, bClass, bProto, b->getParent(), b->getMetadata(),
                                                          a->tenuredGetAllocKind());
         if (!reserved.newashape)
             return false;
     }
 
     /* The avals/bvals vectors hold all original values from the objects. */
 
     if (!reserved.avals.reserve(a->slotSpan()))
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -523,16 +523,20 @@ class JSObject : public js::ObjectImpl
 
     /*
      * Get the enclosing scope of an object. When called on non-scope object,
      * this will just be the global (the name "enclosing scope" still applies
      * in this situation because non-scope objects can be on the scope chain).
      */
     inline JSObject *enclosingScope();
 
+    /* Access the metadata on an object. */
+    inline JSObject *getMetadata() const;
+    static bool setMetadata(JSContext *cx, js::HandleObject obj, js::HandleObject newMetadata);
+
     inline js::GlobalObject &global() const;
     using js::ObjectImpl::compartment;
 
     /* Remove the type (and prototype) or parent from a new object. */
     static inline bool clearType(JSContext *cx, js::HandleObject obj);
     static bool clearParent(JSContext *cx, js::HandleObject obj);
 
     /*
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -247,16 +247,22 @@ JSObject::finalize(js::FreeOp *fop)
 
 inline JSObject *
 JSObject::getParent() const
 {
     return lastProperty()->getObjectParent();
 }
 
 inline JSObject *
+JSObject::getMetadata() const
+{
+    return lastProperty()->getObjectMetadata();
+}
+
+inline JSObject *
 JSObject::enclosingScope()
 {
     return isScope()
            ? &asScope().enclosingScope()
            : isDebugScope()
            ? &asDebugScope().enclosingScope()
            : getParent();
 }
@@ -303,16 +309,17 @@ JSObject::canRemoveLastProperty()
      * property's base shape is consistent with that stored in the previous
      * shape. If not consistent, then the last property cannot be removed as it
      * will induce a change in the object itself, and the object must be
      * converted to dictionary mode instead. See BaseShape comment in jsscope.h
      */
     JS_ASSERT(!inDictionaryMode());
     js::Shape *previous = lastProperty()->previous().get();
     return previous->getObjectParent() == lastProperty()->getObjectParent()
+        && previous->getObjectMetadata() == lastProperty()->getObjectMetadata()
         && previous->getObjectFlags() == lastProperty()->getObjectFlags();
 }
 
 inline const js::HeapSlot *
 JSObject::getRawSlots()
 {
     JS_ASSERT(isGlobal());
     return slots;
@@ -1679,19 +1686,22 @@ CopyInitializerObject(JSContext *cx, Han
     gc::AllocKind allocKind = gc::GetGCObjectFixedSlotsKind(baseobj->numFixedSlots());
     allocKind = gc::GetBackgroundAllocKind(allocKind);
     JS_ASSERT_IF(baseobj->isTenured(), allocKind == baseobj->tenuredGetAllocKind());
     RootedObject obj(cx);
     obj = NewBuiltinClassInstance(cx, &ObjectClass, allocKind, newKind);
     if (!obj)
         return NULL;
 
+    RootedObject metadata(cx, obj->getMetadata());
     RootedShape lastProp(cx, baseobj->lastProperty());
     if (!JSObject::setLastProperty(cx, obj, lastProp))
         return NULL;
+    if (metadata && !JSObject::setMetadata(cx, obj, metadata))
+        return NULL;
 
     return obj;
 }
 
 JSObject *
 NewReshapedObject(JSContext *cx, HandleTypeObject type, JSObject *parent,
                   gc::AllocKind kind, HandleShape shape);
 
@@ -1788,16 +1798,28 @@ JSObject *
 DefineConstructorAndPrototype(JSContext *cx, HandleObject obj, JSProtoKey key, HandleAtom atom,
                               JSObject *protoProto, Class *clasp,
                               Native constructor, unsigned nargs,
                               const JSPropertySpec *ps, const JSFunctionSpec *fs,
                               const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs,
                               JSObject **ctorp = NULL,
                               gc::AllocKind ctorKind = JSFunction::FinalizeKind);
 
+static JS_ALWAYS_INLINE JSObject *
+NewObjectMetadata(JSContext *cx)
+{
+    // The metadata callback is invoked before each created object, except when
+    // analysis is active as the callback may reenter JS.
+    if (JS_UNLIKELY((size_t)cx->compartment->objectMetadataCallback) && !cx->compartment->activeAnalysis) {
+        gc::AutoSuppressGC suppress(cx);
+        return cx->compartment->objectMetadataCallback(cx);
+    }
+    return NULL;
+}
+
 } /* namespace js */
 
 extern JSObject *
 js_InitClass(JSContext *cx, js::HandleObject obj, JSObject *parent_proto,
              js::Class *clasp, JSNative constructor, unsigned nargs,
              const JSPropertySpec *ps, const JSFunctionSpec *fs,
              const JSPropertySpec *static_ps, const JSFunctionSpec *static_fs,
              JSObject **ctorp = NULL,
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -41,68 +41,16 @@ Class js::JSONClass = {
     JS_DeletePropertyStub,  /* delProperty */
     JS_PropertyStub,        /* getProperty */
     JS_StrictPropertyStub,  /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub
 };
 
-/* ES5 15.12.2. */
-JSBool
-js_json_parse(JSContext *cx, unsigned argc, Value *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    /* Step 1. */
-    JSString *str = (argc >= 1) ? ToString<CanGC>(cx, args[0]) : cx->names().undefined;
-    if (!str)
-        return false;
-
-    JSStableString *stable = str->ensureStable(cx);
-    if (!stable)
-        return false;
-
-    JS::Anchor<JSString *> anchor(stable);
-
-    RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue());
-
-    /* Steps 2-5. */
-    return ParseJSONWithReviver(cx, stable->chars(), stable->length(), reviver, args.rval());
-}
-
-/* ES5 15.12.3. */
-JSBool
-js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
-{
-    RootedObject replacer(cx, (argc >= 2 && vp[3].isObject())
-                              ? &vp[3].toObject()
-                              : NULL);
-    RootedValue value(cx, (argc >= 1) ? vp[2] : UndefinedValue());
-    RootedValue space(cx, (argc >= 3) ? vp[4] : UndefinedValue());
-
-    StringBuffer sb(cx);
-    if (!js_Stringify(cx, &value, replacer, space, sb))
-        return false;
-
-    // XXX This can never happen to nsJSON.cpp, but the JSON object
-    // needs to support returning undefined. So this is a little awkward
-    // for the API, because we want to support streaming writers.
-    if (!sb.empty()) {
-        JSString *str = sb.finishString();
-        if (!str)
-            return false;
-        vp->setString(str);
-    } else {
-        vp->setUndefined();
-    }
-
-    return true;
-}
-
 static inline bool IsQuoteSpecialCharacter(jschar c)
 {
     JS_STATIC_ASSERT('\b' < ' ');
     JS_STATIC_ASSERT('\f' < ' ');
     JS_STATIC_ASSERT('\n' < ' ');
     JS_STATIC_ASSERT('\r' < ' ');
     JS_STATIC_ASSERT('\t' < ' ');
     return c == '"' || c == '\\' || c < ' ';
@@ -850,23 +798,22 @@ Revive(JSContext *cx, HandleValue revive
 
     if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp))
         return false;
 
     Rooted<jsid> id(cx, NameToId(cx->names().empty));
     return Walk(cx, obj, id, reviver, vp);
 }
 
-JSBool
+bool
 js::ParseJSONWithReviver(JSContext *cx, StableCharPtr chars, size_t length, HandleValue reviver,
-                         MutableHandleValue vp, DecodingMode decodingMode /* = STRICT */)
+                         MutableHandleValue vp)
 {
     /* 15.12.2 steps 2-3. */
-    JSONParser parser(cx, chars, length,
-                      decodingMode == STRICT ? JSONParser::StrictJSON : JSONParser::LegacyJSON);
+    JSONParser parser(cx, chars, length);
     if (!parser.parse(vp))
         return false;
 
     /* 15.12.2 steps 4-5. */
     if (js_IsCallable(reviver))
         return Revive(cx, reviver, vp);
     return true;
 }
@@ -875,16 +822,68 @@ js::ParseJSONWithReviver(JSContext *cx, 
 static JSBool
 json_toSource(JSContext *cx, unsigned argc, Value *vp)
 {
     vp->setString(cx->names().JSON);
     return JS_TRUE;
 }
 #endif
 
+/* ES5 15.12.2. */
+JSBool
+js_json_parse(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    /* Step 1. */
+    JSString *str = (argc >= 1) ? ToString<CanGC>(cx, args[0]) : cx->names().undefined;
+    if (!str)
+        return false;
+
+    JSStableString *stable = str->ensureStable(cx);
+    if (!stable)
+        return false;
+
+    JS::Anchor<JSString *> anchor(stable);
+
+    RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue());
+
+    /* Steps 2-5. */
+    return ParseJSONWithReviver(cx, stable->chars(), stable->length(), reviver, args.rval());
+}
+
+/* ES5 15.12.3. */
+JSBool
+js_json_stringify(JSContext *cx, unsigned argc, Value *vp)
+{
+    RootedObject replacer(cx, (argc >= 2 && vp[3].isObject())
+                              ? &vp[3].toObject()
+                              : NULL);
+    RootedValue value(cx, (argc >= 1) ? vp[2] : UndefinedValue());
+    RootedValue space(cx, (argc >= 3) ? vp[4] : UndefinedValue());
+
+    StringBuffer sb(cx);
+    if (!js_Stringify(cx, &value, replacer, space, sb))
+        return false;
+
+    // XXX This can never happen to nsJSON.cpp, but the JSON object
+    // needs to support returning undefined. So this is a little awkward
+    // for the API, because we want to support streaming writers.
+    if (!sb.empty()) {
+        JSString *str = sb.finishString();
+        if (!str)
+            return false;
+        vp->setString(str);
+    } else {
+        vp->setUndefined();
+    }
+
+    return true;
+}
+
 static const JSFunctionSpec json_static_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str,  json_toSource,      0, 0),
 #endif
     JS_FN("parse",          js_json_parse,      2, 0),
     JS_FN("stringify",      js_json_stringify,  3, 0),
     JS_FS_END
 };
--- a/js/src/json.h
+++ b/js/src/json.h
@@ -2,47 +2,30 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef json_h___
 #define json_h___
 
-#include "jsprvtd.h"
-#include "jspubtd.h"
-#include "jsapi.h"
-
+#include "js/CharacterEncoding.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
 #include "js/Vector.h"
-
-#define JSON_MAX_DEPTH  2048
-#define JSON_PARSER_BUFSIZE 1024
+#include "vm/StringBuffer.h"
 
 extern JSObject *
 js_InitJSONClass(JSContext *cx, js::HandleObject obj);
 
 extern JSBool
-js_Stringify(JSContext *cx, js::MutableHandleValue vp,
-             JSObject *replacer, js::Value space,
-             js::StringBuffer &sb);
-
-// Avoid build errors on certain platforms that define these names as constants
-#undef STRICT
-#undef LEGACY
-
-/*
- * The type of JSON decoding to perform.  Strict decoding is to-the-spec;
- * legacy decoding accepts a few non-JSON syntaxes historically accepted by the
- * implementation.  (Full description of these deviations is deliberately
- * omitted.)  New users should use strict decoding rather than legacy decoding,
- * as legacy decoding might be removed at a future time.
- */
-enum DecodingMode { STRICT, LEGACY };
+js_Stringify(JSContext *cx, js::MutableHandleValue vp, JSObject *replacer,
+             js::Value space, js::StringBuffer &sb);
 
 namespace js {
 
-extern JS_FRIEND_API(JSBool)
-ParseJSONWithReviver(JSContext *cx, JS::StableCharPtr chars, size_t length, HandleValue filter,
-                     MutableHandleValue vp, DecodingMode decodingMode = STRICT);
+extern bool
+ParseJSONWithReviver(JSContext *cx, JS::StableCharPtr chars, size_t length, HandleValue reviver,
+                     MutableHandleValue vp);
 
-} /* namespace js */
+} // namespace js
 
 #endif /* json_h___ */
--- a/js/src/jsonparser.cpp
+++ b/js/src/jsonparser.cpp
@@ -217,18 +217,28 @@ JSONParser::readNumber()
         for (; current < end; current++) {
             if (!JS7_ISDEC(*current))
                 break;
         }
     }
 
     /* Fast path: no fractional or exponent part. */
     if (current == end || (*current != '.' && *current != 'e' && *current != 'E')) {
+        TwoByteChars chars(digitStart.get(), current - digitStart);
+        if (chars.length() < strlen("9007199254740992")) {
+            // If the decimal number is shorter than the length of 2**53, (the
+            // largest number a double can represent with integral precision),
+            // parse it using a decimal-only parser.  This comparison is
+            // conservative but faster than a fully-precise check.
+            double d = ParseDecimalNumber(chars);
+            return numberToken(negative ? -d : d);
+        }
+
+        double d;
         const jschar *dummy;
-        double d;
         if (!GetPrefixInteger(cx, digitStart.get(), current.get(), 10, &dummy, &d))
             return token(OOM);
         JS_ASSERT(current == dummy);
         return numberToken(negative ? -d : d);
     }
 
     /* (\.[0-9]+)? */
     if (current < end && *current == '.') {
@@ -450,29 +460,16 @@ JSONParser::advancePropertyName()
     if (current >= end) {
         error("end of data when property name was expected");
         return token(Error);
     }
 
     if (*current == '"')
         return readString<PropertyName>();
 
-    if (parsingMode == LegacyJSON && *current == '}') {
-        /*
-         * Previous JSON parsing accepted trailing commas in non-empty object
-         * syntax, and some users depend on this.  (Specifically, Places data
-         * serialization in versions of Firefox before 4.0.  We can remove this
-         * mode when profile upgrades from 3.6 become unsupported.)  Permit
-         * such trailing commas only when legacy parsing is specifically
-         * requested.
-         */
-        current++;
-        return token(ObjectClose);
-    }
-
     error("expected double-quoted property name");
     return token(Error);
 }
 
 JSONParser::Token
 JSONParser::advancePropertyColon()
 {
     JS_ASSERT(current[-1] == '"');
@@ -644,23 +641,16 @@ JSONParser::parse(MutableHandleValue vp)
                     return false;
                 token = advancePropertyColon();
                 if (token != Colon) {
                     JS_ASSERT(token == Error);
                     return errorReturn();
                 }
                 goto JSONValue;
             }
-            if (token == ObjectClose) {
-                JS_ASSERT(state == FinishObjectMember);
-                JS_ASSERT(parsingMode == LegacyJSON);
-                if (!finishObject(&value, stack.back().properties()))
-                    return false;
-                break;
-            }
             if (token == OOM)
                 return false;
             if (token != Error)
                 error("property names must be double-quoted strings");
             return errorReturn();
 
           case FinishArrayElement: {
             ElementVector &elements = stack.back().elements();
@@ -739,34 +729,16 @@ JSONParser::parse(MutableHandleValue vp)
                     if (!finishObject(&value, *properties))
                         return false;
                     break;
                 }
                 goto JSONMember;
               }
 
               case ArrayClose:
-                if (parsingMode == LegacyJSON &&
-                    !stack.empty() &&
-                    stack.back().state == FinishArrayElement) {
-                    /*
-                     * Previous JSON parsing accepted trailing commas in
-                     * non-empty array syntax, and some users depend on this.
-                     * (Specifically, Places data serialization in versions of
-                     * Firefox prior to 4.0.  We can remove this mode when
-                     * profile upgrades from 3.6 become unsupported.)  Permit
-                     * such trailing commas only when specifically
-                     * instructed to do so.
-                     */
-                    if (!finishArray(&value, stack.back().elements()))
-                        return false;
-                    break;
-                }
-                /* FALL THROUGH */
-
               case ObjectClose:
               case Colon:
               case Comma:
                 error("unexpected character");
                 return errorReturn();
 
               case OOM:
                 return false;
--- a/js/src/jsonparser.h
+++ b/js/src/jsonparser.h
@@ -4,40 +4,36 @@
  * 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 jsonparser_h___
 #define jsonparser_h___
 
 #include "mozilla/Attributes.h"
 
-#include "jscntxt.h"
-#include "jsstr.h"
+#include "jsapi.h"
+
+#include "vm/String.h"
 
 namespace js {
 
-/*
- * NB: This class must only be used on the stack.
- */
-class JSONParser : private AutoGCRooter
+class MOZ_STACK_CLASS JSONParser : private AutoGCRooter
 {
   public:
     enum ErrorHandling { RaiseError, NoError };
-    enum ParsingMode { StrictJSON, LegacyJSON };
 
   private:
     /* Data members */
 
     JSContext * const cx;
     StableCharPtr current;
     const StableCharPtr end;
 
     Value v;
 
-    const ParsingMode parsingMode;
     const ErrorHandling errorHandling;
 
     enum Token { String, Number, True, False, Null,
                  ArrayOpen, ArrayClose,
                  ObjectOpen, ObjectClose,
                  Colon, Comma,
                  OOM, Error };
 
@@ -108,30 +104,23 @@ class JSONParser : private AutoGCRooter
     Token lastToken;
 #endif
 
     JSONParser *thisDuringConstruction() { return this; }
 
   public:
     /* Public API */
 
-    /*
-     * Create a parser for the provided JSON data.  The parser will accept
-     * certain legacy, non-JSON syntax if decodingMode is LegacyJSON.
-     * Description of this syntax is deliberately omitted: new code should only
-     * use strict JSON parsing.
-     */
+    /* Create a parser for the provided JSON data. */
     JSONParser(JSContext *cx, JS::StableCharPtr data, size_t length,
-               ParsingMode parsingMode = StrictJSON,
                ErrorHandling errorHandling = RaiseError)
       : AutoGCRooter(cx, JSONPARSER),
         cx(cx),
         current(data),
         end((data + length).get(), data.get(), length),
-        parsingMode(parsingMode),
         errorHandling(errorHandling),
         stack(cx),
         freeElements(cx),
         freeProperties(cx)
 #ifdef DEBUG
       , lastToken(Error)
 #endif
     {
@@ -145,55 +134,55 @@ class JSONParser : private AutoGCRooter
      * successfully, store the prescribed value in *vp and return true.  If an
      * internal error (e.g. OOM) occurs during parsing, return false.
      * Otherwise, if invalid input was specifed but no internal error occurred,
      * behavior depends upon the error handling specified at construction: if
      * error handling is RaiseError then throw a SyntaxError and return false,
      * otherwise return true and set *vp to |undefined|.  (JSON syntax can't
      * represent |undefined|, so the JSON data couldn't have specified it.)
      */
-    bool parse(js::MutableHandleValue vp);
+    bool parse(MutableHandleValue vp);
 
   private:
-    js::Value numberValue() const {
+    Value numberValue() const {
         JS_ASSERT(lastToken == Number);
         JS_ASSERT(v.isNumber());
         return v;
     }
 
-    js::Value stringValue() const {
+    Value stringValue() const {
         JS_ASSERT(lastToken == String);
         JS_ASSERT(v.isString());
         return v;
     }
 
     JSAtom *atomValue() const {
-        js::Value strval = stringValue();
+        Value strval = stringValue();
         return &strval.toString()->asAtom();
     }
 
     Token token(Token t) {
         JS_ASSERT(t != String);
         JS_ASSERT(t != Number);
 #ifdef DEBUG
         lastToken = t;
 #endif
         return t;
     }
 
     Token stringToken(JSString *str) {
-        this->v = js::StringValue(str);
+        this->v = StringValue(str);
 #ifdef DEBUG
         lastToken = String;
 #endif
         return String;
     }
 
     Token numberToken(double d) {
-        this->v = js::NumberValue(d);
+        this->v = NumberValue(d);
 #ifdef DEBUG
         lastToken = Number;
 #endif
         return Number;
     }
 
     enum StringType { PropertyName, LiteralValue };
     template<StringType ST> Token readString();
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -88,17 +88,17 @@ Bindings::initWithTemporaryStorage(JSCon
      * any time, such accesses are mediated by DebugScopeProxy (see
      * DebugScopeProxy::handleUnaliasedAccess).
      */
 
     JS_STATIC_ASSERT(CallObject::RESERVED_SLOTS == 2);
     gc::AllocKind allocKind = gc::FINALIZE_OBJECT2_BACKGROUND;
     JS_ASSERT(gc::GetGCKindSlots(allocKind) == CallObject::RESERVED_SLOTS);
     RootedShape initial(cx,
-        EmptyShape::getInitialShape(cx, &CallClass, NULL, cx->global(),
+        EmptyShape::getInitialShape(cx, &CallClass, NULL, cx->global(), NULL,
                                     allocKind, BaseShape::VAROBJ | BaseShape::DELEGATE));
     if (!initial)
         return false;
     self->callObjShape_.init(initial);
 
 #ifdef DEBUG
     HashSet<PropertyName *> added(cx);
     if (!added.init())
@@ -113,17 +113,17 @@ Bindings::initWithTemporaryStorage(JSCon
 
 #ifdef DEBUG
         /* The caller ensures no duplicate aliased names. */
         JS_ASSERT(!added.has(bi->name()));
         if (!added.put(bi->name()))
             return false;
 #endif
 
-        StackBaseShape base(cx->compartment, &CallClass, cx->global(),
+        StackBaseShape base(cx->compartment, &CallClass, cx->global(), NULL,
                             BaseShape::VAROBJ | BaseShape::DELEGATE);
 
         UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
         if (!nbase)
             return false;
 
         RootedId id(cx, NameToId(bi->name()));
         unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE |
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1688,87 +1688,97 @@ class StringRegExpGuard
 
         return cx->compartment->regExps.get(cx, patstr, opt, &re_);
     }
 
     RegExpShared &regExp() { return *re_; }
 };
 
 static bool
-DoMatchLocal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearStr,
-             RegExpShared &re, MutableHandleValue rval)
+DoMatchLocal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input,
+             RegExpShared &re)
 {
-    size_t charsLen = linearStr->length();
+    size_t charsLen = input->length();
+    const jschar *chars = input->chars();
+
     size_t i = 0;
     ScopedMatchPairs matches(&cx->tempLifoAlloc());
-    RegExpRunStatus status = re.execute(cx, linearStr->getChars(cx), charsLen, &i, matches);
+    RegExpRunStatus status = re.execute(cx, chars, charsLen, &i, matches);
     if (status == RegExpRunStatus_Error)
         return false;
 
-    /* Emulate ExecuteRegExpLegacy() behavior. */
     if (status == RegExpRunStatus_Success_NotFound) {
-        rval.setNull();
+        args.rval().setNull();
         return true;
     }
 
-    res->updateFromMatchPairs(cx, linearStr, matches);
-    return CreateRegExpMatchResult(cx, linearStr, matches, rval);
+    res->updateFromMatchPairs(cx, input, matches);
+
+    RootedValue rval(cx);
+    if (!CreateRegExpMatchResult(cx, input, chars, charsLen, matches, &rval))
+        return false;
+
+    args.rval().set(rval);
+    return true;
 }
 
 static bool
-BuildGlobalMatchArray(JSContext *cx, HandleString matchesInput, const MatchPair &pair, size_t count,
-                      MutableHandleObject array)
+DoMatchGlobal(JSContext *cx, CallArgs args, RegExpStatics *res, Handle<JSLinearString*> input,
+              RegExpShared &re)
 {
-    JS_ASSERT(count <= JSID_INT_MAX);  /* by max string length */
-    JS_ASSERT(pair.check());
-
-    if (!array)
-        array.set(NewDenseEmptyArray(cx));
-    if (!array)
-        return false;
-
-    JSString *str = js_NewDependentString(cx, matchesInput, pair.start, pair.length());
-    if (!str)
-        return false;
-
-    RootedValue v(cx);
-    v.setString(str);
-    return JSObject::defineElement(cx, array, count, v);
-}
-
-static bool
-DoMatchGlobal(JSContext *cx, RegExpStatics *res, Handle<JSLinearString*> linearPtr, RegExpShared &re,
-              MutableHandleObject array)
-{
-    size_t charsLen = linearPtr->length();
-
-    size_t prevMatch = 0;
-    bool isMatchFound = false;
-    for (size_t count = 0, i = 0, curMatch = 0; i <= charsLen; ++count) {
+    size_t charsLen = input->length();
+    const jschar *chars = input->chars();
+
+    AutoValueVector elements(cx);
+
+    MatchPair match;
+    size_t i = 0; /* Index used for iterating through the string. */
+    size_t lastMatch = 0; /* Index of last successful match. */
+
+    /* Accumulate results for each match. */
+    while (i <= charsLen) {
         if (!JS_CHECK_OPERATION_LIMIT(cx))
             return false;
 
-        MatchPair match;
-        RegExpRunStatus status = re.executeMatchOnly(cx, linearPtr->chars(), charsLen, &i, match);
+        size_t i_orig = i;
+
+        RegExpRunStatus status = re.executeMatchOnly(cx, chars, charsLen, &i, match);
         if (status == RegExpRunStatus_Error)
             return false;
-
         if (status == RegExpRunStatus_Success_NotFound)
             break;
 
-        isMatchFound = true;
-        prevMatch = curMatch;
-        curMatch = i;
-        if (!BuildGlobalMatchArray(cx, linearPtr, match, count, array))
+        lastMatch = i_orig;
+
+        /* Extract the matched substring, root it, and remember it for later. */
+        JSLinearString *str = js_NewDependentString(cx, input, match.start, match.length());
+        if (!str)
             return false;
+        if (!elements.append(StringValue(str)))
+            return false;
+
         if (match.isEmpty())
             ++i;
     }
-    if (isMatchFound)
-        res->updateLazily(cx, linearPtr, &re, prevMatch);
+
+    /* If unmatched, return null. */
+    if (elements.empty()) {
+        args.rval().setNull();
+        return true;
+    }
+
+    /* The last successful match updates the RegExpStatics. */
+    res->updateLazily(cx, input, &re, lastMatch);
+
+    /* Copy the rooted vector into the array object. */
+    JSObject *array = NewDenseCopiedArray(cx, elements.length(), elements.begin());
+    if (!array)
+        return false;
+
+    args.rval().setObject(*array);
     return true;
 }
 
 static bool
 BuildFlatMatchArray(JSContext *cx, HandleString textstr, const FlatMatch &fm, CallArgs *args)
 {
     if (fm.match() < 0) {
         args->rval().setNull();
@@ -1817,28 +1827,19 @@ js::str_match(JSContext *cx, unsigned ar
     if (!g.normalizeRegExp(cx, false, 1, args))
         return false;
 
     RegExpStatics *res = cx->regExpStatics();
     Rooted<JSLinearString*> linearStr(cx, str->ensureLinear(cx));
     if (!linearStr)
         return false;
 
-    if (g.regExp().global()) {
-        RootedObject array(cx);
-        if (!DoMatchGlobal(cx, res, linearStr, g.regExp(), &array))
-            return false;
-        args.rval().setObjectOrNull(array);
-    } else {
-        RootedValue rval(cx);
-        if (!DoMatchLocal(cx, res, linearStr, g.regExp(), &rval))
-            return false;
-        args.rval().set(rval);
-    }
-    return true;
+    if (g.regExp().global())
+        return DoMatchGlobal(cx, args, res, linearStr, g.regExp());
+    return DoMatchLocal(cx, args, res, linearStr, g.regExp());
 }
 
 JSBool
 js::str_search(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedString str(cx, ThisToStringForStringProto(cx, args));
     if (!str)
--- a/js/src/jstypedarray.cpp
+++ b/js/src/jstypedarray.cpp
@@ -554,17 +554,17 @@ ArrayBufferObject::create(JSContext *cx,
 
     RootedObject obj(cx, NewBuiltinClassInstance(cx, &ArrayBufferClass));
     if (!obj)
         return NULL;
     JS_ASSERT_IF(obj->isTenured(), obj->tenuredGetAllocKind() == gc::FINALIZE_OBJECT16_BACKGROUND);
     JS_ASSERT(obj->getClass() == &ArrayBufferClass);
 
     js::Shape *empty = EmptyShape::getInitialShape(cx, &ArrayBufferClass,
-                                                   obj->getProto(), obj->getParent(),
+                                                   obj->getProto(), obj->getParent(), obj->getMetadata(),
                                                    gc::FINALIZE_OBJECT16_BACKGROUND);
     if (!empty)
         return NULL;
     obj->setLastPropertyInfallible(empty);
 
     /*
      * The beginning stores an ObjectElements header structure holding the
      * length. The rest of it is a flat data store for the array buffer.
@@ -1780,17 +1780,17 @@ class TypedArrayTemplate
 
         // Mark the object as non-extensible. We cannot simply call
         // obj->preventExtensions() because that has to iterate through all
         // properties, and on long arrays that is much too slow. We could
         // initialize the length fields to zero to avoid that, but then it
         // would just boil down to a slightly slower wrapper around the
         // following code anyway:
         js::Shape *empty = EmptyShape::getInitialShape(cx, fastClass(),
-                                                       obj->getProto(), obj->getParent(),
+                                                       obj->getProto(), obj->getParent(), obj->getMetadata(),
                                                        gc::FINALIZE_OBJECT8_BACKGROUND,
                                                        BaseShape::NOT_EXTENSIBLE);
         if (!empty)
             return NULL;
         obj->setLastPropertyInfallible(empty);
 
 #ifdef DEBUG
         uint32_t bufferByteLength = buffer->byteLength();
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -36,17 +36,16 @@ EXPORTS += [
     'jsapi.h',
     'jsclass.h',
     'jsclist.h',
     'jscpucfg.h',
     'jsdbgapi.h',
     'jsdhash.h',
     'jsfriendapi.h',
     'jslock.h',
-    'json.h',
     'jsperf.h',
     'jsprf.h',
     'jsprototypes.h',
     'jsproxy.h',
     'jsprvtd.h',
     'jspubtd.h',
     'jstypes.h',
     'jsutil.h',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3502,38 +3502,16 @@ NewGlobal(JSContext *cx, unsigned argc, 
     if (!JS_WrapObject(cx, global.address()))
         return false;
 
     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(global));
     return true;
 }
 
 static JSBool
-ParseLegacyJSON(JSContext *cx, unsigned argc, jsval *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (argc != 1 || !JSVAL_IS_STRING(args[0])) {
-        JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS, "parseLegacyJSON");
-        return false;
-    }
-
-    JSString *str = JSVAL_TO_STRING(args[0]);
-
-    size_t length;
-    const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
-    if (!chars)
-        return false;
-
-    RootedValue value(cx, NullValue());
-    return js::ParseJSONWithReviver(cx, StableCharPtr(chars, length), length,
-                                    value, args.rval(), LEGACY);
-}
-
-static JSBool
 EnableStackWalkingAssertion(JSContext *cx, unsigned argc, jsval *vp)
 {
     if (argc == 0 || !JSVAL_IS_BOOLEAN(JS_ARGV(cx, vp)[0])) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_INVALID_ARGS,
                              "enableStackWalkingAssertion");
         return false;
     }
 
@@ -3890,21 +3868,16 @@ static JSFunctionSpecWithHelp shell_func
 "deserialize(a)",
 "  Deserialize data generated by serialize."),
 
     JS_FN_HELP("newGlobal", NewGlobal, 1, 0,
 "newGlobal([obj])",
 "  Return a new global object in a new compartment. If obj\n"
 "  is given, the compartment will be in the same zone as obj."),
 
-    JS_FN_HELP("parseLegacyJSON", ParseLegacyJSON, 1, 0,
-"parseLegacyJSON(str)",
-"  Parse str as legacy JSON, returning the result if the\n"
-"  parse succeeded and throwing a SyntaxError if not."),
-
     JS_FN_HELP("enableStackWalkingAssertion", EnableStackWalkingAssertion, 1, 0,
 "enableStackWalkingAssertion(enabled)",
 "  Enables or disables a particularly expensive assertion in stack-walking\n"
 "  code.  If your test isn't ridiculously thorough, such that performing this\n"
 "  assertion increases test duration by an order of magnitude, you shouldn't\n"
 "  use this."),
 
     JS_FN_HELP("getMaxArgs", GetMaxArgs, 0, 0,
@@ -4599,35 +4572,35 @@ dom_doFoo(JSContext* cx, JSHandleObject 
 
     /* Just return argc. */
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setInt32(argc);
     return true;
 }
 
 const JSJitInfo dom_x_getterinfo = {
-    (JSJitPropertyOp)dom_get_x,
+    { (JSJitGetterOp)dom_get_x },
     0,        /* protoID */
     0,        /* depth */
     JSJitInfo::Getter,
     true,     /* isInfallible. False in setters. */
     true      /* isConstant. Only relevant for getters. */
 };
 
 const JSJitInfo dom_x_setterinfo = {
-    (JSJitPropertyOp)dom_set_x,
+    { (JSJitGetterOp)dom_set_x },
     0,        /* protoID */
     0,        /* depth */
     JSJitInfo::Setter,
     false,    /* isInfallible. False in setters. */
     false     /* isConstant. Only relevant for getters. */
 };
 
 const JSJitInfo doFoo_methodinfo = {
-    (JSJitPropertyOp)dom_doFoo,
+    { (JSJitGetterOp)dom_doFoo },
     0,        /* protoID */
     0,        /* depth */
     JSJitInfo::Method,
     false,    /* isInfallible. False in setters. */
     false     /* isConstant. Only relevant for getters. */
 };
 
 static const JSPropertySpec dom_props[] = {
@@ -4679,17 +4652,17 @@ dom_genericGetter(JSContext *cx, unsigne
         JS_SET_RVAL(cx, vp, UndefinedValue());
         return true;
     }
 
     JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
 
     const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
     MOZ_ASSERT(info->type == JSJitInfo::Getter);
-    JSJitPropertyOp getter = info->op;
+    JSJitGetterOp getter = info->getter;
     return getter(cx, obj, val.toPrivate(), vp);
 }
 
 static JSBool
 dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp)
 {
     RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
     if (!obj)
@@ -4702,17 +4675,17 @@ dom_genericSetter(JSContext* cx, unsigne
         return true;
     }
 
     JS::Value* argv = JS_ARGV(cx, vp);
     JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
 
     const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
     MOZ_ASSERT(info->type == JSJitInfo::Setter);
-    JSJitPropertyOp setter = info->op;
+    JSJitSetterOp setter = info->setter;
     if (!setter(cx, obj, val.toPrivate(), argv))
         return false;
     JS_SET_RVAL(cx, vp, UndefinedValue());
     return true;
 }
 
 static JSBool
 dom_genericMethod(JSContext* cx, unsigned argc, JS::Value *vp)
@@ -4725,17 +4698,17 @@ dom_genericMethod(JSContext* cx, unsigne
         JS_SET_RVAL(cx, vp, UndefinedValue());
         return true;
     }
 
     JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
 
     const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
     MOZ_ASSERT(info->type == JSJitInfo::Method);
-    JSJitMethodOp method = (JSJitMethodOp)info->op;
+    JSJitMethodOp method = info->method;
     return method(cx, obj, val.toPrivate(), argc, vp);
 }
 
 static void
 InitDOMObject(HandleObject obj)
 {
     /* Fow now just initialize to a constant we can check. */
     SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL((void *)0x1234));
deleted file mode 100644
--- a/js/src/tests/ecma_5/extensions/legacy-JSON.js
+++ /dev/null
@@ -1,55 +0,0 @@
-// |reftest| skip-if(!xulRuntime.shell) -- needs parseLegacyJSON
-// Any copyright is dedicated to the Public Domain.
-// http://creativecommons.org/licenses/publicdomain/
-
-try
-{
-  parseLegacyJSON("[,]");
-  throw new Error("didn't throw");
-}
-catch (e)
-{
-  assertEq(e instanceof SyntaxError, true, "didn't get syntax error, got: " + e);
-}
-
-try
-{
-  parseLegacyJSON("{,}");
-  throw new Error("didn't throw");
-}
-catch (e)
-{
-  assertEq(e instanceof SyntaxError, true, "didn't get syntax error, got: " + e);
-}
-
-assertEq(parseLegacyJSON("[1,]").length, 1);
-assertEq(parseLegacyJSON("[1, ]").length, 1);
-assertEq(parseLegacyJSON("[1 , ]").length, 1);
-assertEq(parseLegacyJSON("[1 ,]").length, 1);
-assertEq(parseLegacyJSON("[1,2,]").length, 2);
-assertEq(parseLegacyJSON("[1,2, ]").length, 2);
-assertEq(parseLegacyJSON("[1,2 , ]").length, 2);
-assertEq(parseLegacyJSON("[1,2 ,]").length, 2);
-
-assertEq(parseLegacyJSON('{"a": 2,}').a, 2);
-assertEq(parseLegacyJSON('{"a": 2, }').a, 2);
-assertEq(parseLegacyJSON('{"a": 2 , }').a, 2);
-assertEq(parseLegacyJSON('{"a": 2 ,}').a, 2);
-
-var obj;
-
-obj = parseLegacyJSON('{"a": 2,"b": 3,}');
-assertEq(obj.a + obj.b, 5);
-obj = parseLegacyJSON('{"a": 2,"b": 3, }');
-assertEq(obj.a + obj.b, 5);
-obj = parseLegacyJSON('{"a": 2,"b": 3 , }');
-assertEq(obj.a + obj.b, 5);
-obj = parseLegacyJSON('{"a": 2,"b": 3 ,}');
-assertEq(obj.a + obj.b, 5);
-
-/******************************************************************************/
-
-if (typeof reportCompare === "function")
-  reportCompare(true, true);
-
-print("Tests complete");
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -182,17 +182,17 @@ ArgumentsObject::create(JSContext *cx, H
     bool strict = callee->strict();
     Class *clasp = strict ? &StrictArgumentsObjectClass : &NormalArgumentsObjectClass;
 
     RootedTypeObject type(cx, proto->getNewType(cx, clasp));
     if (!type)
         return NULL;
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(proto),
-                                                      proto->getParent(), FINALIZE_KIND,
+                                                      proto->getParent(), NewObjectMetadata(cx), FINALIZE_KIND,
                                                       BaseShape::INDEXED));
     if (!shape)
         return NULL;
 
     unsigned numFormals = callee->nargs;
     unsigned numDeletedWords = NumWordsForBitArrayOfLength(numActuals);
     unsigned numArgs = Max(numActuals, numFormals);
     unsigned numBytes = offsetof(ArgumentsData, args) +
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -289,17 +289,17 @@ DeclEnvObject *
 DeclEnvObject::createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap)
 {
     RootedTypeObject type(cx, cx->compartment->getNewType(cx, &DeclEnvClass, NULL));
     if (!type)
         return NULL;
 
     RootedShape emptyDeclEnvShape(cx);
     emptyDeclEnvShape = EmptyShape::getInitialShape(cx, &DeclEnvClass, NULL,
-                                                    cx->global(), FINALIZE_KIND,
+                                                    cx->global(), NULL, FINALIZE_KIND,
                                                     BaseShape::DELEGATE);
     if (!emptyDeclEnvShape)
         return NULL;
 
     RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, heap, emptyDeclEnvShape, type));
     if (!obj)
         return NULL;
 
@@ -332,17 +332,17 @@ DeclEnvObject::create(JSContext *cx, Han
 WithObject *
 WithObject::create(JSContext *cx, HandleObject proto, HandleObject enclosing, uint32_t depth)
 {
     RootedTypeObject type(cx, proto->getNewType(cx, &WithClass));
     if (!type)
         return NULL;
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &WithClass, TaggedProto(proto),
-                                                      &enclosing->global(), FINALIZE_KIND));
+                                                      &enclosing->global(), NULL, FINALIZE_KIND));
     if (!shape)
         return NULL;
 
     RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, type));
     if (!obj)
         return NULL;
 
     obj->asScope().setEnclosingScope(enclosing);
@@ -665,17 +665,17 @@ ClonedBlockObject::copyUnaliasedValues(A
 StaticBlockObject *
 StaticBlockObject::create(JSContext *cx)
 {
     RootedTypeObject type(cx, cx->compartment->getNewType(cx, &BlockClass, NULL));
     if (!type)
         return NULL;
 
     RootedShape emptyBlockShape(cx);
-    emptyBlockShape = EmptyShape::getInitialShape(cx, &BlockClass, NULL, NULL, FINALIZE_KIND,
+    emptyBlockShape = EmptyShape::getInitialShape(cx, &BlockClass, NULL, NULL, NULL, FINALIZE_KIND,
                                                   BaseShape::DELEGATE);
     if (!emptyBlockShape)
         return NULL;
 
     JSObject *obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, emptyBlockShape, type);
     if (!obj)
         return NULL;
 
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -44,34 +44,38 @@ static inline void
 GetterSetterWriteBarrierPostRemove(JSRuntime *rt, JSObject **objp)
 {
 #ifdef JSGC_GENERATIONAL
     rt->gcStoreBuffer.removeRelocatableCell(reinterpret_cast<gc::Cell **>(objp));
 #endif
 }
 
 inline
-BaseShape::BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, uint32_t objectFlags)
+BaseShape::BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, JSObject *metadata,
+                     uint32_t objectFlags)
 {
     JS_ASSERT(!(objectFlags & ~OBJECT_FLAG_MASK));
     mozilla::PodZero(this);
     this->clasp = clasp;
     this->parent = parent;
+    this->metadata = metadata;
     this->flags = objectFlags;
     this->compartment_ = comp;
 }
 
 inline
-BaseShape::BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, uint32_t objectFlags,
-                     uint8_t attrs, js::PropertyOp rawGetter, js::StrictPropertyOp rawSetter)
+BaseShape::BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, JSObject *metadata,
+                     uint32_t objectFlags, uint8_t attrs,
+                     PropertyOp rawGetter, StrictPropertyOp rawSetter)
 {
     JS_ASSERT(!(objectFlags & ~OBJECT_FLAG_MASK));
     mozilla::PodZero(this);
     this->clasp = clasp;
     this->parent = parent;
+    this->metadata = metadata;
     this->flags = objectFlags;
     this->rawGetter = rawGetter;
     this->rawSetter = rawSetter;
     if ((attrs & JSPROP_GETTER) && rawGetter) {
         this->flags |= HAS_GETTER_OBJECT;
         GetterSetterWriteBarrierPost(runtime(), &this->getterObj);
     }
     if ((attrs & JSPROP_SETTER) && rawSetter) {
@@ -82,31 +86,33 @@ BaseShape::BaseShape(JSCompartment *comp
 }
 
 inline
 BaseShape::BaseShape(const StackBaseShape &base)
 {
     mozilla::PodZero(this);
     this->clasp = base.clasp;
     this->parent = base.parent;
+    this->metadata = base.metadata;
     this->flags = base.flags;
     this->rawGetter = base.rawGetter;
     this->rawSetter = base.rawSetter;
     if ((base.flags & HAS_GETTER_OBJECT) && base.rawGetter)
         GetterSetterWriteBarrierPost(runtime(), &this->getterObj);
     if ((base.flags & HAS_SETTER_OBJECT) && base.rawSetter)
         GetterSetterWriteBarrierPost(runtime(), &this->setterObj);
     this->compartment_ = base.compartment;
 }
 
 inline BaseShape &
 BaseShape::operator=(const BaseShape &other)
 {
     clasp = other.clasp;
     parent = other.parent;
+    metadata = other.metadata;
     flags = other.flags;
     slotSpan_ = other.slotSpan_;
     if (flags & HAS_GETTER_OBJECT) {
         getterObj = other.getterObj;
         GetterSetterWriteBarrierPost(runtime(), &getterObj);
     } else {
         if (rawGetter)
             GetterSetterWriteBarrierPostRemove(runtime(), &getterObj);
@@ -130,16 +136,17 @@ BaseShape::matchesGetterSetter(PropertyO
     return rawGetter == this->rawGetter && rawSetter == this->rawSetter;
 }
 
 inline
 StackBaseShape::StackBaseShape(Shape *shape)
   : flags(shape->getObjectFlags()),
     clasp(shape->getObjectClass()),
     parent(shape->getObjectParent()),
+    metadata(shape->getObjectMetadata()),
     compartment(shape->compartment())
 {
     updateGetterSetter(shape->attrs, shape->getter(), shape->setter());
 }
 
 inline void
 StackBaseShape::updateGetterSetter(uint8_t attrs,
                                    PropertyOp rawGetter,
@@ -192,16 +199,17 @@ BaseShape::assertConsistency()
 #ifdef DEBUG
     if (isOwned()) {
         UnownedBaseShape *unowned = baseUnowned();
         JS_ASSERT(hasGetterObject() == unowned->hasGetterObject());
         JS_ASSERT(hasSetterObject() == unowned->hasSetterObject());
         JS_ASSERT_IF(hasGetterObject(), getterObject() == unowned->getterObject());
         JS_ASSERT_IF(hasSetterObject(), setterObject() == unowned->setterObject());
         JS_ASSERT(getObjectParent() == unowned->getObjectParent());
+        JS_ASSERT(getObjectMetadata() == unowned->getObjectMetadata());
         JS_ASSERT(getObjectFlags() == unowned->getObjectFlags());
     }
 #endif
 }
 
 inline
 Shape::Shape(const StackShape &other, uint32_t nfixed)
   : base_(other.base),
@@ -490,16 +498,19 @@ BaseShape::markChildren(JSTracer *trc)
     if (hasSetterObject())
         MarkObjectUnbarriered(trc, &setterObj, "setter");
 
     if (isOwned())
         MarkBaseShape(trc, &unowned_, "base");
 
     if (parent)
         MarkObject(trc, &parent, "parent");
+
+    if (metadata)
+        MarkObject(trc, &metadata, "metadata");
 }
 
 /*
  * Property lookup hooks on objects are required to return a non-NULL shape to
  * signify that the property has been found. For cases where the property is
  * not actually represented by a Shape, use a dummy value. This includes all
  * properties of non-native objects, and dense elements for native objects.
  * Use separate APIs for these two cases.
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -293,17 +293,17 @@ Shape::replaceLastProperty(JSContext *cx
                            TaggedProto proto, HandleShape shape)
 {
     JS_ASSERT(!shape->inDictionary());
 
     if (!shape->parent) {
         /* Treat as resetting the initial property of the shape hierarchy. */
         AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
         return EmptyShape::getInitialShape(cx, base.clasp, proto,
-                                           base.parent, kind,
+                                           base.parent, base.metadata, kind,
                                            base.flags & BaseShape::OBJECT_FLAG_MASK);
     }
 
     StackShape child(shape);
     StackShape::AutoRooter childRoot(cx, &child);
     {
         UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
         if (!nbase)
@@ -1046,16 +1046,51 @@ Shape::setObjectParent(JSContext *cx, JS
     StackBaseShape base(last);
     base.parent = parent;
 
     RootedShape lastRoot(cx, last);
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
 /* static */ bool
+JSObject::setMetadata(JSContext *cx, HandleObject obj, HandleObject metadata)
+{
+    if (obj->inDictionaryMode()) {
+        StackBaseShape base(obj->lastProperty());
+        base.metadata = metadata;
+        UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
+        if (!nbase)
+            return false;
+
+        obj->lastProperty()->base()->adoptUnowned(nbase);
+        return true;
+    }
+
+    Shape *newShape = Shape::setObjectMetadata(cx, metadata, obj->getTaggedProto(), obj->shape_);
+    if (!newShape)
+        return false;
+
+    obj->shape_ = newShape;
+    return true;
+}
+
+/* static */ Shape *
+Shape::setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last)
+{
+    if (last->getObjectMetadata() == metadata)
+        return last;
+
+    StackBaseShape base(last);
+    base.metadata = metadata;
+
+    RootedShape lastRoot(cx, last);
+    return replaceLastProperty(cx, base, proto, lastRoot);
+}
+
+/* static */ bool
 js::ObjectImpl::preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj)
 {
     MOZ_ASSERT(obj->isExtensible(),
                "Callers must ensure |obj| is extensible before calling "
                "preventExtensions");
 
     if (obj->isProxy()) {
         RootedObject object(cx, obj->asObjectPtr());
@@ -1149,38 +1184,44 @@ Shape::setObjectFlag(JSContext *cx, Base
 }
 
 /* static */ inline HashNumber
 StackBaseShape::hash(const StackBaseShape *base)
 {
     HashNumber hash = base->flags;
     hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(base->clasp) >> 3);
     hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(base->parent) >> 3);
+    hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(base->metadata) >> 3);
     hash = JS_ROTATE_LEFT32(hash, 4) ^ uintptr_t(base->rawGetter);
     hash = JS_ROTATE_LEFT32(hash, 4) ^ uintptr_t(base->rawSetter);
     return hash;
 }
 
 /* static */ inline bool
 StackBaseShape::match(UnownedBaseShape *key, const StackBaseShape *lookup)
 {
     return key->flags == lookup->flags
         && key->clasp == lookup->clasp
         && key->parent == lookup->parent
+        && key->metadata == lookup->metadata
         && key->rawGetter == lookup->rawGetter
         && key->rawSetter == lookup->rawSetter;
 }
 
 void
 StackBaseShape::AutoRooter::trace(JSTracer *trc)
 {
     if (base->parent) {
         gc::MarkObjectRoot(trc, (JSObject**)&base->parent,
                            "StackBaseShape::AutoRooter parent");
     }
+    if (base->metadata) {
+        gc::MarkObjectRoot(trc, (JSObject**)&base->metadata,
+                           "StackBaseShape::AutoRooter metadata");
+    }
     if ((base->flags & BaseShape::HAS_GETTER_OBJECT) && base->rawGetter) {
         gc::MarkObjectRoot(trc, (JSObject**)&base->rawGetter,
                            "StackBaseShape::AutoRooter getter");
     }
     if ((base->flags & BaseShape::HAS_SETTER_OBJECT) && base->rawSetter) {
         gc::MarkObjectRoot(trc, (JSObject**)&base->rawSetter,
                            "StackBaseShape::AutoRooter setter");
     }
@@ -1247,86 +1288,90 @@ inline
 InitialShapeEntry::InitialShapeEntry(const ReadBarriered<Shape> &shape, TaggedProto proto)
   : shape(shape), proto(proto)
 {
 }
 
 inline InitialShapeEntry::Lookup
 InitialShapeEntry::getLookup() const
 {
-    return Lookup(shape->getObjectClass(), proto, shape->getObjectParent(),
+    return Lookup(shape->getObjectClass(), proto, shape->getObjectParent(), shape->getObjectMetadata(),
                   shape->numFixedSlots(), shape->getObjectFlags());
 }
 
 /* static */ inline HashNumber
 InitialShapeEntry::hash(const Lookup &lookup)
 {
     HashNumber hash = uintptr_t(lookup.clasp) >> 3;
     hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(lookup.proto.toWord()) >> 3);
-    hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(lookup.parent) >> 3);
+    hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(lookup.parent) >> 3) ^ (uintptr_t(lookup.metadata) >> 3);
     return hash + lookup.nfixed;
 }
 
 /* static */ inline bool
 InitialShapeEntry::match(const InitialShapeEntry &key, const Lookup &lookup)
 {
     const Shape *shape = *key.shape.unsafeGet();
     return lookup.clasp == shape->getObjectClass()
         && lookup.proto.toWord() == key.proto.toWord()
         && lookup.parent == shape->getObjectParent()
+        && lookup.metadata == shape->getObjectMetadata()
         && lookup.nfixed == shape->numFixedSlots()
         && lookup.baseFlags == shape->getObjectFlags();
 }
 
 /* static */ Shape *
-EmptyShape::getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto, JSObject *parent,
+EmptyShape::getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto,
+                            JSObject *parent, JSObject *metadata,
                             size_t nfixed, uint32_t objectFlags)
 {
     JS_ASSERT_IF(proto.isObject(), cx->compartment == proto.toObject()->compartment());
     JS_ASSERT_IF(parent, cx->compartment == parent->compartment());
 
     InitialShapeSet &table = cx->compartment->initialShapes;
 
     if (!table.initialized() && !table.init())
         return NULL;
 
     typedef InitialShapeEntry::Lookup Lookup;
     InitialShapeSet::AddPtr p =
-        table.lookupForAdd(Lookup(clasp, proto, parent, nfixed, objectFlags));
+        table.lookupForAdd(Lookup(clasp, proto, parent, metadata, nfixed, objectFlags));
 
     if (p)
         return p->shape;
 
     Rooted<TaggedProto> protoRoot(cx, proto);
     RootedObject parentRoot(cx, parent);
+    RootedObject metadataRoot(cx, metadata);
 
-    StackBaseShape base(cx->compartment, clasp, parent, objectFlags);
+    StackBaseShape base(cx->compartment, clasp, parent, metadata, objectFlags);
     Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base));
     if (!nbase)
         return NULL;
 
     Shape *shape = cx->propertyTree().newShape(cx);
     if (!shape)
         return NULL;
     new (shape) EmptyShape(nbase, nfixed);
 
-    if (!table.relookupOrAdd(p, Lookup(clasp, protoRoot, parentRoot, nfixed, objectFlags),
+    if (!table.relookupOrAdd(p, Lookup(clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags),
                              InitialShapeEntry(shape, protoRoot)))
     {
         return NULL;
     }
 
     return shape;
 }
 
 /* static */ Shape *
-EmptyShape::getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto, JSObject *parent,
+EmptyShape::getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto,
+                            JSObject *parent, JSObject *metadata,
                             AllocKind kind, uint32_t objectFlags)
 {
-    return getInitialShape(cx, clasp, proto, parent, GetGCKindSlots(kind, clasp), objectFlags);
+    return getInitialShape(cx, clasp, proto, parent, metadata, GetGCKindSlots(kind, clasp), objectFlags);
 }
 
 void
 NewObjectCache::invalidateEntriesForShape(JSContext *cx, HandleShape shape, HandleObject proto)
 {
     Class *clasp = shape->getObjectClass();
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
@@ -1344,18 +1389,18 @@ NewObjectCache::invalidateEntriesForShap
     if (lookupType(clasp, type, kind, &entry))
         PodZero(&entries[entry]);
 }
 
 /* static */ void
 EmptyShape::insertInitialShape(JSContext *cx, HandleShape shape, HandleObject proto)
 {
     InitialShapeEntry::Lookup lookup(shape->getObjectClass(), TaggedProto(proto),
-                                     shape->getObjectParent(), shape->numFixedSlots(),
-                                     shape->getObjectFlags());
+                                     shape->getObjectParent(), shape->getObjectMetadata(),
+                                     shape->numFixedSlots(), shape->getObjectFlags());
 
     InitialShapeSet::Ptr p = cx->compartment->initialShapes.lookup(lookup);
     JS_ASSERT(p);
 
     InitialShapeEntry &entry = const_cast<InitialShapeEntry &>(*p);
     JS_ASSERT(entry.shape->isEmptyShape());
 
     /* The new shape had better be rooted at the old one. */
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -261,16 +261,18 @@ class BaseShape : public js::gc::Cell
         HAD_ELEMENTS_ACCESS = 0x1000,
 
         OBJECT_FLAG_MASK    = 0x1ff8
     };
 
   private:
     Class               *clasp;         /* Class of referring object. */
     HeapPtrObject       parent;         /* Parent of referring object. */
+    HeapPtrObject       metadata;       /* Optional holder of metadata about
+                                         * the referring object. */
     JSCompartment       *compartment_;  /* Compartment shape belongs to. */
     uint32_t            flags;          /* Vector of above flags. */
     uint32_t            slotSpan_;      /* Object slot span for BaseShapes at
                                          * dictionary last properties. */
 
     union {
         PropertyOp      rawGetter;      /* getter hook for shape */
         JSObject        *getterObj;     /* user-defined callable "get" object or
@@ -284,44 +286,43 @@ class BaseShape : public js::gc::Cell
     };
 
     /* For owned BaseShapes, the canonical unowned BaseShape. */
     HeapPtr<UnownedBaseShape> unowned_;
 
     /* For owned BaseShapes, the shape's shape table. */
     ShapeTable       *table_;
 
-#if JS_BITS_PER_WORD == 32
-    void             *padding;
-#endif
-
     BaseShape(const BaseShape &base) MOZ_DELETE;
 
   public:
     void finalize(FreeOp *fop);
 
-    inline BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, uint32_t objectFlags);
-    inline BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, uint32_t objectFlags,
-                     uint8_t attrs, PropertyOp rawGetter, StrictPropertyOp rawSetter);
+    inline BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, JSObject *metadata,
+                     uint32_t objectFlags);
+    inline BaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, JSObject *metadata,
+                     uint32_t objectFlags, uint8_t attrs,
+                     PropertyOp rawGetter, StrictPropertyOp rawSetter);
     inline BaseShape(const StackBaseShape &base);
 
     /* Not defined: BaseShapes must not be stack allocated. */
     ~BaseShape();
 
     inline BaseShape &operator=(const BaseShape &other);
 
     bool isOwned() const { return !!(flags & OWNED_SHAPE); }
 
     inline bool matchesGetterSetter(PropertyOp rawGetter,
                                     StrictPropertyOp rawSetter) const;
 
     inline void adoptUnowned(UnownedBaseShape *other);
     inline void setOwned(UnownedBaseShape *unowned);
 
     JSObject *getObjectParent() const { return parent; }
+    JSObject *getObjectMetadata() const { return metadata; }
     uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; }
 
     bool hasGetterObject() const { return !!(flags & HAS_GETTER_OBJECT); }
     JSObject *getterObject() const { JS_ASSERT(hasGetterObject()); return getterObj; }
 
     bool hasSetterObject() const { return !!(flags & HAS_SETTER_OBJECT); }
     JSObject *setterObject() const { JS_ASSERT(hasSetterObject()); return setterObj; }
 
@@ -391,33 +392,37 @@ BaseShape::baseUnowned()
 /* Entries for the per-compartment baseShapes set of unowned base shapes. */
 struct StackBaseShape
 {
     typedef const StackBaseShape *Lookup;
 
     uint32_t flags;
     Class *clasp;
     JSObject *parent;
+    JSObject *metadata;
     PropertyOp rawGetter;
     StrictPropertyOp rawSetter;
     JSCompartment *compartment;
 
     explicit StackBaseShape(BaseShape *base)
       : flags(base->flags & BaseShape::OBJECT_FLAG_MASK),
         clasp(base->clasp),
         parent(base->parent),
+        metadata(base->metadata),
         rawGetter(NULL),
         rawSetter(NULL),
         compartment(base->compartment())
     {}
 
-    StackBaseShape(JSCompartment *comp, Class *clasp, JSObject *parent, uint32_t objectFlags)
+    StackBaseShape(JSCompartment *comp, Class *clasp,
+                   JSObject *parent, JSObject *metadata, uint32_t objectFlags)
       : flags(objectFlags),
         clasp(clasp),
         parent(parent),
+        metadata(metadata),
         rawGetter(NULL),
         rawSetter(NULL),
         compartment(comp)
     {}
 
     inline StackBaseShape(Shape *shape);
 
     inline void updateGetterSetter(uint8_t attrs,
@@ -586,18 +591,20 @@ class Shape : public js::gc::Cell
         void popFront() {
             JS_ASSERT(!empty());
             cursor = cursor->parent;
         }
     };
 
     Class *getObjectClass() const { return base()->clasp; }
     JSObject *getObjectParent() const { return base()->parent; }
+    JSObject *getObjectMetadata() const { return base()->metadata; }
 
     static Shape *setObjectParent(JSContext *cx, JSObject *obj, TaggedProto proto, Shape *last);
+    static Shape *setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last);
     static Shape *setObjectFlag(JSContext *cx, BaseShape::Flag flag, TaggedProto proto, Shape *last);
 
     uint32_t getObjectFlags() const { return base()->getObjectFlags(); }
     bool hasObjectFlag(BaseShape::Flag flag) const {
         JS_ASSERT(!(flag & ~BaseShape::OBJECT_FLAG_MASK));
         return !!(base()->flags & flag);
     }
 
@@ -894,19 +901,19 @@ class AutoRooterGetterSetter
 struct EmptyShape : public js::Shape
 {
     EmptyShape(UnownedBaseShape *base, uint32_t nfixed);
 
     /*
      * Lookup an initial shape matching the given parameters, creating an empty
      * shape if none was found.
      */
-    static Shape *getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto,
+    static Shape *getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto, JSObject *metadata,
                                   JSObject *parent, size_t nfixed, uint32_t objectFlags = 0);
-    static Shape *getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto,
+    static Shape *getInitialShape(JSContext *cx, Class *clasp, TaggedProto proto, JSObject *metadata,
                                   JSObject *parent, gc::AllocKind kind, uint32_t objectFlags = 0);
 
     /*
      * Reinsert an alternate initial shape, to be returned by future
      * getInitialShape calls, until the new shape becomes unreachable in a GC
      * and the table entry is purged.
      */
     static void insertInitialShape(JSContext *cx, HandleShape shape, HandleObject proto);
@@ -931,22 +938,23 @@ struct InitialShapeEntry
      */
     TaggedProto proto;
 
     /* State used to determine a match on an initial shape. */
     struct Lookup {
         Class *clasp;
         TaggedProto proto;
         JSObject *parent;
+        JSObject *metadata;
         uint32_t nfixed;
         uint32_t baseFlags;
-        Lookup(Class *clasp, TaggedProto proto, JSObject *parent, uint32_t nfixed,
-               uint32_t baseFlags)
-            : clasp(clasp), proto(proto), parent(parent),
-              nfixed(nfixed), baseFlags(baseFlags)
+        Lookup(Class *clasp, TaggedProto proto, JSObject *parent, JSObject *metadata,
+               uint32_t nfixed, uint32_t baseFlags)
+          : clasp(clasp), proto(proto), parent(parent), metadata(metadata),
+            nfixed(nfixed), baseFlags(baseFlags)
         {}
     };
 
     inline InitialShapeEntry();
     inline InitialShapeEntry(const ReadBarriered<Shape> &shape, TaggedProto proto);
 
     inline Lookup getLookup() const;
 
new file mode 100644
--- /dev/null
+++ b/layout/base/Units.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 20; 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 MOZ_UNITS_H_
+#define MOZ_UNITS_H_
+
+#include "mozilla/gfx/Point.h"
+#include "nsDeviceContext.h"
+
+namespace mozilla {
+
+// The pixels that content authors use to specify sizes in.
+struct CSSPixel {
+  static gfx::PointTyped<CSSPixel> FromAppUnits(const nsPoint &pt) {
+    return gfx::PointTyped<CSSPixel>(NSAppUnitsToFloatPixels(pt.x, float(nsDeviceContext::AppUnitsPerCSSPixel())),
+                                     NSAppUnitsToFloatPixels(pt.y, float(nsDeviceContext::AppUnitsPerCSSPixel())));
+  }
+
+  static nsPoint ToAppUnits(const gfx::PointTyped<CSSPixel> &pt) {
+    return nsPoint(NSFloatPixelsToAppUnits(pt.x, float(nsDeviceContext::AppUnitsPerCSSPixel())),
+                   NSFloatPixelsToAppUnits(pt.y, float(nsDeviceContext::AppUnitsPerCSSPixel())));
+  }
+};
+
+typedef gfx::PointTyped<CSSPixel> CSSPoint;
+
+};
+
+#endif
--- a/layout/base/moz.build
+++ b/layout/base/moz.build
@@ -37,16 +37,17 @@ XPIDL_MODULE = 'layout_base'
 MODULE = 'layout'
 
 EXPORTS += [
     'DisplayItemClip.h',
     'DisplayListClipState.h',
     'FrameLayerBuilder.h',
     'FramePropertyTable.h',
     'StackArena.h',
+    'Units.h',
     'nsArenaMemoryStats.h',
     'nsBidi.h',
     'nsBidiPresUtils.h',
     'nsCSSFrameConstructor.h',
     'nsCaret.h',
     'nsChangeHint.h',
     'nsCompatibility.h',
     'nsDisplayItemTypes.h',
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -4683,16 +4683,28 @@ nsImageRenderer::Draw(nsPresContext*    
 bool
 nsImageRenderer::IsRasterImage()
 {
   if (mType != eStyleImageType_Image || !mImageContainer)
     return false;
   return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
 }
 
+bool
+nsImageRenderer::IsAnimatedImage()
+{
+  if (mType != eStyleImageType_Image || !mImageContainer)
+    return false;
+  bool animated = false;
+  if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
+    return true;
+
+  return false;
+}
+
 already_AddRefed<mozilla::layers::ImageContainer>
 nsImageRenderer::GetContainer(LayerManager* aManager)
 {
   if (mType != eStyleImageType_Image || !mImageContainer)
     return nullptr;
 
   nsRefPtr<ImageContainer> container;
   nsresult rv = mImageContainer->GetImageContainer(aManager, getter_AddRefs(container));
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -58,16 +58,17 @@ public:
   void Draw(nsPresContext*       aPresContext,
             nsRenderingContext& aRenderingContext,
             const nsRect&        aDest,
             const nsRect&        aFill,
             const nsPoint&       aAnchor,
             const nsRect&        aDirty);
 
   bool IsRasterImage();
+  bool IsAnimatedImage();
   already_AddRefed<ImageContainer> GetContainer(LayerManager* aManager);
 
 private:
   /*
    * Compute the "unscaled" dimensions of the image in aUnscaled{Width,Height}
    * and aRatio.  Whether the image has a height and width are indicated by
    * aHaveWidth and aHaveHeight.  If the image doesn't have a ratio, aRatio will
    * be (0, 0).
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -613,17 +613,16 @@ static void RecordFrameMetrics(nsIFrame*
                                const nsRect& aViewport,
                                nsRect* aDisplayPort,
                                nsRect* aCriticalDisplayPort,
                                ViewID aScrollId,
                                const nsDisplayItem::ContainerParameters& aContainerParameters,
                                bool aMayHaveTouchListeners) {
   nsPresContext* presContext = aForFrame->PresContext();
   int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
-  float auPerCSSPixel = nsPresContext::AppUnitsPerCSSPixel();
 
   nsIntRect visible = aVisibleRect.ScaleToNearestPixels(
     aContainerParameters.mXScale, aContainerParameters.mYScale, auPerDevPixel);
   aRoot->SetVisibleRegion(nsIntRegion(visible));
 
   FrameMetrics metrics;
 
   metrics.mViewport = mozilla::gfx::Rect(
@@ -659,19 +658,17 @@ static void RecordFrameMetrics(nsIFrame*
     metrics.mScrollableRect =
       mozilla::gfx::Rect(nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.x),
                          nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.y),
                          nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.width),
                          nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.height));
     metrics.mContentRect = contentBounds.ScaleToNearestPixels(
       aContainerParameters.mXScale, aContainerParameters.mYScale, auPerDevPixel);
     nsPoint scrollPosition = scrollableFrame->GetScrollPosition();
-    metrics.mScrollOffset = mozilla::gfx::Point(
-      NSAppUnitsToDoublePixels(scrollPosition.x, auPerCSSPixel),
-      NSAppUnitsToDoublePixels(scrollPosition.y, auPerCSSPixel));
+    metrics.mScrollOffset = CSSPoint::FromAppUnits(scrollPosition);
   }
   else {
     nsRect contentBounds = aForFrame->GetRect();
     metrics.mScrollableRect =
       mozilla::gfx::Rect(nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.x),
                          nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.y),
                          nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.width),
                          nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.height));
@@ -682,17 +679,18 @@ static void RecordFrameMetrics(nsIFrame*
   metrics.mScrollId = aScrollId;
 
   nsIPresShell* presShell = presContext->GetPresShell();
   if (TabChild *tc = GetTabChildFrom(presShell)) {
     metrics.mZoom = tc->GetZoom();
   }
   metrics.mResolution = gfxSize(presShell->GetXResolution(), presShell->GetYResolution());
 
-  metrics.mDevPixelsPerCSSPixel = auPerCSSPixel / auPerDevPixel;
+  metrics.mDevPixelsPerCSSPixel =
+    (float)nsPresContext::AppUnitsPerCSSPixel() / auPerDevPixel;
 
   metrics.mMayHaveTouchListeners = aMayHaveTouchListeners;
 
   if (nsIWidget* widget = aForFrame->GetNearestWidget()) {
     widget->GetBounds(metrics.mCompositionBounds);
   }
 
   metrics.mPresShellId = presShell->GetPresShellId();
@@ -1556,16 +1554,17 @@ nsDisplayBackgroundImage::nsDisplayBackg
                                                    uint32_t aLayer,
                                                    bool aIsThemed,
                                                    const nsStyleBackground* aBackgroundStyle)
   : nsDisplayImageContainer(aBuilder, aFrame)
   , mBackgroundStyle(aBackgroundStyle)
   , mLayer(aLayer)
   , mIsThemed(aIsThemed)
   , mIsBottommostLayer(true)
+  , mIsAnimated(false)
 {
   MOZ_COUNT_CTOR(nsDisplayBackgroundImage);
 
   if (mIsThemed) {
     const nsStyleDisplay* disp = mFrame->StyleDisplay();
     mFrame->IsThemed(disp, &mThemeTransparency);
     // Perform necessary RegisterThemeGeometry
     if (disp->mAppearance == NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR ||
@@ -1817,16 +1816,17 @@ nsDisplayBackgroundImage::TryOptimizeToI
   // Sub-pixel alignment is hard, lets punt on that.
   if (state.mAnchor != nsPoint(0.0f, 0.0f)) {
     return false;
   }
 
   int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
   mDestRect = nsLayoutUtils::RectToGfxRect(state.mDestArea, appUnitsPerDevPixel);
   mImageContainer = imageContainer;
+  mIsAnimated = imageRenderer->IsAnimatedImage();
 
   // Ok, we can turn this into a layer if needed.
   return true;
 }
 
 already_AddRefed<ImageContainer>
 nsDisplayBackgroundImage::GetContainer(LayerManager* aManager,
                                        nsDisplayListBuilder *aBuilder)
@@ -1840,41 +1840,46 @@ nsDisplayBackgroundImage::GetContainer(L
   return container.forget();
 }
 
 LayerState
 nsDisplayBackgroundImage::GetLayerState(nsDisplayListBuilder* aBuilder,
                                         LayerManager* aManager,
                                         const FrameLayerBuilder::ContainerParameters& aParameters)
 {
-  if (!aManager->IsCompositingCheap() ||
-      !nsLayoutUtils::GPUImageScalingEnabled() ||
-      !TryOptimizeToImageLayer(aManager, aBuilder)) {
-    return LAYER_NONE;
+  if (!TryOptimizeToImageLayer(aManager, aBuilder) ||
+      !nsLayoutUtils::AnimatedImageLayersEnabled() ||
+      !mIsAnimated) {
+    if (!aManager->IsCompositingCheap() ||
+        !nsLayoutUtils::GPUImageScalingEnabled()) {
+      return LAYER_NONE;
+    }
   }
 
-  gfxSize imageSize = mImageContainer->GetCurrentSize();
-  NS_ASSERTION(imageSize.width != 0 && imageSize.height != 0, "Invalid image size!");
-
-  gfxRect destRect = mDestRect;
-
-  destRect.width *= aParameters.mXScale;
-  destRect.height *= aParameters.mYScale;
-
-  // Calculate the scaling factor for the frame.
-  gfxSize scale = gfxSize(destRect.width / imageSize.width, destRect.height / imageSize.height);
-
-  // If we are not scaling at all, no point in separating this into a layer.
-  if (scale.width == 1.0f && scale.height == 1.0f) {
-    return LAYER_NONE;
-  }
-
-  // If the target size is pretty small, no point in using a layer.
-  if (destRect.width * destRect.height < 64 * 64) {
-    return LAYER_NONE;
+  if (!mIsAnimated) {
+    gfxSize imageSize = mImageContainer->GetCurrentSize();
+    NS_ASSERTION(imageSize.width != 0 && imageSize.height != 0, "Invalid image size!");
+
+    gfxRect destRect = mDestRect;
+
+    destRect.width *= aParameters.mXScale;
+    destRect.height *= aParameters.mYScale;
+
+    // Calculate the scaling factor for the frame.
+    gfxSize scale = gfxSize(destRect.width / imageSize.width, destRect.height / imageSize.height);
+
+    // If we are not scaling at all, no point in separating this into a layer.
+    if (scale.width == 1.0f && scale.height == 1.0f) {
+      return LAYER_NONE;
+    }
+
+    // If the target size is pretty small, no point in using a layer.
+    if (destRect.width * destRect.height < 64 * 64) {
+      return LAYER_NONE;
+    }
   }
 
   return LAYER_ACTIVE;
 }
 
 already_AddRefed<Layer>
 nsDisplayBackgroundImage::BuildLayer(nsDisplayListBuilder* aBuilder,
                                      LayerManager* aManager,
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -2025,16 +2025,18 @@ protected:
   nsRect mBounds;
   uint32_t mLayer;
 
   nsITheme::Transparency mThemeTransparency;
   /* Used to cache mFrame->IsThemed() since it isn't a cheap call */
   bool mIsThemed;
   /* true if this item represents the bottom-most background layer */
   bool mIsBottommostLayer;
+  /* true if this image is known to be animated */
+  bool mIsAnimated;
 };
 
 class nsDisplayBackgroundColor : public nsDisplayItem
 {
 public:
   nsDisplayBackgroundColor(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                            const nsStyleBackground* aBackgroundStyle,
                            nscolor aColor)
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -377,16 +377,32 @@ nsLayoutUtils::GPUImageScalingEnabled()
     sGPUImageScalingPrefInitialised = true;
     sGPUImageScalingEnabled =
       Preferences::GetBool("layout.gpu-image-scaling.enabled", false);
   }
 
   return sGPUImageScalingEnabled;
 }
 
+bool
+nsLayoutUtils::AnimatedImageLayersEnabled()
+{
+  static bool sAnimatedImageLayersEnabled;
+  static bool sAnimatedImageLayersPrefCached = false;
+
+  if (!sAnimatedImageLayersPrefCached) {
+    sAnimatedImageLayersPrefCached = true;
+    Preferences::AddBoolVarCache(&sAnimatedImageLayersEnabled,
+                                 "layout.animated-image-layers.enabled",
+                                 false);
+  }
+
+  return sAnimatedImageLayersEnabled;
+}
+
 void
 nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame,
                                   nsOverflowAreas& aOverflowAreas)
 {
   // Iterate over all children except pop-ups.
   const nsIFrame::ChildListIDs skip(nsIFrame::kPopupList |
                                     nsIFrame::kSelectPopupList);
   for (nsIFrame::ChildListIterator childLists(aFrame);
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1609,16 +1609,21 @@ public:
 
   /**
    * Checks whether we want to use the GPU to scale images when
    * possible.
    */
   static bool GPUImageScalingEnabled();
 
   /**
+   * Checks whether we want to layerize animated images whenever possible.
+   */
+  static bool AnimatedImageLayersEnabled();
+
+  /**
    * Unions the overflow areas of all non-popup children of aFrame with
    * aOverflowAreas.
    */
   static void UnionChildOverflow(nsIFrame* aFrame,
                                  nsOverflowAreas& aOverflowAreas);
 
   /**
    * Return whether this is a frame whose width is used when computing
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/730559.html
@@ -0,0 +1,1 @@
+<!DOCTYPE html><html style="height: 6523790304542em; width: 6207636626031em; -moz-box-sizing: border-box; border-style: dotted; -moz-column-width: 20px;"></html>
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -442,16 +442,17 @@ load 688996-1.html
 load 688996-2.html
 load 683712.html
 load 691210.html
 load text-overflow-bug713610.html
 load 700031.xhtml
 load 718516.html
 load 723108.html
 load 724978.xhtml
+load 730559.html
 load first-letter-638937.html
 load first-letter-638937-2.html
 load 734777.html
 test-pref(layout.css.flexbox.enabled,true) load 737313-1.html
 test-pref(layout.css.flexbox.enabled,true) load 737313-2.html
 test-pref(layout.css.flexbox.enabled,true) load 737313-3.html
 load 747688.html
 load 750066.html
--- a/layout/generic/nsColumnSetFrame.cpp
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -223,17 +223,19 @@ nsColumnSetFrame::ChooseColumnStrategy(c
     // Reduce column count if necessary to make columns fit in the
     // available width. Compute max number of columns that fit in
     // availContentWidth, satisfying colGap*(maxColumns - 1) +
     // colWidth*maxColumns <= availContentWidth
     if (availContentWidth != NS_INTRINSICSIZE && colGap + colWidth > 0
         && numColumns > 0) {
       // This expression uses truncated rounding, which is what we
       // want
-      int32_t maxColumns = (availContentWidth + colGap)/(colGap + colWidth);
+      int32_t maxColumns =
+        std::min(nscoord(nsStyleColumn::kMaxColumnCount),
+                 (availContentWidth + colGap)/(colGap + colWidth));
       numColumns = std::max(1, std::min(numColumns, maxColumns));
     }
   } else if (numColumns > 0 && availContentWidth != NS_INTRINSICSIZE) {
     nscoord widthMinusGaps = availContentWidth - colGap*(numColumns - 1);
     colWidth = widthMinusGaps/numColumns;
   } else {
     colWidth = NS_INTRINSICSIZE;
   }
@@ -249,16 +251,19 @@ nsColumnSetFrame::ChooseColumnStrategy(c
     // First, determine how many columns will be showing if the column
     // count is auto
     if (numColumns <= 0) {
       // choose so that colGap*(nominalColumnCount - 1) +
       // colWidth*nominalColumnCount is nearly availContentWidth
       // make sure to round down
       if (colGap + colWidth > 0) {
         numColumns = (availContentWidth + colGap)/(colGap + colWidth);
+        // The number of columns should never exceed kMaxColumnCount.
+        numColumns = std::min(nscoord(nsStyleColumn::kMaxColumnCount),
+                              numColumns);
       }
       if (numColumns <= 0) {
         numColumns = 1;
       }
     }
 
     // Compute extra space and divide it among the columns
     nscoord extraSpace =
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1604,20 +1604,19 @@ nsGfxScrollFrameInner::ScrollToCSSPixels
     pt.y = current.y;
     range.y = pt.y;
     range.height = 0;
   }
   ScrollTo(pt, nsIScrollableFrame::INSTANT, &range);
 }
 
 void
-nsGfxScrollFrameInner::ScrollToCSSPixelsApproximate(const Point& aScrollPosition)
+nsGfxScrollFrameInner::ScrollToCSSPixelsApproximate(const CSSPoint& aScrollPosition)
 {
-  nsPoint pt(nsPresContext::CSSPixelsToAppUnits(aScrollPosition.x),
-             nsPresContext::CSSPixelsToAppUnits(aScrollPosition.y));
+  nsPoint pt = CSSPoint::ToAppUnits(aScrollPosition);
   nscoord halfRange = nsPresContext::CSSPixelsToAppUnits(1000);
   nsRect range(pt.x - halfRange, pt.y - halfRange, 2*halfRange - 1, 2*halfRange - 1);
   ScrollTo(pt, nsIScrollableFrame::INSTANT, &range);
 }
 
 nsIntPoint
 nsGfxScrollFrameInner::GetScrollPositionCSSPixels()
 {
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -42,17 +42,16 @@ class ScrollbarActivity;
 // When set, the next scroll operation on the scrollframe will invalidate its
 // entire contents. Useful for text-overflow.
 // This bit is cleared after each time the scrollframe is scrolled. Whoever
 // needs to set it should set it again on each paint.
 #define NS_SCROLLFRAME_INVALIDATE_CONTENTS_ON_SCROLL NS_FRAME_STATE_BIT(20)
 
 class nsGfxScrollFrameInner : public nsIReflowCallback {
 public:
-  typedef mozilla::gfx::Point Point;
   typedef mozilla::layout::ScrollbarActivity ScrollbarActivity;
 
   class AsyncScroll;
 
   nsGfxScrollFrameInner(nsContainerFrame* aOuter, bool aIsRoot);
   ~nsGfxScrollFrameInner();
 
   typedef nsIScrollableFrame::ScrollbarStyles ScrollbarStyles;
@@ -167,17 +166,17 @@ public:
    * aScrollPosition. Null means only aScrollPosition is allowed.
    * This is a closed-ended range --- aRange.XMost()/aRange.YMost() are allowed.
    */
   void ScrollTo(nsPoint aScrollPosition, nsIScrollableFrame::ScrollMode aMode,
                 const nsRect* aRange = nullptr) {
     ScrollToWithOrigin(aScrollPosition, aMode, nsGkAtoms::other, aRange);
   }
   void ScrollToCSSPixels(nsIntPoint aScrollPosition);
-  void ScrollToCSSPixelsApproximate(const Point& aScrollPosition);
+  void ScrollToCSSPixelsApproximate(const mozilla::CSSPoint& aScrollPosition);
   nsIntPoint GetScrollPositionCSSPixels();
   void ScrollToImpl(nsPoint aScrollPosition, const nsRect& aRange);
   void ScrollVisual(nsPoint aOldScrolledFramePosition);
   void ScrollBy(nsIntPoint aDelta, nsIScrollableFrame::ScrollUnit aUnit,
                 nsIScrollableFrame::ScrollMode aMode, nsIntPoint* aOverflow, nsIAtom *aOrigin = nullptr);
   void ScrollToRestoredPosition();
   nsSize GetLineScrollAmount() const;
   nsSize GetPageScrollAmount() const;
@@ -515,17 +514,17 @@ public:
   }
   virtual void ScrollTo(nsPoint aScrollPosition, ScrollMode aMode,
                         const nsRect* aRange = nullptr) MOZ_OVERRIDE {
     mInner.ScrollTo(aScrollPosition, aMode, aRange);
   }
   virtual void ScrollToCSSPixels(nsIntPoint aScrollPosition) MOZ_OVERRIDE {
     mInner.ScrollToCSSPixels(aScrollPosition);
   }
-  virtual void ScrollToCSSPixelsApproximate(const Point& aScrollPosition) MOZ_OVERRIDE {
+  virtual void ScrollToCSSPixelsApproximate(const mozilla::CSSPoint& aScrollPosition) MOZ_OVERRIDE {
     mInner.ScrollToCSSPixelsApproximate(aScrollPosition);
   }
   virtual nsIntPoint GetScrollPositionCSSPixels() MOZ_OVERRIDE {
     return mInner.GetScrollPositionCSSPixels();
   }
   virtual void ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, ScrollMode aMode,
                         nsIntPoint* aOverflow, nsIAtom *aOrigin = nullptr) MOZ_OVERRIDE {
     mInner.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin);
@@ -778,17 +777,17 @@ public:
   }
   virtual void ScrollTo(nsPoint aScrollPosition, ScrollMode aMode,
                         const nsRect* aRange = nullptr) MOZ_OVERRIDE {
     mInner.ScrollTo(aScrollPosition, aMode, aRange);
   }
   virtual void ScrollToCSSPixels(nsIntPoint aScrollPosition) MOZ_OVERRIDE {
     mInner.ScrollToCSSPixels(aScrollPosition);
   }
-  virtual void ScrollToCSSPixelsApproximate(const Point& aScrollPosition) MOZ_OVERRIDE {
+  virtual void ScrollToCSSPixelsApproximate(const mozilla::CSSPoint& aScrollPosition) MOZ_OVERRIDE {
     mInner.ScrollToCSSPixelsApproximate(aScrollPosition);
   }
   virtual nsIntPoint GetScrollPositionCSSPixels() MOZ_OVERRIDE {
     return mInner.GetScrollPositionCSSPixels();
   }
   virtual void ScrollBy(nsIntPoint aDelta, ScrollUnit aUnit, ScrollMode aMode,
                         nsIntPoint* aOverflow, nsIAtom *aOrigin = nullptr) MOZ_OVERRIDE {
     mInner.ScrollBy(aDelta, aUnit, aMode, aOverflow, aOrigin);
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -10,33 +10,32 @@
 #ifndef nsIScrollFrame_h___
 #define nsIScrollFrame_h___
 
 #include "nsISupports.h"
 #include "nsCoord.h"
 #include "nsPresContext.h"
 #include "mozilla/gfx/Point.h"
 #include "nsIScrollbarOwner.h"
+#include "Units.h"
 
 #define NS_DEFAULT_VERTICAL_SCROLL_DISTANCE   3
 #define NS_DEFAULT_HORIZONTAL_SCROLL_DISTANCE 5
 
 class nsBoxLayoutState;
 class nsIScrollPositionListener;
 class nsIFrame;
 
 /**
  * Interface for frames that are scrollable. This interface exposes
  * APIs for examining scroll state, observing changes to scroll state,
  * and triggering scrolling.
  */
 class nsIScrollableFrame : public nsIScrollbarOwner {
 public:
-  typedef mozilla::gfx::Point Point;
-
   NS_DECL_QUERYFRAME_TARGET(nsIScrollableFrame)
 
   /**
    * Get the frame for the content that we are scrolling within
    * this scrollable frame.
    */
   virtual nsIFrame* GetScrolledFrame() const = 0;
 
@@ -161,17 +160,18 @@ public:
   /**
    * Scrolls to a particular position in float CSS pixels.
    * This does not guarantee that GetScrollPositionCSSPixels equals
    * aScrollPosition afterward. It tries to scroll as close to
    * aScrollPosition as possible while scrolling by an integer
    * number of layer pixels (so the operation is fast and looks clean).
    * The scroll mode is INSTANT.
    */
-  virtual void ScrollToCSSPixelsApproximate(const Point& aScrollPosition) = 0;
+  virtual void ScrollToCSSPixelsApproximate(const mozilla::CSSPoint& aScrollPosition) = 0;
+
   /**
    * Returns the scroll position in integer CSS pixels, rounded to the nearest
    * pixel.
    */
   virtual nsIntPoint GetScrollPositionCSSPixels() = 0;
   /**
    * When scrolling by a relative amount, we can choose various units.
    */
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1253,45 +1253,53 @@ nsDisplayImage::GetDestRect()
   return destRect;
 }
 
 LayerState
 nsDisplayImage::GetLayerState(nsDisplayListBuilder* aBuilder,
                               LayerManager* aManager,
                               const FrameLayerBuilder::ContainerParameters& aParameters)
 {
-  if (mImage->GetType() != imgIContainer::TYPE_RASTER ||
-      !aManager->IsCompositingCheap() ||
-      !nsLayoutUtils::GPUImageScalingEnabled()) {
-    return LAYER_NONE;
+  bool animated = false;
+  if (!nsLayoutUtils::AnimatedImageLayersEnabled() ||
+      mImage->GetType() != imgIContainer::TYPE_RASTER ||
+      NS_FAILED(mImage->GetAnimated(&animated)) ||
+      !animated) {
+    if (!aManager->IsCompositingCheap() ||
+        !nsLayoutUtils::GPUImageScalingEnabled()) {
+      return LAYER_NONE;
+    }
   }
 
-  int32_t imageWidth;
-  int32_t imageHeight;
-  mImage->GetWidth(&imageWidth);
-  mImage->GetHeight(&imageHeight);
+  if (!animated) {
+    int32_t imageWidth;
+    int32_t imageHeight;
+    mImage->GetWidth(&imageWidth);
+    mImage->GetHeight(&imageHeight);
 
-  NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
+    NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!");
 
-  gfxRect destRect = GetDestRect();
+    gfxRect destRect = GetDestRect();
 
-  destRect.width *= aParameters.mXScale;
-  destRect.height *= aParameters.mYScale;
+    destRect.width *= aParameters.mXScale;
+    destRect.height *= aParameters.mYScale;
 
-  // Calculate the scaling factor for the frame.
-  gfxSize scale = gfxSize(destRect.width / imageWidth, destRect.height / imageHeight);
+    // Calculate the scaling factor for the frame.
+    gfxSize scale = gfxSize(destRect.width / imageWidth,
+                            destRect.height / imageHeight);
 
-  // If we are not scaling at all, no point in separating this into a layer.
-  if (scale.width == 1.0f && scale.height == 1.0f) {
-    return LAYER_NONE;
-  }
+    // If we are not scaling at all, no point in separating this into a layer.
+    if (scale.width == 1.0f && scale.height == 1.0f) {
+      return LAYER_NONE;
+    }
 
-  // If the target size is pretty small, no point in using a layer.
-  if (destRect.width * destRect.height < 64 * 64) {
-    return LAYER_NONE;
+    // If the target size is pretty small, no point in using a layer.
+    if (destRect.width * destRect.height < 64 * 64) {
+      return LAYER_NONE;
+    }
   }
 
   nsRefPtr<ImageContainer> container;
   mImage->GetImageContainer(aManager, getter_AddRefs(container));
   if (!container) {
     return LAYER_NONE;
   }
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c03fad8bb09fb01a92f545cbccef322eec84a0dd
GIT binary patch
literal 1748
zc${<hbhEHbWME`qT*$=0@E-^n{)2(yPZmxt1||j_1`q(sGcf(@>0f#JE&t*<JU6;G
z-`n$>zwOb9z-OJyR(WoF$9?>**Y$6)Cx4y$F3rcorMhp?M-3^h>0V1dzfAF3ZnEO*
zs>NB`UDkY;e$;iGbHmR$imQ*l*jCBAeAn|EJ8C`8+rR&_;9vd~^$g~S6UH4Yl6FW;
zesSWEgy$y@NySRZu$42LZ~olO+4aD3mROco7gs{nEUR5ow_g3)CHdOYJG#4a^~}|~
zy>C|k<d%`K{1cH=GiSz`9cS+3{OFO;wbhM?t>~Tmcl+JzZ~xYcuQT_H^DEDrzHT4i
zb-V9;^7qa5hvnD%PwU@y{{H*3_GXL+c~l}UoOaaX+^=)zQouuJOTqO%OCkhM9sQ>%
zxi)9YrNGDTHj0aTa)N|TdQQ<~UMq6x(+TexhTUhr1YbVxt6*9BZqLQd$Nd*Lmh-5F
zn4G?-<(aCJdQ0TkWzj%sU*EvcSD_-2@i|X#l^zTCNObQ}jW)Y{Q!n%Cy4DM3C!@U!
z9nUO{zJ54%N9EUdr5CQhkN0ad<;e@!apWOur@gM;<sDa&11B2#w8h*!lzM7rXs+3X
z$j{Fi7TVV9+0H6?ab<=3cDuc|N^@?^jXZzvkJR_Kr*~GqpLa+4$NS?4JNe^dWPW};
zd~&9~{2iHJpZ8x}>3=_3_V?G_H+SaGzb*Uc`}T(?`{SeK{{Gzj^5%N^+j9SYumAY+
z{QW5T|9@Bi{P*+y{9E!3OaFckX<!xFF{P1HsbWeKuaU)+W<jSPlUqcCc1&)SOsbgN
zCR=1Nxm~g8$D|I`NjoNWYA&jn)TO)0Vp6x^p&t`_OfT)2*lYQyVq%}|CyR;wj!Zu%
zOmG$2IbouwQssn6zDAZ4CI>qG?4J@Ew6lL|WKw1SwAdoc{^^NLKl^5+PTJWwGjma8
z->lqCmVL7e5B=<&Q+jD<@7&5qmA&(7KUwz9Z)Ez_v!GRISI@#urK+Aqy+&3&izhn$
z>RvK6Xjk{rnMqaM%jOnYbuVAo^s8&d(n-6zR<2xh$}4x%POs>+Z3kCLuibZ3OJ?1%
zm#bvfpZlpLyWtw=YT1qVB(=TeHa*i^Ew}ldrMCQ*Z=S2=xBiRNR@la#wOe63f0ec3
z4)LztiaX_JSu5>QU$t9lxBf0`<vr%7b}R3-zh$km&;8YImHqy|tW^*E7hzxkW-dl9
S2IkSsHJZ6bGZ(cpmo)${7=cXy
--- a/layout/reftests/invalidation/reftest.list
+++ b/layout/reftests/invalidation/reftest.list
@@ -1,7 +1,9 @@
 == table-repaint-a.html table-repaint-a-ref.html
 == table-repaint-b.html table-repaint-b-ref.html
 == table-repaint-c.html table-repaint-c-ref.html
 == table-repaint-d.html table-repaint-d-ref.html
 == 540247-1.xul 540247-1-ref.xul
 == 543681-1.html 543681-1-ref.html
 == test-image-layers.html test-image-layers-ref.html
+pref(layout.animated-image-layers.enabled,true) == test-animated-image-layers.html test-animated-image-layers-ref.html
+pref(layout.animated-image-layers.enabled,true) == test-animated-image-layers-background.html test-animated-image-layers-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/test-animated-image-layers-background.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<body>
+<div>
+<div id="image" style="width: 256px; height: 256px; background-image: url('image_rgrg-256x256-animated.gif');" class="reftest-no-paint"></div>
+</div>
+<script type="application/javascript">
+
+function doTest() {
+  document.body.style.background = "black";
+  document.documentElement.removeAttribute("class");
+}
+document.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/test-animated-image-layers-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body style="background:black">
+<div>
+<img id="image" src="./image_rgrg-256x256.png"></img>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/test-animated-image-layers.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<body>
+<div>
+<img id="image" class="reftest-no-paint" src="./image_rgrg-256x256-animated.gif"></img>
+</div>
+<script type="application/javascript">
+
+function doTest() {
+  document.body.style.background = "black";
+  document.documentElement.removeAttribute("class");
+}
+document.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -7196,18 +7196,19 @@ nsRuleNode::ComputeColumnData(void* aSta
 
   // column-count: auto, integer, inherit
   const nsCSSValue* columnCountValue = aRuleData->ValueForColumnCount();
   if (eCSSUnit_Auto == columnCountValue->GetUnit() ||
       eCSSUnit_Initial == columnCountValue->GetUnit()) {
     column->mColumnCount = NS_STYLE_COLUMN_COUNT_AUTO;
   } else if (eCSSUnit_Integer == columnCountValue->GetUnit()) {
     column->mColumnCount = columnCountValue->GetIntValue();
-    // Max 1000 columns - wallpaper for bug 345583.
-    column->mColumnCount = std::min(column->mColumnCount, 1000U);
+    // Max kMaxColumnCount columns - wallpaper for bug 345583.
+    column->mColumnCount = std::min(column->mColumnCount,
+                                    nsStyleColumn::kMaxColumnCount);
   } else if (eCSSUnit_Inherit == columnCountValue->GetUnit()) {
     canStoreInRuleTree = false;
     column->mColumnCount = parent->mColumnCount;
   }
 
   // column-rule-width: length, enum, inherit
   const nsCSSValue& widthValue = *aRuleData->ValueForColumnRuleWidth();
   if (eCSSUnit_Initial == widthValue.GetUnit()) {
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -735,16 +735,18 @@ nsChangeHint nsStyleXUL::CalcDifference(
   if (mBoxOrdinal != aOther.mBoxOrdinal)
     return NS_STYLE_HINT_FRAMECHANGE;
   return NS_STYLE_HINT_REFLOW;
 }
 
 // --------------------
 // nsStyleColumn
 //
+/* static */ const uint32_t nsStyleColumn::kMaxColumnCount = 1000;
+
 nsStyleColumn::nsStyleColumn(nsPresContext* aPresContext)
 {
   MOZ_COUNT_CTOR(nsStyleColumn);
   mColumnCount = NS_STYLE_COLUMN_COUNT_AUTO;
   mColumnWidth.SetAutoValue();
   mColumnGap.SetNormalValue();
   mColumnFill = NS_STYLE_COLUMN_FILL_BALANCE;
 
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2122,16 +2122,22 @@ struct nsStyleColumn {
     aContext->FreeToShell(sizeof(nsStyleColumn), this);
   }
 
   nsChangeHint CalcDifference(const nsStyleColumn& aOther) const;
   static nsChangeHint MaxDifference() {
     return NS_STYLE_HINT_FRAMECHANGE;
   }
 
+  /**
+   * This is the maximum number of columns we can process. It's used in both
+   * nsColumnSetFrame and nsRuleNode.
+   */
+  static const uint32_t kMaxColumnCount;
+
   uint32_t     mColumnCount; // [reset] see nsStyleConsts.h
   nsStyleCoord mColumnWidth; // [reset] coord, auto
   nsStyleCoord mColumnGap;   // [reset] coord, normal
 
   nscolor      mColumnRuleColor;  // [reset]
   uint8_t      mColumnRuleStyle;  // [reset]
   uint8_t      mColumnFill;  // [reset] see nsStyleConsts.h
 
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -907,22 +907,20 @@ nsTransitionManager::WalkTransitionRule(
       !aData->mPresContext->IsProcessingAnimationStyleChange()) {
     // If we're processing a normal style change rather than one from
     // animation, don't add the transition rule.  This allows us to
     // compute the new style value rather than having the transition
     // override it, so that we can start transitioning differently.
 
     // We need to immediately restyle with animation
     // after doing this.
-    if (et) {
-      nsRestyleHint hint =
-        aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ?
-        eRestyle_Self : eRestyle_Subtree;
-      mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint);
-    }
+    nsRestyleHint hint =
+      aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ?
+      eRestyle_Self : eRestyle_Subtree;
+    mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint);
     return;
   }
 
   et->EnsureStyleRuleFor(
     aData->mPresContext->RefreshDriver()->MostRecentRefresh());
 
   aData->mRuleWalker->Forward(et->mStyleRule);
 }
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -822,16 +822,23 @@ void MediaPipelineTransmit::PipelineList
   }
 
   // We now need to send the video frame to the other side
   if (!img) {
     // segment.AppendFrame() allows null images, which show up here as null
     return;
   }
 
+  // We get passed duplicate frames every ~10ms even if there's no frame change!
+  int32_t serial = img->GetSerial();
+  if (serial == last_img_) {
+    return;
+  }
+  last_img_ = serial;
+
   ImageFormat format = img->GetFormat();
 #ifdef MOZ_WIDGET_GONK
   if (format == GONK_IO_SURFACE) {
     layers::GonkIOSurfaceImage *nativeImage = static_cast<layers::GonkIOSurfaceImage*>(img);
     layers::SurfaceDescriptor handle = nativeImage->GetSurfaceDescriptor();
     layers::SurfaceDescriptorGralloc grallocHandle = handle.get_SurfaceDescriptorGralloc();
 
     android::sp<android::GraphicBuffer> graphicBuffer = layers::GrallocBufferActor::GetFrom(grallocHandle);
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -316,18 +316,22 @@ class MediaPipelineTransmit : public Med
 
   // Override MediaPipeline::TransportReady.
   virtual nsresult TransportReady_s(TransportFlow *flow);
 
   // Separate class to allow ref counting
   class PipelineListener : public MediaStreamListener {
    public:
     PipelineListener(const RefPtr<MediaSessionConduit>& conduit)
-      : conduit_(conduit), active_(false), samples_10ms_buffer_(nullptr),
-        buffer_current_(0), samplenum_10ms_(0){}
+      : conduit_(conduit),
+        active_(false),
+        last_img_(-1),
+        samples_10ms_buffer_(nullptr),
+        buffer_current_(0),
+        samplenum_10ms_(0) {}
 
     ~PipelineListener()
     {
       // release conduit on mainthread.  Must use forget()!
       nsresult rv = NS_DispatchToMainThread(new
         ConduitDeleteEvent(conduit_.forget()), NS_DISPATCH_NORMAL);
       MOZ_ASSERT(!NS_FAILED(rv),"Could not dispatch conduit shutdown to main");
       if (NS_FAILED(rv)) {
@@ -354,16 +358,18 @@ class MediaPipelineTransmit : public Med
 				   TrackRate rate, AudioChunk& chunk);
 #ifdef MOZILLA_INTERNAL_API
     virtual void ProcessVideoChunk(VideoSessionConduit *conduit,
 				   TrackRate rate, VideoChunk& chunk);
 #endif
     RefPtr<MediaSessionConduit> conduit_;
     volatile bool active_;
 
+    int32_t last_img_; // serial number of last Image
+
     // These vars handle breaking audio samples into exact 10ms chunks:
     // The buffer of 10ms audio samples that we will send once full
     // (can be carried over from one call to another).
     nsAutoArrayPtr<int16_t> samples_10ms_buffer_;
     // The location of the pointer within that buffer (in units of samples).
     int64_t buffer_current_;
     // The number of samples in a 10ms audio chunk.
     int64_t samplenum_10ms_;
--- a/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp
+++ b/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp
@@ -2,81 +2,103 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CSFLog.h"
 #include "base/basictypes.h"
 #include "MediaStreamList.h"
 #ifdef MOZILLA_INTERNAL_API
 #include "mozilla/dom/MediaStreamListBinding.h"
+#include "nsContentUtils.h"
 #endif
 #include "nsIScriptGlobalObject.h"
 #include "PeerConnectionImpl.h"
 
 namespace mozilla {
 namespace dom {
 
 MediaStreamList::MediaStreamList(sipcc::PeerConnectionImpl* peerConnection,
                                  StreamType type)
   : mPeerConnection(peerConnection),
     mType(type)
 {
-  MOZ_COUNT_CTOR(mozilla::dom::MediaStreamList);
+  SetIsDOMBinding();
 }
 
 MediaStreamList::~MediaStreamList()
 {
-  MOZ_COUNT_DTOR(mozilla::dom::MediaStreamList);
 }
 
+#ifdef MOZILLA_INTERNAL_API
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(MediaStreamList)
+#else
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+#endif
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamList)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamList)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
 JSObject*
-MediaStreamList::WrapObject(JSContext* cx, bool* aTookOwnership)
+MediaStreamList::WrapObject(JSContext* cx, JS::Handle<JSObject*> scope)
 {
 #ifdef MOZILLA_INTERNAL_API
-  nsCOMPtr<nsIScriptGlobalObject> global =
-    do_QueryInterface(mPeerConnection->GetWindow());
-  JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
-  if (!scope) {
-    return nullptr;
-  }
-
-  JSAutoCompartment ac(cx, scope);
-  JSObject* obj = MediaStreamListBinding::Wrap(cx, scope, this, aTookOwnership);
-  return obj;
+  return MediaStreamListBinding::Wrap(cx, scope, this);
 #else
   return nullptr;
 #endif
 }
 
+nsISupports*
+MediaStreamList::GetParentObject()
+{
+  return mPeerConnection->GetWindow();
+}
+
 template<class T>
 static DOMMediaStream*
 GetStreamFromInfo(T* info, bool& found)
 {
   if (!info) {
     found = false;
     return nullptr;
   }
 
   found = true;
   return info->GetMediaStream();
 }
 
 DOMMediaStream*
 MediaStreamList::IndexedGetter(uint32_t index, bool& found)
 {
+  if (!mPeerConnection->media()) { // PeerConnection closed
+    found = false;
+    return nullptr;
+  }
   if (mType == Local) {
     return GetStreamFromInfo(mPeerConnection->media()->
       GetLocalStream(index), found);
   }
 
   return GetStreamFromInfo(mPeerConnection->media()->
     GetRemoteStream(index), found);
 }
 
 uint32_t
 MediaStreamList::Length()
 {
+  if (!mPeerConnection->media()) { // PeerConnection closed
+    return 0;
+  }
   return mType == Local ? mPeerConnection->media()->LocalStreamsLength() :
       mPeerConnection->media()->RemoteStreamsLength();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/media/webrtc/signaling/src/peerconnection/MediaStreamList.h
+++ b/media/webrtc/signaling/src/peerconnection/MediaStreamList.h
@@ -4,43 +4,49 @@
 
 #ifndef MediaStreamList_h__
 #define MediaStreamList_h__
 
 #include "mozilla/ErrorResult.h"
 #include "nsISupportsImpl.h"
 #include "nsAutoPtr.h"
 #include "jspubtd.h"
-#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsWrapperCache.h"
 
 #ifdef USE_FAKE_MEDIA_STREAMS
 #include "FakeMediaStreams.h"
 #else
 #include "DOMMediaStream.h"
 #endif
 
 namespace sipcc {
 class PeerConnectionImpl;
 } // namespace sipcc
 
 namespace mozilla {
 namespace dom {
 
-class MediaStreamList : public NonRefcountedDOMObject
+class MediaStreamList : public nsISupports,
+                        public nsWrapperCache
 {
 public:
   enum StreamType {
     Local,
     Remote
   };
 
   MediaStreamList(sipcc::PeerConnectionImpl* peerConnection, StreamType type);
-  ~MediaStreamList();
+  virtual ~MediaStreamList();
 
-  JSObject* WrapObject(JSContext* cx, bool* aTookOwnership);
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaStreamList)
+
+  virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> scope)
+    MOZ_OVERRIDE;
+  nsISupports* GetParentObject();
 
   DOMMediaStream* IndexedGetter(uint32_t index, bool& found);
   uint32_t Length();
 
 private:
   nsRefPtr<sipcc::PeerConnectionImpl> mPeerConnection;
   StreamType mType;
 };
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1481,28 +1481,34 @@ PeerConnectionImpl::GetSdpParseErrors() 
 }
 
 
 #ifdef MOZILLA_INTERNAL_API
 static nsresult
 GetStreams(JSContext* cx, PeerConnectionImpl* peerConnection,
            MediaStreamList::StreamType type, JS::Value* streams)
 {
-  nsAutoPtr<MediaStreamList> list(new MediaStreamList(peerConnection, type));
+  nsRefPtr<MediaStreamList> list(new MediaStreamList(peerConnection, type));
 
-  bool tookOwnership = false;
-  JSObject* obj = list->WrapObject(cx, &tookOwnership);
-  if (!tookOwnership) {
+  nsCOMPtr<nsIScriptGlobalObject> global =
+    do_QueryInterface(peerConnection->GetWindow());
+  JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
+  if (!scope) {
     streams->setNull();
     return NS_ERROR_FAILURE;
   }
 
-  // Transfer ownership to the binding.
+  JSAutoCompartment ac(cx, scope);
+  JSObject* obj = list->WrapObject(cx, scope);
+  if (!obj) {
+    streams->setNull();
+    return NS_ERROR_FAILURE;
+  }
+
   streams->setObject(*obj);
-  list.forget();
   return NS_OK;
 }
 #endif
 
 NS_IMETHODIMP
 PeerConnectionImpl::GetLocalStreams(JSContext* cx, JS::Value* streams)
 {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
--- a/media/webrtc/trunk/tools/gyp/pylib/gyp/generator/mozmake.py
+++ b/media/webrtc/trunk/tools/gyp/pylib/gyp/generator/mozmake.py
@@ -369,24 +369,20 @@ class MakefileGenerator(object):
       data['LIBRARY_NAME'] = striplib(spec['target_name'])
       data['FORCE_STATIC_LIB'] = 1
     elif spec['type'] in ('loadable_module', 'shared_library'):
       data['LIBRARY_NAME'] = striplib(spec['target_name'])
       data['FORCE_SHARED_LIB'] = 1
     else:
       # Maybe nothing?
       return False
-    if self.flavor == 'win':
-      top = self.relative_topsrcdir
-    else:
-      top = self.topsrcdir
-    WriteMakefile(output_file, data, build_file, depth, top,
+    WriteMakefile(output_file, data, build_file, depth, self.topsrcdir,
                   # we set srcdir up one directory, since the subdir
                   # doesn't actually exist in the source directory
-                  swapslashes(os.path.normpath(os.path.join(top, self.relative_srcdir, os.path.split(rel_path)[0]))),
+                  swapslashes(os.path.normpath(os.path.join(self.topsrcdir, self.relative_srcdir, os.path.split(rel_path)[0]))),
                   self.relative_srcdir,
                   self.common_mk_path)
     return True
 
 def GenerateOutput(target_list, target_dicts, data, params):
   options = params['options']
   flavor = GetFlavor(params)
   generator_flags = params.get('generator_flags', {})
@@ -432,26 +428,20 @@ def GenerateOutput(target_list, target_d
 
   generator = MakefileGenerator(target_dicts, data, options, depth, topsrcdir, relative_topsrcdir, relative_srcdir, output_dir, flavor, common_mk_path)
   generator.ProcessTargets(needed_targets)
 
   # Write the top-level makefile, which simply calls the other makefiles
   topdata = {'DIRS': generator.dirs}
   if generator.parallel_dirs:
     topdata['PARALLEL_DIRS'] = generator.parallel_dirs
-  if flavor == 'win':
-    top = relative_topsrcdir
-    src = srcdir
-  else:
-    top = topsrcdir
-    src = abs_srcdir
   WriteMakefile(makefile_path, topdata, params['build_files'][0],
                 depth,
-                swapslashes(top),
-                swapslashes(src),
+                swapslashes(topsrcdir),
+                swapslashes(abs_srcdir),
                 swapslashes(relative_srcdir),
                 common_mk_path)
   scriptname = "$(topsrcdir)/media/webrtc/trunk/tools/gyp/pylib/gyp/generator/mozmake.py"
   # Reassemble a commandline from parts so that all the paths are correct
   # NOTE: this MUST match the commandline generated in configure.in!
   # since we don't see --include statements, duplicate them in FORCE_INCLUDE_FILE lines
   # Being in a define, they also get used by the common.mk invocation of gyp so they
   # they don't disappear in the second round of tail-swallowing
--- a/media/webrtc/trunk/webrtc/modules/video_capture/windows/device_info_ds.cc
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/windows/device_info_ds.cc
@@ -581,17 +581,17 @@ WebRtc_Word32 DeviceInfoDS::CreateCapabi
                 capability->interlaced = h->dwInterlaceFlags
                                         & (AMINTERLACE_IsInterlaced
                                            | AMINTERLACE_DisplayModeBobOnly);
                 avgTimePerFrame = h->AvgTimePerFrame;
             }
 
             if (hrVC == S_OK)
             {
-                LONGLONG *frameDurationList;
+                LONGLONG *frameDurationList = NULL;
                 LONGLONG maxFPS;
                 long listSize;
                 SIZE size;
                 size.cx = capability->width;
                 size.cy = capability->height;
 
                 // GetMaxAvailableFrameRate doesn't return max frame rate always
                 // eg: Logitech Notebook. This may be due to a bug in that API
--- a/mobile/android/base/AwesomeBar.java
+++ b/mobile/android/base/AwesomeBar.java
@@ -3,16 +3,17 @@
  * 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/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.health.BrowserHealthRecorder;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
@@ -40,16 +41,18 @@ import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ImageButton;
 import android.widget.TabWidget;
 import android.widget.Toast;
 
+import org.json.JSONObject;
+
 import java.net.URLEncoder;
 
 interface AutocompleteHandler {
     void onAutocomplete(String res);
 }
 
 public class AwesomeBar extends GeckoActivity
                         implements AutocompleteHandler,
@@ -101,16 +104,17 @@ public class AwesomeBar extends GeckoAct
             }
 
             @Override
             public void onSearch(SearchEngine engine, String text) {
                 Intent resultIntent = new Intent();
                 resultIntent.putExtra(URL_KEY, text);
                 resultIntent.putExtra(TARGET_KEY, mTarget);
                 resultIntent.putExtra(SEARCH_KEY, engine.name);
+                recordSearch(engine.identifier, "barsuggest");
                 finishWithResult(resultIntent);
             }
 
             @Override
             public void onEditSuggestion(final String text) {
                 ThreadUtils.postToUiThread(new Runnable() {
                     @Override
                     public void run() {
@@ -383,16 +387,38 @@ public class AwesomeBar extends GeckoAct
         if (title != null && !TextUtils.isEmpty(title))
             resultIntent.putExtra(TITLE_KEY, title);
         if (userEntered)
             resultIntent.putExtra(USER_ENTERED_KEY, userEntered);
         resultIntent.putExtra(TARGET_KEY, mTarget);
         finishWithResult(resultIntent);
     }
 
+    /**
+     * Record in Health Report that a search has occurred.
+     *
+     * @param identifier
+     *        a search identifier, such as "partnername". Can be null.
+     * @param where
+     *        where the search was initialized; one of the values in
+     *        {@link BrowserHealthRecorder#SEARCH_LOCATIONS}.
+     */
+    private static void recordSearch(String identifier, String where) {
+        Log.i(LOGTAG, "Recording search: " + identifier + ", " + where);
+        try {
+            JSONObject message = new JSONObject();
+            message.put("type", BrowserHealthRecorder.EVENT_SEARCH);
+            message.put("location", where);
+            message.put("identifier", identifier);
+            GeckoAppShell.getEventDispatcher().dispatchEvent(message);
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Error recording search.", e);
+        }
+    }
+
     private void openUserEnteredAndFinish(final String url) {
         final int index = url.indexOf(' ');
 
         // Check for a keyword if the URL looks like a search query
         if (!StringUtils.isSearchQuery(url, true)) {
             openUrlAndFinish(url, "", true);
             return;
         }
@@ -409,16 +435,19 @@ public class AwesomeBar extends GeckoAct
                     keyword = url.substring(0, index);
                     keywordSearch = url.substring(index + 1);
                 }
 
                 final String keywordUrl = BrowserDB.getUrlForKeyword(getContentResolver(), keyword);
                 final String searchUrl = (keywordUrl != null)
                                        ? keywordUrl.replace("%s", URLEncoder.encode(keywordSearch))
                                        : url;
+                if (keywordUrl != null) {
+                    recordSearch(null, "barkeyword");
+                }
                 openUrlAndFinish(searchUrl, "", true);
             }
         });
     }
 
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         // Galaxy Note sends key events for the stylus that are outside of the
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -8,16 +8,17 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
+import org.mozilla.gecko.health.BrowserHealthReporter;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.widget.AboutHome;
 
@@ -137,16 +138,18 @@ abstract public class BrowserApp extends
     // Tag for the AboutHome fragment. The fragment is automatically attached
     // after restoring from a saved state, so we use this tag to identify it.
     private static final String ABOUTHOME_TAG = "abouthome";
 
     private SharedPreferencesHelper mSharedPreferencesHelper;
 
     private OrderedBroadcastHelper mOrderedBroadcastHelper;
 
+    private BrowserHealthReporter mBrowserHealthReporter;
+
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
         switch(msg) {
             case LOCATION_CHANGE:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     maybeCancelFaviconLoad(tab);
                 }
                 // fall through
@@ -408,16 +411,17 @@ abstract public class BrowserApp extends
         registerEventListener("Feedback:OpenPlayStore");
         registerEventListener("Feedback:MaybeLater");
         registerEventListener("Telemetry:Gather");
 
         Distribution.init(this, getPackageResourcePath());
         JavaAddonManager.getInstance().init(getApplicationContext());
         mSharedPreferencesHelper = new SharedPreferencesHelper(getApplicationContext());
         mOrderedBroadcastHelper = new OrderedBroadcastHelper(getApplicationContext());
+        mBrowserHealthReporter = new BrowserHealthReporter();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
                     @Override
                     public NdefMessage createNdefMessage(NfcEvent event) {
                         Tab tab = Tabs.getInstance().getSelectedTab();
@@ -626,16 +630,21 @@ abstract public class BrowserApp extends
             mSharedPreferencesHelper = null;
         }
 
         if (mOrderedBroadcastHelper != null) {
             mOrderedBroadcastHelper.uninit();
             mOrderedBroadcastHelper = null;
         }
 
+        if (mBrowserHealthReporter != null) {
+            mBrowserHealthReporter.uninit();
+            mBrowserHealthReporter = null;
+        }
+
         unregisterEventListener("CharEncoding:Data");
         unregisterEventListener("CharEncoding:State");
         unregisterEventListener("Feedback:LastUrl");
         unregisterEventListener("Feedback:OpenPlayStore");
         unregisterEventListener("Feedback:MaybeLater");
         unregisterEventListener("Telemetry:Gather");
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 14) {
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -794,29 +794,30 @@ public class BrowserToolbar implements T
     public void setPageActionVisibility(boolean isLoading) {
         // Handle the loading mode page actions
         mStop.setVisibility(isLoading ? View.VISIBLE : View.GONE);
 
         // Handle the viewing mode page actions
         setSiteSecurityVisibility(mShowSiteSecurity && !isLoading);
 
         // Handle the readerMode image and visibility: We show the reader mode button if 1) you can
-        // enter reader mode for current page or 2) if you're already in reader mode and can exit back,
+        // enter reader mode for current page or 2) if you're already in reader mode,
         // in which case we show the reader mode "close" (reader_active) icon.
-        boolean exitableReaderMode = false;
+        boolean inReaderMode = false;
         Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab != null)
-            exitableReaderMode = ReaderModeUtils.isAboutReader(tab.getURL()) && tab.canDoBack();
-        mReader.setImageResource(exitableReaderMode ? R.drawable.reader_active : R.drawable.reader);
-        mReader.setVisibility(!isLoading && (mShowReader || exitableReaderMode) ? View.VISIBLE : View.GONE);
+            inReaderMode = ReaderModeUtils.isAboutReader(tab.getURL());
+        mReader.setImageResource(inReaderMode ? R.drawable.reader_active : R.drawable.reader);
+
+        mReader.setVisibility(!isLoading && (mShowReader || inReaderMode) ? View.VISIBLE : View.GONE);
 
         // We want title to fill the whole space available for it when there are icons
         // being shown on the right side of the toolbar as the icons already have some
         // padding in them. This is just to avoid wasting space when icons are shown.
-        mTitle.setPadding(0, 0, (!isLoading && !(mShowReader || exitableReaderMode) ? mTitlePadding : 0), 0);
+        mTitle.setPadding(0, 0, (!isLoading && !(mShowReader || inReaderMode) ? mTitlePadding : 0), 0);
 
         updateFocusOrder();
     }
 
     private void setSiteSecurityVisibility(final boolean visible) {
         if (visible == mSiteSecurityVisible)
             return;
 
--- a/mobile/android/base/DoorHanger.java
+++ b/mobile/android/base/DoorHanger.java
@@ -6,36 +6,49 @@
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.widget.Divider;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Build;
 import android.text.SpannableString;
 import android.text.method.LinkMovementMethod;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.URLSpan;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class DoorHanger extends LinearLayout implements Button.OnClickListener {
     private static final String LOGTAG = "GeckoDoorHanger";
 
-    private GeckoApp mActivity;
     // The popup that holds this doorhanger
     private DoorHangerPopup mPopup;
     private LinearLayout mChoicesLayout;
     private TextView mTextView;
+    private List<PromptInput> mInputs;
+
+    private static int sInputPadding = -1;
+    private static int sSpinnerTextColor = -1;
+    private static int sSpinnerTextSize = -1;
 
     // LayoutParams used for adding button layouts
     static private LayoutParams mLayoutParams;
     private final int mTabId;
     // Value used to identify the notification
     private final String mValue;
 
     // Optional checkbox added underneath message text
@@ -43,23 +56,32 @@ public class DoorHanger extends LinearLa
 
     // Divider between doorhangers.
     private View mDivider;
 
     private int mPersistence = 0;
     private boolean mPersistWhileVisible = false;
     private long mTimeout = 0;
 
-    DoorHanger(GeckoApp aActivity, DoorHangerPopup aPopup, int aTabId, String aValue) {
-        super(aActivity);
+    DoorHanger(Context context, DoorHangerPopup popup, int tabId, String value) {
+        super(context);
+
+        mPopup = popup;
+        mTabId = tabId;
+        mValue = value;
 
-        mActivity = aActivity;
-        mPopup = aPopup;
-        mTabId = aTabId;
-        mValue = aValue;
+        if (sInputPadding == -1) {
+            sInputPadding = getResources().getDimensionPixelSize(R.dimen.doorhanger_padding_spinners);
+        }
+        if (sSpinnerTextColor == -1) {
+            sSpinnerTextColor = getResources().getColor(R.color.text_color_primary_disable_only);
+        }
+        if (sSpinnerTextSize == -1) {
+            sSpinnerTextSize = getResources().getDimensionPixelSize(R.dimen.doorhanger_spinner_textsize);
+        }
     }
  
     int getTabId() {
         return mTabId;
     }
 
     String getValue() {
         return mValue;
@@ -72,17 +94,17 @@ public class DoorHanger extends LinearLa
     public void hideDivider() {
         mDivider.setVisibility(View.GONE);
     }
 
     // Postpone stuff that needs to be done on the main thread
     void init(String message, JSONArray buttons, JSONObject options) {
         setOrientation(VERTICAL);
 
-        LayoutInflater.from(mActivity).inflate(R.layout.doorhanger, this);
+        LayoutInflater.from(getContext()).inflate(R.layout.doorhanger, this);
         setVisibility(View.GONE);
 
         mTextView = (TextView) findViewById(R.id.doorhanger_title);
         mTextView.setText(message);
 
         mChoicesLayout = (LinearLayout) findViewById(R.id.doorhanger_choices);
 
         mDivider = findViewById(R.id.divider_doorhanger);
@@ -109,23 +131,23 @@ public class DoorHanger extends LinearLa
     }
 
     private void addButton(String aText, int aCallback) {
         if (mLayoutParams == null)
             mLayoutParams = new LayoutParams(LayoutParams.FILL_PARENT,
                                              LayoutParams.FILL_PARENT,
                                              1.0f);
 
-        Button button = (Button) LayoutInflater.from(mActivity).inflate(R.layout.doorhanger_button, null);
+        Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
         button.setText(aText);
         button.setTag(Integer.toString(aCallback));
         button.setOnClickListener(this);
 
         if (mChoicesLayout.getChildCount() > 0) {
-            Divider divider = new Divider(mActivity, null);
+            Divider divider = new Divider(getContext(), null);
             divider.setOrientation(Divider.Orientation.VERTICAL);
             divider.setBackgroundColor(0xFFD1D5DA);
             mChoicesLayout.addView(divider);
         }
 
         mChoicesLayout.addView(button, mLayoutParams);
     }
 
@@ -133,30 +155,38 @@ public class DoorHanger extends LinearLa
     public void onClick(View v) {
         JSONObject response = new JSONObject();
         try {
             response.put("callback", v.getTag().toString());
 
             // If the checkbox is being used, pass its value
             if (mCheckBox != null)
                 response.put("checked", mCheckBox.isChecked());
+
+            if (mInputs != null) {
+                JSONObject inputs = new JSONObject();
+                for (PromptInput input : mInputs) {
+                    inputs.put(input.getId(), input.getValue());
+                }
+                response.put("inputs", inputs);
+            }
         } catch (JSONException e) {
             Log.e(LOGTAG, "Error creating onClick response", e);
         }
 
         GeckoEvent e = GeckoEvent.createBroadcastEvent("Doorhanger:Reply", response.toString());
         GeckoAppShell.sendEventToGecko(e);
         mPopup.removeDoorHanger(this);
 
         // This will hide the doorhanger (and hide the popup if there are no
         // more doorhangers to show)
         mPopup.updatePopup();
     }
 
-    private void setOptions(JSONObject options) {
+    private void setOptions(final JSONObject options) {
         try {
             mPersistence = options.getInt("persistence");
         } catch (JSONException e) { }
 
         try {
             mPersistWhileVisible = options.getBoolean("persistWhileVisible");
         } catch (JSONException e) { }
 
@@ -181,24 +211,95 @@ public class DoorHanger extends LinearLa
             ForegroundColorSpan colorSpan = new ForegroundColorSpan(mTextView.getCurrentTextColor());
             titleWithLink.setSpan(colorSpan, 0, title.length(), 0);
 
             titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0);
             mTextView.setText(titleWithLink);
             mTextView.setMovementMethod(LinkMovementMethod.getInstance());
         } catch (JSONException e) { }
 
+        final JSONArray inputs = options.optJSONArray("inputs");
+        if (inputs != null) {
+            mInputs = new ArrayList<PromptInput>();
+
+            final ViewGroup group = (ViewGroup) findViewById(R.id.doorhanger_inputs);
+            group.setVisibility(VISIBLE);
+
+            for (int i = 0; i < inputs.length(); i++) {
+                try {
+                    PromptInput input = PromptInput.getInput(inputs.getJSONObject(i));
+                    mInputs.add(input);
+
+                    View v = input.getView(getContext());
+                    styleInput(input, v);
+                    group.addView(v);
+                } catch(JSONException ex) { }
+            }
+        }
+
         try {
             String checkBoxText = options.getString("checkbox");
             mCheckBox = (CheckBox) findViewById(R.id.doorhanger_checkbox);
             mCheckBox.setText(checkBoxText);
             mCheckBox.setVisibility(VISIBLE);
         } catch (JSONException e) { }
     }
 
+    private void styleInput(PromptInput input, View view) {
+        if (input instanceof PromptInput.MenulistInput) {
+            styleSpinner(input, view);
+        } else {
+            // add some top and bottom padding to separate inputs
+            view.setPadding(0, sInputPadding,
+                            0, sInputPadding);
+        }
+    }
+
+    private void styleSpinner(PromptInput input, View view) {
+        PromptInput.MenulistInput spinInput = (PromptInput.MenulistInput) input;
+
+        /* Spinners have some intrinsic padding. To force the spinner's text to line up with
+         * the doorhanger text, we have to take that padding into account.
+         * 
+         * |-----A-------| <-- Normal doorhanger message
+         * |-B-|---C+D---| <-- (optional) Spinner Label
+         * |-B-|-C-|--D--| <-- Spinner
+         *
+         * A - Desired padding (sInputPadding)
+         * B - Final padding applied to input element (sInputPadding - rect.left - textPadding).
+         * C - Spinner background drawable padding (rect.left).
+         * D - Spinner inner TextView padding (textPadding).
+         */
+
+        // First get the padding of the selected view inside the spinner. Since the spinner
+        // hasn't been shown yet, we get this view directly from the adapter.
+        Spinner spinner = spinInput.spinner;
+        SpinnerAdapter adapter = spinner.getAdapter();
+        View dropView = adapter.getView(0, null, spinner);
+        int textPadding = 0;
+        if (dropView != null) {
+            textPadding = dropView.getPaddingLeft();
+        }
+
+        // Then get the intrinsic padding built into the background image of the spinner.
+        Rect rect = new Rect();
+        spinner.getBackground().getPadding(rect);
+
+        // Set the difference in padding to the spinner view to align it with doorhanger text.
+        view.setPadding(sInputPadding - rect.left - textPadding, 0, rect.right, sInputPadding);
+
+        if (spinInput.textView != null) {
+            spinInput.textView.setTextColor(sSpinnerTextColor);
+            spinInput.textView.setTextSize(sSpinnerTextSize);
+
+            // If this spinner has a label, offset it to also be aligned with the doorhanger text.
+            spinInput.textView.setPadding(rect.left + textPadding, 0, 0, 0);
+        }
+    }
+
     // This method checks with persistence and timeout options to see if
     // it's okay to remove a doorhanger.
     boolean shouldRemove() {
         if (mPersistWhileVisible && mPopup.isShowing()) {
             // We still want to decrement mPersistence, even if the popup is showing
             if (mPersistence != 0)
                 mPersistence--;
             return false;
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1851,23 +1851,24 @@ abstract public class GeckoApp
         Tabs.getInstance().detachFromActivity(this);
 
         if (SmsManager.getInstance() != null) {
             SmsManager.getInstance().stop();
             if (isFinishing())
                 SmsManager.getInstance().shutdown();
         }
 
+        if (mHealthRecorder != null) {
+            mHealthRecorder.close();
+            mHealthRecorder = null;
+        }
+
         super.onDestroy();
 
         Tabs.unregisterOnTabsChangedListener(this);
-        if (mHealthRecorder != null) {
-            mHealthRecorder.close(GeckoAppShell.getEventDispatcher());
-            mHealthRecorder = null;
-        }
     }
 
     protected void registerEventListener(String event) {
         GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
     }
 
     protected void unregisterEventListener(String event) {
         GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -107,16 +107,17 @@ FENNEC_JAVA_FILES = \
   GeckoProfile.java \
   GeckoSmsManager.java \
   GeckoThread.java \
   GeckoJavaSampler.java \
   GlobalHistory.java \
   GeckoViewsFactory.java \
   GeckoView.java \
   health/BrowserHealthRecorder.java \
+  health/BrowserHealthReporter.java \
   InputMethods.java \
   JavaAddonManager.java \
   LightweightTheme.java \
   LightweightThemeDrawable.java \
   LinkPreference.java \
   MemoryMonitor.java \
   MotionEventInterceptor.java \
   MultiChoicePreference.java \
@@ -125,16 +126,17 @@ FENNEC_JAVA_FILES = \
   NotificationService.java \
   NSSBridge.java \
   OrderedBroadcastHelper.java \
   PrefsHelper.java \
   PrivateDataPreference.java \
   PrivateTab.java \
   ProfileMigrator.java \
   Prompt.java \
+  PromptInput.java \
   PromptService.java \
   Restarter.java \
   SearchEngine.java \
   sqlite/ByteBufferInputStream.java \
   sqlite/MatrixBlobCursor.java \
   sqlite/SQLiteBridge.java \
   sqlite/SQLiteBridgeException.java \
   ReaderModeUtils.java \
--- a/mobile/android/base/Prompt.java
+++ b/mobile/android/base/Prompt.java
@@ -100,196 +100,16 @@ public class Prompt implements OnClickLi
             mIconTextPadding = (int) (res.getDimension(R.dimen.prompt_service_icon_text_padding));
             mIconSize = (int) (res.getDimension(R.dimen.prompt_service_icon_size));
             mInputPaddingSize = (int) (res.getDimension(R.dimen.prompt_service_inputs_padding));
             mMinRowSize = (int) (res.getDimension(R.dimen.prompt_service_min_list_item_height));
             mInitialized = true;
         }
     }
 
-    private static String formatDateString(String dateFormat, Calendar calendar) {
-        return new SimpleDateFormat(dateFormat).format(calendar.getTime());
-    }
-
-    private class PromptInput {
-        private final JSONObject mJSONInput;
-
-        private final String mLabel;
-        private final String mType;
-        private final String mId;
-        private final String mHint;
-        private final boolean mAutofocus;
-        private final String mValue;
-
-        private View mView;
-
-        public PromptInput(JSONObject aJSONInput) {
-            mJSONInput = aJSONInput;
-            mLabel = getSafeString(aJSONInput, "label");
-            mType = getSafeString(aJSONInput, "type");
-            String id = getSafeString(aJSONInput, "id");
-            mId = TextUtils.isEmpty(id) ? mType : id;
-            mHint = getSafeString(aJSONInput, "hint");
-            mValue = getSafeString(aJSONInput, "value");
-            mAutofocus = getSafeBool(aJSONInput, "autofocus");
-        }
-
-        public View getView() throws UnsupportedOperationException {
-            if (mType.equals("checkbox")) {
-                CheckBox checkbox = new CheckBox(mContext);
-                checkbox.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
-                checkbox.setText(mLabel);
-                checkbox.setChecked(getSafeBool(mJSONInput, "checked"));
-                mView = (View)checkbox;
-            } else if (mType.equals("date")) {
-                try {
-                    DateTimePicker input = new DateTimePicker(mContext, "yyyy-MM-dd", mValue,
-                                                              DateTimePicker.PickersState.DATE);
-                    input.toggleCalendar(true);
-                    mView = (View)input;
-                } catch (UnsupportedOperationException ex) {
-                    // We can't use our custom version of the DatePicker widget because the sdk is too old.
-                    // But we can fallback on the native one.
-                    DatePicker input = new DatePicker(mContext);
-                    try {
-                        if (!TextUtils.isEmpty(mValue)) {
-                            GregorianCalendar calendar = new GregorianCalendar();
-                            calendar.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(mValue));
-                            input.updateDate(calendar.get(Calendar.YEAR),
-                                             calendar.get(Calendar.MONTH),
-                                             calendar.get(Calendar.DAY_OF_MONTH));
-                        }
-                    } catch (Exception e) {
-                        Log.e(LOGTAG, "error parsing format string: " + e);
-                    }
-                    mView = (View)input;
-                }
-            } else if (mType.equals("week")) {
-                DateTimePicker input = new DateTimePicker(mContext, "yyyy-'W'ww", mValue,
-                                                          DateTimePicker.PickersState.WEEK);
-                mView = (View)input;
-            } else if (mType.equals("time")) {
-                TimePicker input = new TimePicker(mContext);
-                input.setIs24HourView(DateFormat.is24HourFormat(mContext));
-
-                GregorianCalendar calendar = new GregorianCalendar();
-                if (!TextUtils.isEmpty(mValue)) {
-                    try {
-                        calendar.setTime(new SimpleDateFormat("kk:mm").parse(mValue));
-                    } catch (Exception e) { }
-                }
-                input.setCurrentHour(calendar.get(GregorianCalendar.HOUR_OF_DAY));
-                input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE));
-                mView = (View)input;
-            } else if (mType.equals("datetime-local") || mType.equals("datetime")) {
-                DateTimePicker input = new DateTimePicker(mContext, "yyyy-MM-dd kk:mm", mValue,
-                                                          DateTimePicker.PickersState.DATETIME);
-                input.toggleCalendar(true);
-                mView = (View)input;
-            } else if (mType.equals("month")) {
-                DateTimePicker input = new DateTimePicker(mContext, "yyyy-MM", mValue,
-                                                          DateTimePicker.PickersState.MONTH);
-                mView = (View)input;
-            } else if (mType.equals("textbox") || mType.equals("password")) {
-                EditText input = new EditText(mContext);
-                int inputtype = InputType.TYPE_CLASS_TEXT;
-                if (mType.equals("password")) {
-                    inputtype |= InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
-                }
-                input.setInputType(inputtype);
-                input.setText(mValue);
-
-                if (!TextUtils.isEmpty(mHint)) {
-                    input.setHint(mHint);
-                }
-
-                if (mAutofocus) {
-                    input.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-                        @Override
-                        public void onFocusChange(View v, boolean hasFocus) {
-                            if (hasFocus) {
-                                ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(v, 0);
-                            }
-                        }
-                    });
-                    input.requestFocus();
-                }
-
-                mView = (View)input;
-            } else if (mType.equals("menulist")) {
-                Spinner spinner = new Spinner(mContext);
-                try {
-                    String[] listitems = getStringArray(mJSONInput, "values");
-                    if (listitems.length > 0) {
-                        ArrayAdapter<String> adapter = new ArrayAdapter<String>(mContext, R.layout.simple_dropdown_item_1line, listitems);
-                        spinner.setAdapter(adapter);
-                        int selectedIndex = getSafeInt(mJSONInput, "selected");
-                        spinner.setSelection(selectedIndex);
-                    }
-                } catch(Exception ex) { }
-                mView = (View)spinner;
-            } else if (mType.equals("label")) {
-                // not really an input, but a way to add labels and such to the dialog
-                TextView view = new TextView(mContext);
-                view.setText(Html.fromHtml(mLabel));
-                mView = view;
-            }
-            return mView;
-        }
-
-        public String getId() {
-            return mId;
-        }
-
-        public String getValue() {
-            if (mType.equals("checkbox")) {
-                CheckBox checkbox = (CheckBox)mView;
-                return checkbox.isChecked() ? "true" : "false";
-            } else if (mType.equals("textbox") || mType.equals("password")) {
-                EditText edit = (EditText)mView;
-                return edit.getText().toString();
-            } else if (mType.equals("menulist")) {
-                Spinner spinner = (Spinner)mView;
-                return Integer.toString(spinner.getSelectedItemPosition());
-            } else if (mType.equals("time")) {
-                TimePicker tp = (TimePicker)mView;
-                GregorianCalendar calendar =
-                    new GregorianCalendar(0,0,0,tp.getCurrentHour(),tp.getCurrentMinute());
-                return formatDateString("kk:mm",calendar);
-            } else if (mType.equals("label")) {
-                return "";
-            } else if (android.os.Build.VERSION.SDK_INT < 11 && mType.equals("date")) {
-                // We can't use the custom DateTimePicker with a sdk older than 11.
-                // Fallback on the native DatePicker.
-                DatePicker dp = (DatePicker)mView;
-                GregorianCalendar calendar =
-                    new GregorianCalendar(dp.getYear(),dp.getMonth(),dp.getDayOfMonth());
-                return formatDateString("yyyy-MM-dd",calendar);
-            } else {
-                DateTimePicker dp = (DateTimePicker)mView;
-                GregorianCalendar calendar = new GregorianCalendar();
-                calendar.setTimeInMillis(dp.getTimeInMillis());
-                if (mType.equals("date")) {
-                    return formatDateString("yyyy-MM-dd",calendar);
-                } else if (mType.equals("week")) {
-                    return formatDateString("yyyy-'W'ww",calendar);
-                } else if (mType.equals("datetime-local")) {
-                    return formatDateString("yyyy-MM-dd kk:mm",calendar);
-                } else if (mType.equals("datetime")) {
-                    calendar.set(GregorianCalendar.ZONE_OFFSET,0);
-                    calendar.setTimeInMillis(dp.getTimeInMillis());
-                    return formatDateString("yyyy-MM-dd kk:mm",calendar);
-                } else if (mType.equals("month")) {
-                    return formatDateString("yyyy-MM",calendar);
-                }
-            }
-            return "";
-        }
-    }
-
     private View applyInputStyle(View view) {
         view.setPadding(mInputPaddingSize, 0, mInputPaddingSize, 0);
         return view;
     }
 
     public void show(JSONObject message) {
         processMessage(message);
     }
@@ -343,32 +163,32 @@ public class Prompt implements OnClickLi
                 }
             } else {
                 builder.setAdapter(adapter, this);
                 mSelected = null;
             }
         } else if (length == 1) {
             try {
                 ScrollView view = new ScrollView(mContext);
-                view.addView(mInputs[0].getView());
+                view.addView(mInputs[0].getView(mContext));
                 builder.setView(applyInputStyle(view));
             } catch(UnsupportedOperationException ex) {
                 // We cannot display these input widgets with this sdk version,
                 // do not display any dialog and finish the prompt now.
                 try {
                     finishDialog(new JSONObject("{\"button\": -1}"));
                 } catch(JSONException e) { }
                 return;
             }
         } else if (length > 1) {
             try {
                 LinearLayout linearLayout = new LinearLayout(mContext);
                 linearLayout.setOrientation(LinearLayout.VERTICAL);
                 for (int i = 0; i < length; i++) {
-                    View content = mInputs[i].getView();
+                    View content = mInputs[i].getView(mContext);
                     linearLayout.addView(content);
                 }
                 ScrollView view = new ScrollView(mContext);
                 view.addView(linearLayout);
                 builder.setView(applyInputStyle(view));
             } catch(UnsupportedOperationException ex) {
                 // We cannot display these input widgets with this sdk version,
                 // do not display any dialog and finish the prompt now.
@@ -481,23 +301,23 @@ public class Prompt implements OnClickLi
         mGuid = getSafeString(geckoObject, "guid");
 
         mButtons = getStringArray(geckoObject, "buttons");
 
         JSONArray inputs = getSafeArray(geckoObject, "inputs");
         mInputs = new PromptInput[inputs.length()];
         for (int i = 0; i < mInputs.length; i++) {
             try {
-                mInputs[i] = new PromptInput(inputs.getJSONObject(i));
+                mInputs[i] = PromptInput.getInput(inputs.getJSONObject(i));
             } catch(Exception ex) { }
         }
 
         PromptListItem[] menuitems = getListItemArray(geckoObject, "listitems");
         mSelected = getBooleanArray(geckoObject, "selected");
-        boolean multiple = getSafeBool(geckoObject, "multiple");
+        boolean multiple = geckoObject.optBoolean("multiple");
         show(title, text, menuitems, multiple);
     }
 
     private static String getSafeString(JSONObject json, String key) {
         try {
             return json.getString(key);
         } catch (Exception e) {
             return "";
@@ -523,29 +343,29 @@ public class Prompt implements OnClickLi
     private static int getSafeInt(JSONObject json, String key ) {
         try {
             return json.getInt(key);
         } catch (Exception e) {
             return 0;
         }
     }
 
-    private String[] getStringArray(JSONObject aObject, String aName) {
+    public static String[] getStringArray(JSONObject aObject, String aName) {
         JSONArray items = getSafeArray(aObject, aName);
         int length = items.length();
         String[] list = new String[length];
         for (int i = 0; i < length; i++) {
             try {
                 list[i] = items.getString(i);
             } catch(Exception ex) { }
         }
         return list;
     }
 
-    private boolean[] getBooleanArray(JSONObject aObject, String aName) {
+    private static boolean[] getBooleanArray(JSONObject aObject, String aName) {
         JSONArray items = new JSONArray();
         try {
             items = aObject.getJSONArray(aName);
         } catch(Exception ex) { return null; }
         int length = items.length();
         boolean[] list = new boolean[length];
         for (int i = 0; i < length; i++) {
             try {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/PromptInput.java
@@ -0,0 +1,353 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import org.mozilla.gecko.util.GeckoEventResponder;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.widget.DateTimePicker;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Build;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CheckedTextView;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.TimePicker;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.Log;
+import android.text.Html;
+import android.widget.ArrayAdapter;
+import android.view.ViewGroup.LayoutParams;
+import android.view.inputmethod.InputMethodManager;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
+
+class PromptInput {
+    private final JSONObject mJSONInput;
+
+    protected final String mLabel;
+    protected final String mType;
+    protected final String mId;
+    protected final String mValue;
+    protected View mView;
+    public static final String LOGTAG = "GeckoPromptInput";
+
+    public static class EditInput extends PromptInput {
+        protected final String mHint;
+        protected final boolean mAutofocus;
+        public static final String INPUT_TYPE = "textbox";
+
+        public EditInput(JSONObject object) {
+            super(object);
+            mHint = object.optString("hint");
+            mAutofocus = object.optBoolean("autofocus");
+        }
+
+        public View getView(final Context context) throws UnsupportedOperationException {
+            EditText input = new EditText(context);
+            input.setInputType(InputType.TYPE_CLASS_TEXT);
+            input.setText(mValue);
+
+            if (!TextUtils.isEmpty(mHint)) {
+                input.setHint(mHint);
+            }
+
+            if (mAutofocus) {
+                input.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+                    @Override
+                    public void onFocusChange(View v, boolean hasFocus) {
+                        if (hasFocus) {
+                            ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(v, 0);
+                        }
+                    }
+                });
+                input.requestFocus();
+            }
+
+            mView = (View)input;
+            return mView;
+        }
+
+        public String getValue() {
+            EditText edit = (EditText)mView;
+            return edit.getText().toString();
+        }
+    }
+
+    public static class PasswordInput extends EditInput {
+        public static final String INPUT_TYPE = "password";
+        public PasswordInput(JSONObject obj) {
+            super(obj);
+        }
+
+        public View getView(Context context) throws UnsupportedOperationException {
+            EditText input = (EditText) super.getView(context);
+            input.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+            return input;
+        }
+
+        public String getValue() {
+            EditText edit = (EditText)mView;
+            return edit.getText().toString();
+        }
+    }
+
+    public static class CheckboxInput extends PromptInput {
+        public static final String INPUT_TYPE = "checkbox";
+        private boolean mChecked;
+
+        public CheckboxInput(JSONObject obj) {
+            super(obj);
+            mChecked = obj.optBoolean("checked");
+        }
+
+        public View getView(Context context) throws UnsupportedOperationException {
+            CheckBox checkbox = new CheckBox(context);
+            checkbox.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
+            checkbox.setText(mLabel);
+            checkbox.setChecked(mChecked);
+            mView = (View)checkbox;
+            return mView;
+        }
+
+        public String getValue() {
+            CheckBox checkbox = (CheckBox)mView;
+            return checkbox.isChecked() ? "true" : "false";
+        }
+    }
+
+    public static class DateTimeInput extends PromptInput {
+        public static final String[] INPUT_TYPES = new String[] {
+            "date",
+            "week",
+            "time",
+            "datetime-local",
+            "month"
+        };
+
+        public DateTimeInput(JSONObject obj) {
+            super(obj);
+        }
+
+        public View getView(Context context) throws UnsupportedOperationException {
+            if (mType.equals("date")) {
+                try {
+                    DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd", mValue,
+                                                              DateTimePicker.PickersState.DATE);
+                    input.toggleCalendar(true);
+                    mView = (View)input;
+                } catch (UnsupportedOperationException ex) {
+                    // We can't use our custom version of the DatePicker widget because the sdk is too old.
+                    // But we can fallback on the native one.
+                    DatePicker input = new DatePicker(context);
+                    try {
+                        if (!TextUtils.isEmpty(mValue)) {
+                            GregorianCalendar calendar = new GregorianCalendar();
+                            calendar.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(mValue));
+                            input.updateDate(calendar.get(Calendar.YEAR),
+                                             calendar.get(Calendar.MONTH),
+                                             calendar.get(Calendar.DAY_OF_MONTH));
+                        }
+                    } catch (Exception e) {
+                        Log.e(LOGTAG, "error parsing format string: " + e);
+                    }
+                    mView = (View)input;
+                }
+            } else if (mType.equals("week")) {
+                DateTimePicker input = new DateTimePicker(context, "yyyy-'W'ww", mValue,
+                                                          DateTimePicker.PickersState.WEEK);
+                mView = (View)input;
+            } else if (mType.equals("time")) {
+                TimePicker input = new TimePicker(context);
+                input.setIs24HourView(DateFormat.is24HourFormat(context));
+
+                GregorianCalendar calendar = new GregorianCalendar();
+                if (!TextUtils.isEmpty(mValue)) {
+                    try {
+                        calendar.setTime(new SimpleDateFormat("kk:mm").parse(mValue));
+                    } catch (Exception e) { }
+                }
+                input.setCurrentHour(calendar.get(GregorianCalendar.HOUR_OF_DAY));
+                input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE));
+                mView = (View)input;
+            } else if (mType.equals("datetime-local") || mType.equals("datetime")) {
+                DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd kk:mm", mValue,
+                                                          DateTimePicker.PickersState.DATETIME);
+                input.toggleCalendar(true);
+                mView = (View)input;
+            } else if (mType.equals("month")) {
+                DateTimePicker input = new DateTimePicker(context, "yyyy-MM", mValue,
+                                                          DateTimePicker.PickersState.MONTH);
+                mView = (View)input;
+            }
+            return mView;
+        }
+
+        private static String formatDateString(String dateFormat, Calendar calendar) {
+            return new SimpleDateFormat(dateFormat).format(calendar.getTime());
+        }
+
+        public String getValue() {
+            if (Build.VERSION.SDK_INT < 11 && mType.equals("date")) {
+                // We can't use the custom DateTimePicker with a sdk older than 11.
+                // Fallback on the native DatePicker.
+                DatePicker dp = (DatePicker)mView;
+                GregorianCalendar calendar =
+                    new GregorianCalendar(dp.getYear(),dp.getMonth(),dp.getDayOfMonth());
+                return formatDateString("yyyy-MM-dd",calendar);
+            } else if (mType.equals("time")) {
+                TimePicker tp = (TimePicker)mView;
+                GregorianCalendar calendar =
+                    new GregorianCalendar(0,0,0,tp.getCurrentHour(),tp.getCurrentMinute());
+                return formatDateString("kk:mm",calendar);
+            } else {
+                DateTimePicker dp = (DateTimePicker)mView;
+                GregorianCalendar calendar = new GregorianCalendar();
+                calendar.setTimeInMillis(dp.getTimeInMillis());
+                if (mType.equals("date")) {
+                    return formatDateString("yyyy-MM-dd",calendar);
+                } else if (mType.equals("week")) {
+                    return formatDateString("yyyy-'W'ww",calendar);
+                } else if (mType.equals("datetime-local")) {
+                    return formatDateString("yyyy-MM-dd kk:mm",calendar);
+                } else if (mType.equals("datetime")) {
+                    calendar.set(GregorianCalendar.ZONE_OFFSET,0);
+                    calendar.setTimeInMillis(dp.getTimeInMillis());
+                    return formatDateString("yyyy-MM-dd kk:mm",calendar);
+                } else if (mType.equals("month")) {
+                    return formatDateString("yyyy-MM",calendar);
+                }
+            }
+            return super.getValue();
+        }
+    }
+
+    public static class MenulistInput extends PromptInput {
+        public static final String INPUT_TYPE = "menulist";
+        private static String[] mListitems;
+        private static int mSelected;
+
+        public Spinner spinner;
+        public AllCapsTextView textView;
+
+        public MenulistInput(JSONObject obj) {
+            super(obj);
+            mListitems = Prompt.getStringArray(obj, "values");
+            mSelected = obj.optInt("selected");
+        }
+
+        public View getView(final Context context) throws UnsupportedOperationException {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+                spinner = new Spinner(context);
+            } else {
+                spinner = new Spinner(context, Spinner.MODE_DIALOG);
+            }
+            try {
+                if (mListitems.length > 0) {
+                    ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item, mListitems);
+                    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+                    spinner.setAdapter(adapter);
+                    spinner.setSelection(mSelected);
+                }
+            } catch(Exception ex) { }
+
+            if (!TextUtils.isEmpty(mLabel)) {
+                LinearLayout container = new LinearLayout(context);
+                container.setOrientation(LinearLayout.VERTICAL);
+
+                textView = new AllCapsTextView(context, null);
+                textView.setText(mLabel);
+                container.addView(textView);
+
+                container.addView(spinner);
+                return container;
+            }
+
+            return spinner;
+        }
+
+        public String getValue() {
+            return Integer.toString(spinner.getSelectedItemPosition());
+        }
+    }
+
+    public static class LabelInput extends PromptInput {
+        public static final String INPUT_TYPE = "label";
+        public LabelInput(JSONObject obj) {
+            super(obj);
+        }
+
+        public View getView(Context context) throws UnsupportedOperationException {
+            // not really an input, but a way to add labels and such to the dialog
+            TextView view = new TextView(context);
+            view.setText(Html.fromHtml(mLabel));
+            mView = view;
+            return mView;
+        }
+
+        public String getValue() {
+            return "";
+        }
+    }
+
+    public PromptInput(JSONObject obj) {
+        mJSONInput = obj;
+        mLabel = obj.optString("label");
+        mType = obj.optString("type");
+        String id = obj.optString("id");
+        mId = TextUtils.isEmpty(id) ? mType : id;
+        mValue = obj.optString("value");
+    }
+
+    public static PromptInput getInput(JSONObject obj) {
+        String type = obj.optString("type");
+        if (EditInput.INPUT_TYPE.equals(type)) {
+            return new EditInput(obj);
+        } else if (PasswordInput.INPUT_TYPE.equals(type)) {
+            return new PasswordInput(obj);
+        } else if (CheckboxInput.INPUT_TYPE.equals(type)) {
+            return new CheckboxInput(obj);
+        } else if (MenulistInput.INPUT_TYPE.equals(type)) {
+            return new MenulistInput(obj);
+        } else if (LabelInput.INPUT_TYPE.equals(type)) {
+            return new LabelInput(obj);
+        } else {
+            for (String dtType : DateTimeInput.INPUT_TYPES) {
+                if (dtType.equals(type)) {
+                    return new DateTimeInput(obj);
+                }
+            }
+        }
+        return null;
+    }
+
+    public View getView(Context context) throws UnsupportedOperationException {
+        return null;
+    }
+
+    public String getId() {
+        return mId;
+    }
+
+    public String getValue() {
+        return "";
+    }
+}
--- a/mobile/android/base/ReaderModeUtils.java
+++ b/mobile/android/base/ReaderModeUtils.java
@@ -70,20 +70,9 @@ public class ReaderModeUtils {
         String aboutReaderUrl = "about:reader?url=" + Uri.encode(url) +
                                 "&readingList=" + (inReadingList ? 1 : 0);
 
         if (tabId >= 0)
             aboutReaderUrl += "&tabId=" + tabId;
 
         return aboutReaderUrl;
     }
-
-    /**
-     * Performs the inverse of getAboutReaderForUrl.
-     */
-    public static String getUrlForAboutReader(String aboutReaderUrl) {
-        String query = Uri.parse(aboutReaderUrl).getQuery();
-
-        // It would be nice if we could use Uri.getQueryParameter, but that
-        // doesn't work because "about:reader" isn't a hierarchical URI.
-        return query.substring(4, query.indexOf("&readingList="));
-    }
 }
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -423,17 +423,17 @@ public class Tab {
         }
 
         GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString());
         GeckoAppShell.sendEventToGecko(e);
     }
 
     public void toggleReaderMode() {
         if (ReaderModeUtils.isAboutReader(mUrl)) {
-            Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlForAboutReader(mUrl));
+            Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl));
         } else if (mReaderEnabled) {
             mEnteringReaderMode = true;
             Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId, mReadingListItem));
         }
     }
 
     public boolean isEnteringReaderMode() {
         return mEnteringReaderMode;
--- a/mobile/android/base/background/healthreport/Environment.java
+++ b/mobile/android/base/background/healthreport/Environment.java
@@ -1,33 +1,46 @@
 /* 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/. */
 
 package org.mozilla.gecko.background.healthreport;
 
-import java.util.ArrayList;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Iterator;
+import java.util.SortedSet;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.common.log.Logger;
 
 /**
  * This captures all of the details that define an 'environment' for FHR's purposes.
  * Whenever this format changes, it'll be changing with a build ID, so no migration
  * of values is needed.
  *
  * Unless you remove the build descriptors from the set, of course.
  *
  * Or store these in a database.
  *
  * Instances of this class should be considered "effectively immutable": control their
  * scope such that clear creation/sharing boundaries exist. Once you've populated and
  * registered an <code>Environment</code>, don't do so again; start from scratch.
  *
  */
 public abstract class Environment {
+  private static final String LOG_TAG = "GeckoEnvironment";
+
   public static int VERSION = 1;
 
+  protected final Class<? extends EnvironmentAppender> appenderClass;
+
   protected volatile String hash = null;
   protected volatile int id = -1;
 
   // org.mozilla.profile.age.
   public int profileCreation;
 
   // org.mozilla.sysinfo.sysinfo.
   public int cpuCount;
@@ -49,57 +62,204 @@ public abstract class Environment {
   public String updateChannel;
 
   // appInfo.
   public int isBlocklistEnabled;
   public int isTelemetryEnabled;
   // public int isDefaultBrowser;        // This is meaningless on Android.
 
   // org.mozilla.addons.active.
-  public final ArrayList<String> addons = new ArrayList<String>();
+  public JSONObject addons = null;
 
   // org.mozilla.addons.counts.
   public int extensionCount;
   public int pluginCount;
   public int themeCount;
 
+  public Environment() {
+    this(Environment.HashAppender.class);
+  }
+
+  public Environment(Class<? extends EnvironmentAppender> appenderClass) {
+    this.appenderClass = appenderClass;
+  }
+
+  public JSONObject getNonIgnoredAddons() {
+    if (addons == null) {
+      return null;
+    }
+    JSONObject out = new JSONObject();
+    @SuppressWarnings("unchecked")
+    Iterator<String> keys = addons.keys();
+    while (keys.hasNext()) {
+      try {
+        final String key = keys.next();
+        final Object obj = addons.get(key);
+        if (obj != null && obj instanceof JSONObject && ((JSONObject) obj).optBoolean("ignore", false)) {
+          continue;
+        }
+        out.put(key, obj);
+      } catch (JSONException ex) {
+        // Do nothing.
+      }
+    }
+    return out;
+  }
+
+  /**
+   * We break out this interface in order to allow for testing -- pass in your
+   * own appender that just records strings, for example.
+   */
+  public static abstract class EnvironmentAppender {
+    public abstract void append(String s);
+    public abstract void append(int v);
+  }
+
+  public static class HashAppender extends EnvironmentAppender {
+    final MessageDigest hasher;
+
+    public HashAppender() throws NoSuchAlgorithmException {
+      // Note to the security minded reader: we deliberately use SHA-1 here, not
+      // a stronger hash. These identifiers don't strictly need a cryptographic
+      // hash function, because there is negligible value in attacking the hash.
+      // We use SHA-1 because it's *shorter* -- the exact same reason that Git
+      // chose SHA-1.
+      hasher = MessageDigest.getInstance("SHA-1");
+    }
+
+    @Override
+    public void append(String s) {
+      try {
+        hasher.update(((s == null) ? "null" : s).getBytes("UTF-8"));
+      } catch (UnsupportedEncodingException e) {
+        // This can never occur. Thanks, Java.
+      }
+    }
+
+    @Override
+    public void append(int profileCreation) {
+      append(Integer.toString(profileCreation, 10));
+    }
+
+    @Override
+    public String toString() {
+      // We *could* use ASCII85… but the savings would be negated by the
+      // inclusion of JSON-unsafe characters like double-quote.
+      return new Base64(-1, null, false).encodeAsString(hasher.digest());
+    }
+  }
+
+  /**
+   * Compute the stable hash of the configured environment.
+   *
+   * @return the hash in base34, or null if there was a problem.
+   */
   public String getHash() {
     // It's never unset, so we only care about partial reads. volatile is enough.
     if (hash != null) {
       return hash;
     }
 
-    StringBuilder b = new StringBuilder();
-    b.append(profileCreation);
-    b.append(cpuCount);
-    b.append(memoryMB);
-    b.append(architecture);
-    b.append(sysName);
-    b.append(sysVersion);
-    b.append(vendor);
-    b.append(appName);
-    b.append(appID);
-    b.append(appVersion);
-    b.append(appBuildID);
-    b.append(platformVersion);
-    b.append(platformBuildID);
-    b.append(os);
-    b.append(xpcomabi);
-    b.append(updateChannel);
-    b.append(isBlocklistEnabled);
-    b.append(isTelemetryEnabled);
-    b.append(extensionCount);
-    b.append(pluginCount);
-    b.append(themeCount);
+    EnvironmentAppender appender;
+    try {
+      appender = appenderClass.newInstance();
+    } catch (InstantiationException ex) {
+      // Should never happen, but...
+      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
+      return null;
+    } catch (IllegalAccessException ex) {
+      // Should never happen, but...
+      Logger.warn(LOG_TAG,  "Could not compute hash.", ex);
+      return null;
+    }
 
-    for (String addon : addons) {
-      b.append(addon);
+    appender.append(profileCreation);
+    appender.append(cpuCount);
+    appender.append(memoryMB);
+    appender.append(architecture);
+    appender.append(sysName);
+    appender.append(sysVersion);
+    appender.append(vendor);
+    appender.append(appName);
+    appender.append(appID);
+    appender.append(appVersion);
+    appender.append(appBuildID);
+    appender.append(platformVersion);
+    appender.append(platformBuildID);
+    appender.append(os);
+    appender.append(xpcomabi);
+    appender.append(updateChannel);
+    appender.append(isBlocklistEnabled);
+    appender.append(isTelemetryEnabled);
+    appender.append(extensionCount);
+    appender.append(pluginCount);
+    appender.append(themeCount);
+
+    // We need sorted values.
+    if (addons != null) {
+      appendSortedAddons(getNonIgnoredAddons(), appender);
     }
 
-    return hash = HealthReportUtils.getEnvironmentHash(b.toString());
+    return hash = appender.toString();
+  }
+
+  /**
+   * Take a collection of add-on descriptors, appending a consistent string
+   * to the provided builder.
+   */
+  public static void appendSortedAddons(JSONObject addons,
+                                        final EnvironmentAppender builder) {
+    final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
+
+    // For each add-on, produce a consistent, sorted mapping of its descriptor.
+    for (String key : keys) {
+      try {
+        JSONObject addon = addons.getJSONObject(key);
+
+        // Now produce the output for this add-on.
+        builder.append(key);
+        builder.append("={");
+
+        for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
+          builder.append(addonKey);
+          builder.append("==");
+          try {
+            builder.append(addon.get(addonKey).toString());
+          } catch (JSONException e) {
+            builder.append("_e_");
+          }
+        }
+
+        builder.append("}");
+      } catch (Exception e) {
+        // Muffle.
+        Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
+      }
+    }
+  }
+
+  public void setJSONForAddons(byte[] json) throws Exception {
+    setJSONForAddons(new String(json, "UTF-8"));
+  }
+
+  public void setJSONForAddons(String json) throws Exception {
+    addons = new JSONObject(json);
+  }
+
+  public void setJSONForAddons(JSONObject json) {
+    addons = json;
+  }
+
+  /**
+   * Includes ignored add-ons.
+   */
+  public String getNormalizedAddonsJSON() {
+    // We trust that our input will already be normalized. If that assumption
+    // is invalidated, then we'll be sorry.
+    return (addons == null) ? "null" : addons.toString();
   }
 
   /**
    * Ensure that the {@link Environment} has been registered with its
    * storage layer, and can be used to annotate events.
    *
    * It's safe to call this method more than once, and each time you'll
    * get the same ID.
--- a/mobile/android/base/background/healthreport/EnvironmentBuilder.java
+++ b/mobile/android/base/background/healthreport/EnvironmentBuilder.java
@@ -1,14 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.background.healthreport;
 
+import java.util.Iterator;
+
+import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.SysInfo;
 import org.mozilla.gecko.background.common.log.Logger;
 
 import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -34,16 +37,17 @@ public class EnvironmentBuilder {
       throw ex;
     }
   }
 
   public static interface ProfileInformationProvider {
     public boolean isBlocklistEnabled();
     public boolean isTelemetryEnabled();
     public long getProfileCreationTime();
+    public JSONObject getAddonsJSON();
   }
 
   protected static void populateEnvironment(Environment e,
                                             ProfileInformationProvider info) {
     e.cpuCount = SysInfo.getCPUCount();
     e.memoryMB = SysInfo.getMemSize();
 
     e.appName = AppConstants.MOZ_APP_NAME;
@@ -64,22 +68,50 @@ public class EnvironmentBuilder {
 
     // Corresponds to Gecko pref "extensions.blocklist.enabled".
     e.isBlocklistEnabled = (info.isBlocklistEnabled() ? 1 : 0);
 
     // Corresponds to one of two Gecko telemetry prefs. We reflect these into
     // GeckoPreferences as "datareporting.telemetry.enabled".
     e.isTelemetryEnabled = (info.isTelemetryEnabled() ? 1 : 0);
 
-    // TODO
     e.extensionCount = 0;
     e.pluginCount = 0;
     e.themeCount = 0;
-    // e.addons;
+
+    JSONObject addons = info.getAddonsJSON();
+    if (addons == null) {
+      return;
+    }
 
+    @SuppressWarnings("unchecked")
+    Iterator<String> it = addons.keys();
+    while (it.hasNext()) {
+      String key = it.next();
+      try {
+        JSONObject addon = addons.getJSONObject(key);
+        String type = addon.optString("type");
+        Logger.pii(LOG_TAG, "Add-on " + key + " is a " + type);
+        if ("extension".equals(type)) {
+          ++e.extensionCount;
+        } else if ("plugin".equals(type)) {
+          ++e.pluginCount;
+        } else if ("theme".equals(type)) {
+          ++e.themeCount;
+        } else if ("service".equals(type)) {
+          // Later.
+        } else {
+          Logger.debug(LOG_TAG, "Unknown add-on type: " + type);
+        }
+      } catch (Exception ex) {
+        Logger.warn(LOG_TAG, "Failed to process add-on " + key, ex);
+      }
+    }
+
+    e.addons = addons;
   }
 
   /**
    * Returns an {@link Environment} not linked to a storage instance, but
    * populated with current field values.
    *
    * @param info a source of profile data
    * @return the new {@link Environment}
@@ -97,11 +129,13 @@ public class EnvironmentBuilder {
 
   /**
    * @return the current environment's ID in the provided storage layer
    */
   public static int registerCurrentEnvironment(HealthReportDatabaseStorage storage,
                                                ProfileInformationProvider info) {
     Environment e = storage.getEnvironment();
     populateEnvironment(e, info);
-    return e.register();
+    e.register();
+    Logger.debug(LOG_TAG, "Registering current environment: " + e.getHash() + " = " + e.id);
+    return e.id;
   }
 }
--- a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
+++ b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
@@ -128,26 +128,27 @@ public class HealthReportDatabaseStorage
       "id", "hash",
       "profileCreation", "cpuCount", "memoryMB",
 
       "isBlocklistEnabled", "isTelemetryEnabled", "extensionCount",
       "pluginCount", "themeCount",
 
       "architecture", "sysName", "sysVersion", "vendor", "appName", "appID",
       "appVersion", "appBuildID", "platformVersion", "platformBuildID", "os",
-      "xpcomabi", "updateChannel"
+      "xpcomabi", "updateChannel",
+
+      // Joined to the add-ons table.
+      "addonsBody"
   };
 
   public static final String[] COLUMNS_MEASUREMENT_DETAILS = new String[] {"id", "name", "version"};
   public static final String[] COLUMNS_MEASUREMENT_AND_FIELD_DETAILS =
       new String[] {"measurement_name", "measurement_id", "measurement_version",
                     "field_name", "field_id", "field_flags"};
 
-  private static final String[] ENVIRONMENT_RECORD_COLUMNS = null;
-
   private static final String[] COLUMNS_VALUE = new String[] {"value"};
   private static final String[] COLUMNS_ID = new String[] {"id"};
 
   private static final String EVENTS_TEXTUAL = "events_textual";
   private static final String EVENTS_INTEGER = "events_integer";
 
   protected static final String DB_NAME = "health.db";
 
@@ -179,17 +180,17 @@ public class HealthReportDatabaseStorage
     this.fields.clear();
     this.envs.clear();
     this.measurementVersions.clear();
   }
 
   protected final HealthReportSQLiteOpenHelper helper;
 
   public static class HealthReportSQLiteOpenHelper extends SQLiteOpenHelper {
-    public static final int CURRENT_VERSION = 2;
+    public static final int CURRENT_VERSION = 3;
     public static final String LOG_TAG = "HealthReportSQL";
 
     /**
      * A little helper to avoid SQLiteOpenHelper misbehaving on Android 2.1.
      * Partly cribbed from
      * <http://stackoverflow.com/questions/5332328/sqliteopenhelper-problem-with-fully-qualified-db-path-name>.
      */
     public static class AbsolutePathContext extends ContextWrapper {
@@ -235,16 +236,21 @@ public class HealthReportDatabaseStorage
         Logger.info(LOG_TAG, "Opening: " + getAbsolutePath(profileDirectory, name));
       }
     }
 
     @Override
     public void onCreate(SQLiteDatabase db) {
       db.beginTransaction();
       try {
+        db.execSQL("CREATE TABLE addons (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+                   "                     body TEXT, " +
+                   "                     UNIQUE (body) " +
+                   ")");
+
         db.execSQL("CREATE TABLE environments (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    "                           hash TEXT, " +
                    "                           profileCreation INTEGER, " +
                    "                           cpuCount        INTEGER, " +
                    "                           memoryMB        INTEGER, " +
                    "                           isBlocklistEnabled INTEGER, " +
                    "                           isTelemetryEnabled INTEGER, " +
                    "                           extensionCount     INTEGER, " +
@@ -258,16 +264,18 @@ public class HealthReportDatabaseStorage
                    "                           appID           TEXT, " +
                    "                           appVersion      TEXT, " +
                    "                           appBuildID      TEXT, " +
                    "                           platformVersion TEXT, " +
                    "                           platformBuildID TEXT, " +
                    "                           os              TEXT, " +
                    "                           xpcomabi        TEXT, " +
                    "                           updateChannel   TEXT, " +
+                   "                           addonsID        INTEGER, " +
+                   "                           FOREIGN KEY (addonsID) REFERENCES addons(id) ON DELETE RESTRICT, " +
                    "                           UNIQUE (hash) " +
                    ")");
 
         db.execSQL("CREATE TABLE measurements (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    "                           name TEXT, " +
                    "                           version INTEGER, " +
                    "                           UNIQUE (name, version) " +
                    ")");
@@ -325,36 +333,97 @@ public class HealthReportDatabaseStorage
                    "       fields.name AS field_name, " +
                    "       fields.id AS field_id, " +
                    "       fields.flags AS field_flags " +
                    "FROM fields JOIN measurements ON fields.measurement = measurements.id");
 
         db.execSQL("CREATE VIEW current_measurements AS " +
                    "SELECT name, MAX(version) AS version FROM measurements GROUP BY name");
 
+        createAddonsEnvironmentsView(db);
+
         db.setTransactionSuccessful();
       } finally {
         db.endTransaction();
       }
     }
 
+    private void createAddonsEnvironmentsView(SQLiteDatabase db) {
+      db.execSQL("CREATE VIEW environments_with_addons AS " +
+          "SELECT e.id AS id, " +
+          "       e.hash AS hash, " +
+          "       e.profileCreation AS profileCreation, " +
+          "       e.cpuCount AS cpuCount, " +
+          "       e.memoryMB AS memoryMB, " +
+          "       e.isBlocklistEnabled AS isBlocklistEnabled, " +
+          "       e.isTelemetryEnabled AS isTelemetryEnabled, " +
+          "       e.extensionCount AS extensionCount, " +
+          "       e.pluginCount AS pluginCount, " +
+          "       e.themeCount AS themeCount, " +
+          "       e.architecture AS architecture, " +
+          "       e.sysName AS sysName, " +
+          "       e.sysVersion AS sysVersion, " +
+          "       e.vendor AS vendor, " +
+          "       e.appName AS appName, " +
+          "       e.appID AS appID, " +
+          "       e.appVersion AS appVersion, " +
+          "       e.appBuildID AS appBuildID, " +
+          "       e.platformVersion AS platformVersion, " +
+          "       e.platformBuildID AS platformBuildID, " +
+          "       e.os AS os, " +
+          "       e.xpcomabi AS xpcomabi, " +
+          "       e.updateChannel AS updateChannel, " +
+          "       addons.body AS addonsBody " +
+          "FROM environments AS e, addons " +
+          "WHERE e.addonsID = addons.id");
+    }
+
+    private void upgradeDatabaseFrom2To3(SQLiteDatabase db) {
+      db.execSQL("CREATE TABLE addons (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+                 "                     body TEXT, " +
+                 "                     UNIQUE (body) " +
+                 ")");
+
+      db.execSQL("ALTER TABLE environments ADD COLUMN addonsID INTEGER REFERENCES addons(id) ON DELETE RESTRICT");
+
+      createAddonsEnvironmentsView(db);
+    }
+
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+      if (oldVersion >= newVersion) {
+        return;
+      }
+
       Logger.info(LOG_TAG, "onUpgrade: from " + oldVersion + " to " + newVersion + ".");
-    }
+      try {
+        db.beginTransaction();
+        switch (oldVersion) {
+        case 2:
+          upgradeDatabaseFrom2To3(db);
+        }
+        db.setTransactionSuccessful();
+      } catch (Exception e) {
+        Logger.error(LOG_TAG, "Failure in onUpgrade.", e);
+        throw new RuntimeException(e);
+      } finally {
+        db.endTransaction();
+      }
+   }
 
     public void deleteEverything() {
       final SQLiteDatabase db = this.getWritableDatabase();
 
       Logger.info(LOG_TAG, "Deleting everything.");
       db.beginTransaction();
       try {
         // Cascade will clear the rest.
         db.delete("measurements", null, null);
         db.delete("environments", null, null);
+        db.delete("addons", null, null);
         db.setTransactionSuccessful();
         Logger.info(LOG_TAG, "Deletion successful.");
       } finally {
         db.endTransaction();
       }
     }
   }
 
@@ -426,17 +495,18 @@ public class HealthReportDatabaseStorage
    */
   public static class DatabaseEnvironment extends Environment {
     protected final HealthReportDatabaseStorage storage;
 
     @Override
     public int register() {
       final String h = getHash();
       if (storage.envs.containsKey(h)) {
-        return storage.envs.get(h);
+        this.id = storage.envs.get(h);
+        return this.id;
       }
 
       // Otherwise, add data and hash to the DB.
       ContentValues v = new ContentValues();
       v.put("hash", h);
       v.put("profileCreation", profileCreation);
       v.put("cpuCount", cpuCount);
       v.put("memoryMB", memoryMB);
@@ -456,40 +526,94 @@ public class HealthReportDatabaseStorage
       v.put("platformVersion", platformVersion);
       v.put("platformBuildID", platformBuildID);
       v.put("os", os);
       v.put("xpcomabi", xpcomabi);
       v.put("updateChannel", updateChannel);
 
       final SQLiteDatabase db = storage.helper.getWritableDatabase();
 
+      // If we're not already, we want all of our inserts to be in a transaction.
+      boolean newTransaction = !db.inTransaction();
+
       // Insert, with a little error handling to populate the cache in case of
       // omission and consequent collision.
       //
+      // We would like to hang a trigger off a view here, and just use that for
+      // inserts. But triggers don't seem to correctly update the last inserted
+      // ID, so Android's insertOrThrow method returns -1.
+      //
+      // Instead, we go without the trigger, simply running the inserts ourselves.
+      //
       // insertWithOnConflict doesn't work as documented: <http://stackoverflow.com/questions/11328877/android-sqllite-on-conflict-ignore-is-ignored-in-ics/11424150>.
       // So we do this the hard way.
       // We presume that almost every get will hit the cache (except for the first, obviously), so we
-      // bias here towards inserts.
+      // bias here towards inserts for the environments.
+      // For add-ons we assume that different environments will share add-ons, so we query first.
+
+      final String addonsJSON = getNormalizedAddonsJSON();
+      if (newTransaction) {
+        db.beginTransaction();
+      }
+
       try {
-        this.id = (int) db.insertOrThrow("environments", null, v);
-        storage.envs.put(h, this.id);
-        return this.id;
-      } catch (SQLException e) {
-        // The inserter should take care of updating `envs`. But if it doesn't...
-        Cursor c = db.query("environments", COLUMNS_ID, "hash = ?", new String[] {hash}, null, null, null);
+        int addonsID = ensureAddons(db, addonsJSON);
+        v.put("addonsID", addonsID);
+
         try {
-          if (!c.moveToFirst()) {
-            throw e;
+          int inserted = (int) db.insertOrThrow("environments", null, v);
+          Logger.debug(LOG_TAG, "Inserted ID: " + inserted + " for hash " + h);
+          if (inserted == -1) {
+            throw new SQLException("Insert returned -1!");
+          }
+          this.id = inserted;
+          storage.envs.put(h, this.id);
+          if (newTransaction) {
+            db.setTransactionSuccessful();
           }
-          this.id = (int) c.getLong(0);
-          storage.envs.put(hash, this.id);
-          return this.id;
-        } finally {
-          c.close();
+          return inserted;
+        } catch (SQLException e) {
+          // The inserter should take care of updating `envs`. But if it
+          // doesn't...
+          Cursor c = db.query("environments", COLUMNS_ID, "hash = ?",
+                              new String[] { h }, null, null, null);
+          try {
+            if (!c.moveToFirst()) {
+              throw e;
+            }
+            this.id = (int) c.getLong(0);
+            Logger.debug(LOG_TAG, "Found " + this.id + " for hash " + h);
+            storage.envs.put(h, this.id);
+            if (newTransaction) {
+              db.setTransactionSuccessful();
+            }
+            return this.id;
+          } finally {
+            c.close();
+          }
         }
+      } finally {
+        if (newTransaction) {
+          db.endTransaction();
+        }
+      }
+    }
+
+    protected static int ensureAddons(SQLiteDatabase db, String json) {
+      Cursor c = db.query("addons", COLUMNS_ID, "body = ?",
+                          new String[] { (json == null) ? "null" : json }, null, null, null);
+      try {
+        if (c.moveToFirst()) {
+          return c.getInt(0);
+        }
+        ContentValues values = new ContentValues();
+        values.put("body", json);
+        return (int) db.insert("addons", null, values);
+      } finally {
+        c.close();
       }
     }
 
     public void init(ContentValues v) {
       profileCreation = v.getAsInteger("profileCreation");
       cpuCount        = v.getAsInteger("cpuCount");
       memoryMB        = v.getAsInteger("memoryMB");
 
@@ -508,16 +632,22 @@ public class HealthReportDatabaseStorage
       appVersion      = v.getAsString("appVersion");
       appBuildID      = v.getAsString("appBuildID");
       platformVersion = v.getAsString("platformVersion");
       platformBuildID = v.getAsString("platformBuildID");
       os              = v.getAsString("os");
       xpcomabi        = v.getAsString("xpcomabi");
       updateChannel   = v.getAsString("updateChannel");
 
+      try {
+        setJSONForAddons(v.getAsString("addonsBody"));
+      } catch (Exception e) {
+        // Nothing we can do.
+      }
+
       this.hash = null;
       this.id = -1;
     }
 
     /**
      * Fill ourselves with data from the DB, then advance the cursor.
      *
      * @param cursor a {@link Cursor} pointing at a record to load.
@@ -547,19 +677,30 @@ public class HealthReportDatabaseStorage
       appVersion      = cursor.getString(i++);
       appBuildID      = cursor.getString(i++);
       platformVersion = cursor.getString(i++);
       platformBuildID = cursor.getString(i++);
       os              = cursor.getString(i++);
       xpcomabi        = cursor.getString(i++);
       updateChannel   = cursor.getString(i++);
 
+      try {
+        setJSONForAddons(cursor.getBlob(i++));
+      } catch (Exception e) {
+        // Nothing we can do.
+      }
+
       return cursor.moveToNext();
     }
 
+    public DatabaseEnvironment(HealthReportDatabaseStorage storage, Class<? extends EnvironmentAppender> appender) {
+      super(appender);
+      this.storage = storage;
+    }
+
     public DatabaseEnvironment(HealthReportDatabaseStorage storage) {
       this.storage = storage;
     }
   }
 
   /**
    * Factory method. Returns a new {@link Environment} that callers can
    * populate and then register.