Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 16 Jan 2015 15:24:32 +0100
changeset 224318 fb5bebd170baed4bcb5f88590bf316142febde4a
parent 224317 120b108aa17634ccc58cc6ddbfac6e2cd4677a17 (current diff)
parent 224200 5438e3f7484802510eaff28a1364e97f492fae61 (diff)
child 224319 b8b515f7197beb3a9b9afd043ba6d0d3dc5d3712
push id54190
push userkwierso@gmail.com
push dateSat, 17 Jan 2015 02:06:29 +0000
treeherdermozilla-inbound@369a8f14ccf8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone38.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 mozilla-central to fx-team
dom/canvas/WebGLUniformInfo.h
js/src/jit/ExecutionMode-inl.h
xpcom/base/nsISupportsObsolete.h
--- a/b2g/components/B2GAppMigrator.js
+++ b/b2g/components/B2GAppMigrator.js
@@ -29,20 +29,22 @@ XPCOMUtils.defineLazyServiceGetter(this,
 function B2GAppMigrator() {
 }
 
 B2GAppMigrator.prototype = {
   classID:         Components.ID('{7211ece0-b458-4635-9afc-f8d7f376ee95}'),
   QueryInterface:  XPCOMUtils.generateQI([Ci.nsIObserver,
                                           Ci.nsISupportsWeakReference]),
   executeBrowserMigration: function() {
-    // The browser db name is hashed the same way everywhere, so it
-    // should be the same on all systems. We should be able to just
-    // hardcode it.
-    let browserDBFileName = "2959517650brreosw.sqlite";
+    if (DEBUG) debug("Executing Browser Migration");
+    // The browser db file and directory names are hashed the same way
+    // everywhere, so it should be the same on all systems. We should
+    // be able to just hardcode it.
+    let browserDBDirName = "2959517650brreosw";
+    let browserDBFileName = browserDBDirName + ".sqlite";
 
     // Storage directories need to be prefixed with the local id of
     // the app
     let browserLocalAppId = appsService.getAppLocalIdByManifestURL("app://browser.gaiamobile.org/manifest.webapp");
     let browserAppStorageDirName = browserLocalAppId + "+f+app+++browser.gaiamobile.org";
 
     // On the phone, the browser db will only be in the old IDB
     // directory, since it only existed up until v2.0. On desktop, it
@@ -52,29 +54,45 @@ B2GAppMigrator.prototype = {
     // create extra directories along the way if they don't already
     // exist.
     let browserDBFile = FileUtils.getDir(kIDBDirType,
                                          ["storage",
                                           "persistent",
                                           browserAppStorageDirName,
                                           "idb"], false, true);
     browserDBFile.append(browserDBFileName);
+    let browserDBDir = FileUtils.getDir(kIDBDirType,
+                                        ["storage",
+                                         "persistent",
+                                         browserAppStorageDirName,
+                                         "idb",
+                                         browserDBDirName
+                                        ], false, true);
 
     if (!browserDBFile.exists()) {
-      if (DEBUG) debug("Browser DB directory " + browserDBFile.path + " does not exist, trying profile location");
+      if (DEBUG) debug("Browser DB " + browserDBFile.path + " does not exist, trying profile location");
       browserDBFile = FileUtils.getDir(kProfileDirType,
                                         ["storage",
                                          "persistent",
                                          browserAppStorageDirName,
                                          "idb"], false, true);
       browserDBFile.append(browserDBFileName);
       if (!browserDBFile.exists()) {
-        if (DEBUG) debug("Browser DB directory " + browserDBFile.path + " does not exist. Cannot copy browser db.");
+        if (DEBUG) debug("Browser DB " + browserDBFile.path + " does not exist. Cannot copy browser db.");
         return;
       }
+      // If we have confirmed we have a DB file, we should also have a
+      // directory.
+      browserDBDir = FileUtils.getDir(kProfileDirType,
+                                      ["storage",
+                                       "persistent",
+                                       browserAppStorageDirName,
+                                       "idb",
+                                       browserDBDirName
+                                      ], false, true);
     }
 
     let systemLocalAppId = appsService.getAppLocalIdByManifestURL("app://system.gaiamobile.org/manifest.webapp");
     let systemAppStorageDirName = systemLocalAppId + "+f+app+++system.gaiamobile.org";
 
     // This check futureproofs the system DB storage directory. It
     // currently exists outside of the profile but will most likely
     // move into the profile at some point.
@@ -97,24 +115,30 @@ B2GAppMigrator.prototype = {
       }
     }
 
     if (DEBUG) {
       debug("Browser DB file exists, copying");
       debug("Browser local id: " + browserLocalAppId + "");
       debug("System local id: " + systemLocalAppId + "");
       debug("Browser DB file path: " + browserDBFile.path + "");
+      debug("Browser DB dir path: " + browserDBDir.path + "");
       debug("System DB directory path: " + systemDBDir.path + "");
     }
 
     try {
       browserDBFile.copyTo(systemDBDir, browserDBFileName);
     } catch (e) {
       debug("File copy caused error! " + e.name);
     }
+    try {
+      browserDBDir.copyTo(systemDBDir, browserDBDirName);
+    } catch (e) {
+      debug("Dir copy caused error! " + e.name);
+    }
     if (DEBUG) debug("Browser DB copied successfully");
   },
 
   observe: function(subject, topic, data) {
     switch (topic) {
       case kMigrationMessageName:
         this.executeBrowserMigration();
         break;
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
@@ -131,12 +131,12 @@
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="libnfcemu" path="external/libnfcemu" remote="b2g" revision="125ccf9bd5986c7728ea44508b3e1d1185ac028b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c5f8d282efe4a4e8b1e31a37300944e338e60e4f"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="f9c93ff3550ce324056d7c062e88fe45ca7dfd43"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="0c557e136bb615923aad0f5c69da4e869523efc5"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="6fa7a4936414ceb4055fd27f7a30e76790f834fb"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e0c735ec89df011ea7dd435087a9045ecff9ff9e">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
@@ -153,13 +153,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="f8bec8a61dc0f2581fa72a31d4144084b47ef7cf"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="9883ea57b0668d8f60dba025d4522dfa69a1fbfa"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="a558dc844bf5144fc38603fd8f4df8d9557052a5"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="57ee1320ed7b4a1a1274d8f3f6c177cd6b9becb2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="12b1977cc704b35f2e9db2bb423fa405348bc2f3"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="985bf15264d865fe7b9c5b45f61c451cbaafa43d"/>
   <project name="platform/system/core" path="system/core" revision="350eac5403124dacb2a5fd9e28ac290a59fc3b8e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="f9c93ff3550ce324056d7c062e88fe45ca7dfd43"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="0c557e136bb615923aad0f5c69da4e869523efc5"/>
   <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="d8952a42771045fca73ec600e2b42a4c7129d723"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="018b44e52b2bac5d3631d559550e88a4b68c6e67"/>
 </manifest>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
@@ -140,13 +140,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="5e110615212302c5d798a3c223dcee458817651c"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="fa9ffd47948eb24466de227e48fe9c4a7c5e7711"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="cd76b19aafd4229ccf83853d02faef8c51ca8b34"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="8a0d0b0d9889ef99c4c6317c810db4c09295f15a"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="c4e2ac95907a5519a0e09f01a0d8e27fec101af0"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="e1eb226fa3ad3874ea7b63c56a9dc7012d7ff3c2"/>
   <project name="platform/system/core" path="system/core" revision="adc485d8755af6a61641d197de7cfef667722580"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="f9c93ff3550ce324056d7c062e88fe45ca7dfd43"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="0c557e136bb615923aad0f5c69da4e869523efc5"/>
   <project name="platform/system/qcom" path="system/qcom" revision="1cdab258b15258b7f9657da70e6f06ebd5a2fc25"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="4ae5df252123591d5b941191790e7abed1bce5a4"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="ce18b47b4a4f93a581d672bbd5cb6d12fe796ca9"/>
 </manifest>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "1764121939b82f4ded4ca30761df48c3841028b0", 
+    "revision": "6de26e62ca7d84d4621406344b53eded6759f6a0", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="1a2a32eda22ef2cd18f57f423a5e7b22a105a6f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="1a2a32eda22ef2cd18f57f423a5e7b22a105a6f8"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
@@ -126,17 +126,17 @@
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Nexus 4 specific things -->
   <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="f9c93ff3550ce324056d7c062e88fe45ca7dfd43"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="0c557e136bb615923aad0f5c69da4e869523efc5"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="b0a528d839cfd9d170d092fe3743b5252b4243a6"/>
   <project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" revision="380945eaa249a2dbdde0daa4c8adb8ca325edba6"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="401e981f51cf047292d101c785be8ec48bf75e8c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="5ae81fcc0d8568a50d0bf8491004bc9485b3a9ae"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="049c281ad212bf528b2af8fc246b0dd0c9f97415"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0024095d96fdccc076e511cfa7a4287cb1e0fd83"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/devtools/shadereditor/test/browser_se_shaders-edit-02.js
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-02.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 ///////////////////
 //
 // Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed. 
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Shader Editor is still waiting for a WebGL context to be created.");
 
 /**
  * Tests if compile or linkage errors are emitted when a shader source
  * gets malformed after being edited.
  */
 
@@ -21,45 +21,51 @@ function ifWebGLSupported() {
   yield promise.all([
     once(gFront, "program-linked"),
     once(panel.panelWin, EVENTS.SOURCES_SHOWN)
   ]);
 
   let vsEditor = yield ShadersEditorsView._getEditor("vs");
   let fsEditor = yield ShadersEditorsView._getEditor("fs");
 
+
   vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
   let [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
 
   ok(error,
     "The new vertex shader source was compiled with errors.");
-  is(error.compile, "",
-    "The compilation status should be empty.");
-  isnot(error.link, "",
-    "The linkage status should not be empty.");
-  is(error.link.split("ERROR").length - 1, 2,
-    "The linkage status contains two errors.");
-  ok(error.link.contains("ERROR: 0:8: 'constructor'"),
-    "A constructor error is contained in the linkage status.");
-  ok(error.link.contains("ERROR: 0:8: 'assign'"),
-    "An assignment error is contained in the linkage status.");
+
+  // The implementation has the choice to defer all compile-time errors to link time.
+  let infoLog = (error.compile != "") ? error.compile : error.link;
+
+  isnot(infoLog, "",
+    "The one of the compile or link info logs should not be empty.");
+  is(infoLog.split("ERROR").length - 1, 2,
+    "The info log status contains two errors.");
+  ok(infoLog.contains("ERROR: 0:8: 'constructor'"),
+    "A constructor error is contained in the info log.");
+  ok(infoLog.contains("ERROR: 0:8: 'assign'"),
+    "An assignment error is contained in the info log.");
+
 
   fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
   [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
 
   ok(error,
     "The new fragment shader source was compiled with errors.");
-  is(error.compile, "",
-    "The compilation status should be empty.");
-  isnot(error.link, "",
-    "The linkage status should not be empty.");
-  is(error.link.split("ERROR").length - 1, 1,
-    "The linkage status contains one error.");
-  ok(error.link.contains("ERROR: 0:6: 'constructor'"),
-    "A constructor error is contained in the linkage status.");
+
+  infoLog = (error.compile != "") ? error.compile : error.link;
+
+  isnot(infoLog, "",
+    "The one of the compile or link info logs should not be empty.");
+  is(infoLog.split("ERROR").length - 1, 1,
+    "The info log contains one error.");
+  ok(infoLog.contains("ERROR: 0:6: 'constructor'"),
+    "A constructor error is contained in the info log.");
+
 
   yield ensurePixelIs(gFront, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
   yield ensurePixelIs(gFront, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
 
   vsEditor.replaceText("vec4", { line: 7, ch: 22 }, { line: 7, ch: 26 });
   [, error] = yield onceSpread(panel.panelWin, EVENTS.SHADER_COMPILED);
   ok(!error, "The new vertex shader source was compiled successfully.");
 
--- a/browser/devtools/shadereditor/test/browser_webgl-actor-test-09.js
+++ b/browser/devtools/shadereditor/test/browser_webgl-actor-test-09.js
@@ -18,26 +18,28 @@ function ifWebGLSupported() {
   let newVertSource = oldVertSource.replace("vec4", "vec3");
 
   try {
     yield vertexShader.compile(newVertSource);
     ok(false, "Vertex shader was compiled with a defective source!");
   } catch (error) {
     ok(error,
       "The new vertex shader source was compiled with errors.");
-    is(error.compile, "",
-      "The compilation status should be empty.");
-    isnot(error.link, "",
-      "The linkage status should not be empty.");
-    is(error.link.split("ERROR").length - 1, 2,
-      "The linkage status contains two errors.");
-    ok(error.link.contains("ERROR: 0:8: 'constructor'"),
-      "A constructor error is contained in the linkage status.");
-    ok(error.link.contains("ERROR: 0:8: 'assign'"),
-      "An assignment error is contained in the linkage status.");
+
+    // The implementation has the choice to defer all compile-time errors to link time.
+    let infoLog = (error.compile != "") ? error.compile : error.link;
+
+    isnot(infoLog, "",
+      "The one of the compile or link info logs should not be empty.");
+    is(infoLog.split("ERROR").length - 1, 2,
+      "The info log contains two errors.");
+    ok(infoLog.contains("ERROR: 0:8: 'constructor'"),
+      "A constructor error is contained in the info log.");
+    ok(infoLog.contains("ERROR: 0:8: 'assign'"),
+      "An assignment error is contained in the info log.");
   }
 
   yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
   yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
   ok(true, "The shader was reverted to the old source.");
 
   let vertSource = yield vertexShader.getText();
   ok(vertSource.contains("vec4(aVertexPosition, 1.0);"),
@@ -47,24 +49,26 @@ function ifWebGLSupported() {
   let newFragSource = oldFragSource.replace("vec3", "vec4");
 
   try {
     yield fragmentShader.compile(newFragSource);
     ok(false, "Fragment shader was compiled with a defective source!");
   } catch (error) {
     ok(error,
       "The new fragment shader source was compiled with errors.");
-    is(error.compile, "",
-      "The compilation status should be empty.");
-    isnot(error.link, "",
-      "The linkage status should not be empty.");
-    is(error.link.split("ERROR").length - 1, 1,
-      "The linkage status contains one error.");
-    ok(error.link.contains("ERROR: 0:6: 'constructor'"),
-      "A constructor error is contained in the linkage status.");
+
+    // The implementation has the choice to defer all compile-time errors to link time.
+    let infoLog = (error.compile != "") ? error.compile : error.link;
+
+    isnot(infoLog, "",
+      "The one of the compile or link info logs should not be empty.");
+    is(infoLog.split("ERROR").length - 1, 1,
+      "The info log contains one error.");
+    ok(infoLog.contains("ERROR: 0:6: 'constructor'"),
+      "A constructor error is contained in the info log.");
   }
 
   yield ensurePixelIs(front, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
   yield ensurePixelIs(front, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
   ok(true, "The shader was reverted to the old source.");
 
   let fragSource = yield fragmentShader.getText();
   ok(fragSource.contains("vec3 vFragmentColor;"),
--- a/configure.in
+++ b/configure.in
@@ -2106,23 +2106,18 @@ ia64*-hpux*)
 *-*linux*)
     # Note: both GNU_CC and INTEL_CC are set when using Intel's C compiler.
     # Similarly for GNU_CXX and INTEL_CXX.
     if test "$INTEL_CC" -o "$INTEL_CXX"; then
         # -Os has been broken on Intel's C/C++ compilers for quite a
         # while; Intel recommends against using it.
         MOZ_OPTIMIZE_FLAGS="-O2"
     elif test "$GNU_CC" -o "$GNU_CXX"; then
-        case $GCC_VERSION in
-        4.5.*)
-            # -Os is broken on gcc 4.5.x we need to tweak it to get good results.
-            MOZ_OPTIMIZE_SIZE_TWEAK="-finline-limit=50"
-        esac
         MOZ_PGO_OPTIMIZE_FLAGS="-O3"
-        MOZ_OPTIMIZE_FLAGS="-Os $MOZ_OPTIMIZE_SIZE_TWEAK"
+        MOZ_OPTIMIZE_FLAGS="-Os"
         if test -z "$CLANG_CC"; then
            MOZ_OPTIMIZE_FLAGS="-freorder-blocks $MOZ_OPTIMIZE_FLAGS"
         fi
     fi
 
     TARGET_NSPR_MDCPUCFG='\"md/_linux.cfg\"'
 
     MOZ_MEMORY=1
@@ -2361,16 +2356,19 @@ ia64*-hpux*)
             if test -z "$CLANG_CL"; then
                 LDFLAGS="$LDFLAGS -SAFESEH"
             fi
         fi
 
         AC_DEFINE(_X86_)
         ;;
     x86_64-*)
+        if test -z "$HAVE_64BIT_BUILD"; then
+            AC_MSG_ERROR([You are targeting 64-bit but using the 32-bit compiler.])
+        fi
         if test -n "$_WIN32_MSVC"; then
             DSO_LDOPTS="$DSO_LDOPTS -MACHINE:X64"
         fi
         AC_DEFINE(_AMD64_)
         ;;
     *)
         AC_DEFINE(_CPU_ARCH_NOT_DEFINED)
         ;;
@@ -2622,17 +2620,17 @@ if test "$GNU_CC" -a "$OS_TARGET" != WIN
   AC_DEFINE(HAVE_VISIBILITY_HIDDEN_ATTRIBUTE)
   AC_DEFINE(HAVE_VISIBILITY_ATTRIBUTE)
   case "$OS_TARGET" in
   Darwin)
     VISIBILITY_FLAGS='-fvisibility=hidden'
     ;;
   *)
     case $GCC_VERSION in
-    4.4*|4.6*)
+    4.6*)
       VISIBILITY_FLAGS='-I$(DIST)/system_wrappers -include $(MOZILLA_DIR)/config/gcc_hidden_dso_handle.h'
       ;;
     *)
       VISIBILITY_FLAGS='-I$(DIST)/system_wrappers -include $(MOZILLA_DIR)/config/gcc_hidden.h'
       ;;
     esac
     WRAP_SYSTEM_INCLUDES=1
     ;;
@@ -6921,17 +6919,16 @@ if test -n "$MOZ_OPTIMIZE"; then
 fi
 fi # COMPILE_ENVIRONMENT
 
 AC_SUBST(MOZ_OPTIMIZE)
 AC_SUBST(MOZ_FRAMEPTR_FLAGS)
 AC_SUBST(MOZ_OPTIMIZE_FLAGS)
 AC_SUBST(MOZ_OPTIMIZE_LDFLAGS)
 AC_SUBST(MOZ_ALLOW_HEAP_EXECUTE_FLAGS)
-AC_SUBST(MOZ_OPTIMIZE_SIZE_TWEAK)
 AC_SUBST(MOZ_PGO)
 AC_SUBST(MOZ_PGO_OPTIMIZE_FLAGS)
 
 dnl ========================================================
 dnl = Enable NS_StackWalk.
 dnl ========================================================
 
 # On Windows, NS_StackWalk will only work correctly if we have frame pointers
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -500,17 +500,17 @@ this.PermissionsTable =  { geolocation: 
                              app: DENY_ACTION,
                              trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "settings:wallpaper.image": {
                              app: DENY_ACTION,
                              trusted: DENY_ACTION,
-                             privileged: PROMPT_ACTION,
+                             privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write"],
                              additional: ["settings-api"]
                            },
                            "engineering-mode": {
                              app: DENY_ACTION,
                              trusted: DENY_ACTION,
                              privileged: DENY_ACTION,
--- a/dom/base/DOMException.cpp
+++ b/dom/base/DOMException.cpp
@@ -717,10 +717,37 @@ DOMException::Create(nsresult aRv)
   nsCString message;
   uint16_t code;
   NSResultToNameAndMessage(aRv, name, message, &code);
   nsRefPtr<DOMException> inst =
     new DOMException(aRv, message, name, code);
   return inst.forget();
 }
 
+bool
+DOMException::Sanitize(JSContext* aCx,
+                       JS::MutableHandle<JS::Value> aSanitizedValue)
+{
+  nsRefPtr<DOMException> retval = this;
+  if (mLocation && !mLocation->CallerSubsumes(aCx)) {
+    nsString message;
+    GetMessageMoz(message);
+    nsString name;
+    GetName(name);
+    retval = new dom::DOMException(nsresult(Result()),
+                                   NS_ConvertUTF16toUTF8(message),
+                                   NS_ConvertUTF16toUTF8(name),
+                                   Code());
+    // Now it's possible that the stack on retval still starts with
+    // stuff aCx is not supposed to touch; it depends on what's on the
+    // stack right this second.  Walk past all of that.
+    while (retval->mLocation && !retval->mLocation->CallerSubsumes(aCx)) {
+      nsCOMPtr<nsIStackFrame> caller;
+      retval->mLocation->GetCaller(getter_AddRefs(caller));
+      retval->mLocation.swap(caller);
+    }
+  }
+
+  return ToJSValue(aCx, retval, aSanitizedValue);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/DOMException.h
+++ b/dom/base/DOMException.h
@@ -151,16 +151,25 @@ public:
 
   // Intentionally shadow the nsXPCException version.
   void GetMessageMoz(nsString& retval);
   void GetName(nsString& retval);
 
   static already_AddRefed<DOMException>
   Create(nsresult aRv);
 
+  // Sanitize() is a workaround for the fact that DOMExceptions can leak stack
+  // information for the first stackframe to callers that should not have access
+  // to it.  To prevent this, we check whether aCx subsumes our first stackframe
+  // and if not hand out a JS::Value for a clone of ourselves.  Otherwise we
+  // hand out a JS::Value for ourselves.
+  //
+  // If the return value is false, an exception was thrown on aCx.
+  bool Sanitize(JSContext* aCx, JS::MutableHandle<JS::Value> aSanitizedValue);
+
 protected:
 
   virtual ~DOMException() {}
 
   nsCString mName;
   nsCString mMessage;
 
   uint16_t mCode;
--- a/dom/base/nsContentList.cpp
+++ b/dom/base/nsContentList.cpp
@@ -203,18 +203,16 @@ NS_GetContentList(nsINode* aRootNode,
   nsContentList* cachedList = sRecentlyUsedContentLists[recentlyUsedCacheIndex];
   if (cachedList && cachedList->MatchesKey(hashKey)) {
     list = cachedList;
     return list.forget();
   }
 
   static const PLDHashTableOps hash_table_ops =
   {
-    PL_DHashAllocTable,
-    PL_DHashFreeTable,
     ContentListHashtableHashKey,
     ContentListHashtableMatchEntry,
     PL_DHashMoveEntryStub,
     PL_DHashClearEntryStub
   };
 
   // Initialize the hashtable if needed.
   if (!gContentListHashTable.ops) {
@@ -315,18 +313,16 @@ GetFuncStringContentList(nsINode* aRootN
                          const nsAString& aString)
 {
   NS_ASSERTION(aRootNode, "content list has to have a root");
 
   nsRefPtr<nsCacheableFuncStringContentList> list;
 
   static const PLDHashTableOps hash_table_ops =
   {
-    PL_DHashAllocTable,
-    PL_DHashFreeTable,
     FuncStringContentListHashtableHashKey,
     FuncStringContentListHashtableMatchEntry,
     PL_DHashMoveEntryStub,
     PL_DHashClearEntryStub
   };
 
   // Initialize the hashtable if needed.
   if (!gFuncStringContentListHashTable.ops) {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -474,18 +474,16 @@ nsContentUtils::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!InitializeEventTable())
     return NS_ERROR_FAILURE;
 
   if (!sEventListenerManagersHash.ops) {
     static const PLDHashTableOps hash_table_ops =
     {
-      PL_DHashAllocTable,
-      PL_DHashFreeTable,
       PL_DHashVoidPtrKeyStub,
       PL_DHashMatchEntryStub,
       PL_DHashMoveEntryStub,
       EventListenerManagerHashClearEntry,
       EventListenerManagerHashInitEntry
     };
 
     PL_DHashTableInit(&sEventListenerManagersHash, &hash_table_ops,
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3967,18 +3967,16 @@ nsDocument::SetSubDocumentFor(Element* a
       }
     }
   } else {
     if (!mSubDocuments) {
       // Create a new hashtable
 
       static const PLDHashTableOps hash_table_ops =
       {
-        PL_DHashAllocTable,
-        PL_DHashFreeTable,
         PL_DHashVoidPtrKeyStub,
         PL_DHashMatchEntryStub,
         PL_DHashMoveEntryStub,
         SubDocClearEntry,
         SubDocInitEntry
       };
 
       mSubDocuments = PL_NewDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
--- a/dom/base/nsScriptNameSpaceManager.cpp
+++ b/dom/base/nsScriptNameSpaceManager.cpp
@@ -315,18 +315,16 @@ nsScriptNameSpaceManager::RegisterInterf
 
 #define GLOBALNAME_HASHTABLE_INITIAL_LENGTH   512
 
 nsresult
 nsScriptNameSpaceManager::Init()
 {
   static const PLDHashTableOps hash_table_ops =
   {
-    PL_DHashAllocTable,
-    PL_DHashFreeTable,
     GlobalNameHashHashKey,
     GlobalNameHashMatchEntry,
     PL_DHashMoveEntryStub,
     GlobalNameHashClearEntry,
     GlobalNameHashInitEntry
   };
 
   mIsInitialized = PL_DHashTableInit(&mGlobalNames, &hash_table_ops,
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -229,30 +229,21 @@ ErrorResult::ReportJSExceptionFromJSImpl
 {
   MOZ_ASSERT(!mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to handle JS exceptions?");
 
   dom::DOMException* domException;
   nsresult rv =
     UNWRAP_OBJECT(DOMException, &mJSException.toObject(), domException);
   if (NS_SUCCEEDED(rv)) {
-    // We actually have to create a new DOMException object, because the one we
+    // We may have to create a new DOMException object, because the one we
     // have has a stack that includes the chrome code that threw it, and in
     // particular has the wrong file/line/column information.
-    nsString message;
-    domException->GetMessageMoz(message);
-    nsString name;
-    domException->GetName(name);
-    nsRefPtr<dom::DOMException> newException =
-      new dom::DOMException(nsresult(domException->Result()),
-                            NS_ConvertUTF16toUTF8(message),
-                            NS_ConvertUTF16toUTF8(name),
-                            domException->Code());
     JS::Rooted<JS::Value> reflector(aCx);
-    if (!GetOrCreateDOMReflector(aCx, newException, &reflector)) {
+    if (!domException->Sanitize(aCx, &reflector)) {
       // Well, that threw _an_ exception.  Let's forget ours.  We can just
       // unroot and not change the value, since mJSException is completely
       // ignored if mResult is not NS_ERROR_DOM_JS_EXCEPTION and we plan to
       // change mResult to a different value.
       js::RemoveRawValueRoot(aCx, &mJSException);
 
       // We no longer have a useful exception but we do want to signal that an
       // error occured.
@@ -296,16 +287,27 @@ ErrorResult::StealJSException(JSContext*
 {
   MOZ_ASSERT(!mMightHaveUnreportedJSException,
              "Must call WouldReportJSException unconditionally in all codepaths that might call StealJSException");
   MOZ_ASSERT(IsJSException(), "No exception to steal");
 
   value.set(mJSException);
   js::RemoveRawValueRoot(cx, &mJSException);
   mResult = NS_OK;
+
+  if (value.isObject()) {
+    // If it's a DOMException we may need to sanitize it.
+    dom::DOMException* domException;
+    nsresult rv =
+      UNWRAP_OBJECT(DOMException, &value.toObject(), domException);
+    if (NS_SUCCEEDED(rv) && !domException->Sanitize(cx, value)) {
+      JS_GetPendingException(cx, value);
+      JS_ClearPendingException(cx);
+    }
+  }
 }
 
 void
 ErrorResult::ReportNotEnoughArgsError(JSContext* cx,
                                       const char* ifaceName,
                                       const char* memberName)
 {
   MOZ_ASSERT(ErrorCode() == NS_ERROR_XPC_NOT_ENOUGH_ARGS);
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1273,18 +1273,17 @@ DOMInterfaces = {
 },
 
 'VTTRegion': {
   'nativeType': 'mozilla::dom::TextTrackRegion',
 },
 
 'WebGLActiveInfo': {
     'nativeType': 'mozilla::WebGLActiveInfo',
-    'headerFile': 'WebGLActiveInfo.h',
-    'wrapperCache': False
+    'headerFile': 'WebGLActiveInfo.h'
 },
 
 'WebGLBuffer': {
     'nativeType': 'mozilla::WebGLBuffer',
     'headerFile': 'WebGLBuffer.h'
 },
 
 'WebGLExtensionCompressedTextureATC': {
@@ -1467,18 +1466,17 @@ DOMInterfaces = {
 
 'WebGLTransformFeedback': {
     'nativeType': 'mozilla::WebGLTransformFeedback',
     'headerFile': 'WebGLTransformFeedback.h'
 },
 
 'WebGLUniformLocation': {
     'nativeType': 'mozilla::WebGLUniformLocation',
-    'headerFile': 'WebGLUniformLocation.h',
-    'wrapperCache': False
+    'headerFile': 'WebGLUniformLocation.h'
 },
 
 'WebGLVertexArray': {
     'nativeType': 'mozilla::WebGLVertexArray',
     'headerFile': 'WebGLVertexArray.h'
 },
 
 'WebrtcGlobalInformation': {
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -16,16 +16,18 @@
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "xpcprivate.h"
 #include "WorkerPrivate.h"
 #include "nsGlobalWindow.h"
 #include "WorkerScope.h"
+#include "jsapi.h"
+#include "nsJSPrincipals.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
   NS_INTERFACE_MAP_ENTRY(mozilla::dom::CallbackObject)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
@@ -181,24 +183,46 @@ CallbackObject::CallSetup::CallSetup(Cal
     JS::ContextOptionsRef(cx).setDontReportUncaught(true);
   }
 }
 
 bool
 CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
 {
   if (mExceptionHandling == eRethrowExceptions) {
-    return true;
+    if (!mCompartment) {
+      // Caller didn't ask us to filter for only exceptions we subsume.
+      return true;
+    }
+
+    // On workers, we don't have nsIPrincipals to work with.  But we also only
+    // have one compartment, so check whether mCompartment is the same as the
+    // current compartment of mCx.
+    if (mCompartment == js::GetContextCompartment(mCx)) {
+      return true;
+    }
+
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // At this point mCx is in the compartment of our unwrapped callback, so
+    // just check whether the principal of mCompartment subsumes that of the
+    // current compartment/global of mCx.
+    nsIPrincipal* callerPrincipal =
+      nsJSPrincipals::get(JS_GetCompartmentPrincipals(mCompartment));
+    nsIPrincipal* calleePrincipal = nsContentUtils::SubjectPrincipal();
+    if (callerPrincipal->SubsumesConsideringDomain(calleePrincipal)) {
+      return true;
+    }
   }
 
-  MOZ_ASSERT(mExceptionHandling == eRethrowContentExceptions);
+  MOZ_ASSERT(mCompartment);
 
-  // For eRethrowContentExceptions we only want to throw an exception if the
-  // object that was thrown is a DOMError or DOMException object in the caller
-  // compartment (which we stored in mCompartment).
+  // Now we only want to throw an exception to the caller if the object that was
+  // thrown is a DOMError or DOMException object in the caller compartment
+  // (which we stored in mCompartment).
 
   if (!aException.isObject()) {
     return false;
   }
 
   JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
   obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false);
   if (js::GetObjectCompartment(obj) != mCompartment) {
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -83,17 +83,20 @@ public:
 
   enum ExceptionHandling {
     // Report any exception and don't throw it to the caller code.
     eReportExceptions,
     // Throw an exception to the caller code if the thrown exception is a
     // binding object for a DOMError or DOMException from the caller's scope,
     // otherwise report it.
     eRethrowContentExceptions,
-    // Throw any exception to the caller code.
+    // Throw exceptions to the caller code, unless the caller compartment is
+    // provided, the exception is not a DOMError or DOMException from the
+    // caller compartment, and the caller compartment does not subsume our
+    // unwrapped callback.
     eRethrowExceptions
   };
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
   {
     return aMallocSizeOf(this);
   }
 
@@ -163,16 +166,21 @@ protected:
      * A class that performs whatever setup we need to safely make a
      * call while this class is on the stack, After the constructor
      * returns, the call is safe to make if GetContext() returns
      * non-null.
      */
   public:
     // If aExceptionHandling == eRethrowContentExceptions then aCompartment
     // needs to be set to the compartment in which exceptions will be rethrown.
+    //
+    // If aExceptionHandling == eRethrowExceptions then aCompartment may be set
+    // to the compartment in which exceptions will be rethrown.  In that case
+    // they will only be rethrown if that compartment's principal subsumes the
+    // principal of our (unwrapped) callback.
     CallSetup(CallbackObject* aCallback, ErrorResult& aRv,
               ExceptionHandling aExceptionHandling,
               JSCompartment* aCompartment = nullptr,
               bool aIsJSImplementedWebIDL = false);
     ~CallSetup();
 
     JSContext* GetContext() const
     {
@@ -184,17 +192,17 @@ protected:
     CallSetup(const CallSetup&) = delete;
 
     bool ShouldRethrowException(JS::Handle<JS::Value> aException);
 
     // Members which can go away whenever
     JSContext* mCx;
 
     // Caller's compartment. This will only have a sensible value if
-    // mExceptionHandling == eRethrowContentExceptions.
+    // mExceptionHandling == eRethrowContentExceptions or eRethrowExceptions.
     JSCompartment* mCompartment;
 
     // And now members whose construction/destruction order we need to control.
     Maybe<AutoEntryScript> mAutoEntryScript;
     Maybe<AutoIncumbentScript> mAutoIncumbentScript;
 
     // Constructed the rooter within the scope of mCxPusher above, so that it's
     // always within a request during its lifetime.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -13613,24 +13613,30 @@ class CGCallback(CGClass):
         argnames = [arg.name for arg in args]
         argnamesWithThis = ["s.GetContext()", "thisValJS"] + argnames
         argnamesWithoutThis = ["s.GetContext()", "JS::UndefinedHandleValue"] + argnames
         # Now that we've recorded the argnames for our call to our private
         # method, insert our optional argument for deciding whether the
         # CallSetup should re-throw exceptions on aRv.
         args.append(Argument("ExceptionHandling", "aExceptionHandling",
                              "eReportExceptions"))
+        # And the argument for communicating when exceptions should really be
+        # rethrown.  In particular, even when aExceptionHandling is
+        # eRethrowExceptions they won't get rethrown if aCompartment is provided
+        # and its principal doesn't subsume either the callback or the
+        # exception.
+        args.append(Argument("JSCompartment*", "aCompartment", "nullptr"))
         # And now insert our template argument.
         argsWithoutThis = list(args)
         args.insert(0, Argument("const T&",  "thisObjPtr"))
         errorReturn = method.getDefaultRetval()
 
         setupCall = fill(
             """
-            CallSetup s(this, aRv, aExceptionHandling);
+            CallSetup s(this, aRv, aExceptionHandling, aCompartment);
             if (!s.GetContext()) {
               aRv.Throw(NS_ERROR_UNEXPECTED);
               return${errorReturn};
             }
             """,
             errorReturn=errorReturn)
 
         bodyWithThis = fill(
@@ -13932,21 +13938,20 @@ class CallbackMember(CGNativeMember):
             default = " " + default
         return default
 
     def getArgs(self, returnType, argList):
         args = CGNativeMember.getArgs(self, returnType, argList)
         if not self.needThisHandling:
             # Since we don't need this handling, we're the actual method that
             # will be called, so we need an aRethrowExceptions argument.
-            if self.rethrowContentException:
-                args.append(Argument("JSCompartment*", "aCompartment", "nullptr"))
-            else:
+            if not self.rethrowContentException:
                 args.append(Argument("ExceptionHandling", "aExceptionHandling",
                                      "eReportExceptions"))
+            args.append(Argument("JSCompartment*", "aCompartment", "nullptr"))
             return args
         # We want to allow the caller to pass in a "this" value, as
         # well as a JSContext.
         return [Argument("JSContext*", "cx"),
                 Argument("JS::Handle<JS::Value>", "aThisVal")] + args
 
     def getCallSetup(self):
         if self.needThisHandling:
@@ -13954,17 +13959,17 @@ class CallbackMember(CGNativeMember):
             return ""
         callSetup = "CallSetup s(this, aRv"
         if self.rethrowContentException:
             # getArgs doesn't add the aExceptionHandling argument but does add
             # aCompartment for us.
             callSetup += ", eRethrowContentExceptions, aCompartment, /* aIsJSImplementedWebIDL = */ "
             callSetup += toStringBool(isJSImplementedDescriptor(self.descriptorProvider))
         else:
-            callSetup += ", aExceptionHandling"
+            callSetup += ", aExceptionHandling, aCompartment"
         callSetup += ");\n"
         return fill(
             """
             $*{callSetup}
             JSContext* cx = s.GetContext();
             if (!cx) {
               aRv.Throw(NS_ERROR_UNEXPECTED);
               return${errorReturn};
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -92,17 +92,18 @@ public:
   void ThrowNotEnoughArgsError() { mResult = NS_ERROR_XPC_NOT_ENOUGH_ARGS; }
   void ReportNotEnoughArgsError(JSContext* cx,
                                 const char* ifaceName,
                                 const char* memberName);
   bool IsNotEnoughArgsError() const { return ErrorCode() == NS_ERROR_XPC_NOT_ENOUGH_ARGS; }
 
   // StealJSException steals the JS Exception from the object. This method must
   // be called only if IsJSException() returns true. This method also resets the
-  // ErrorCode() to NS_OK.
+  // ErrorCode() to NS_OK.  The value will be ensured to be sanitized wrt to the
+  // current compartment of cx if it happens to be a DOMException.
   void StealJSException(JSContext* cx, JS::MutableHandle<JS::Value> value);
 
   void MOZ_ALWAYS_INLINE MightThrowJSException()
   {
 #ifdef DEBUG
     mMightHaveUnreportedJSException = true;
 #endif
   }
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -281,16 +281,17 @@ public:
   static already_AddRefed<nsIStackFrame>
   CreateStack(JSContext* aCx, int32_t aMaxDepth = -1);
 
   NS_IMETHOD GetLanguageName(nsACString& aLanguageName) MOZ_OVERRIDE;
   NS_IMETHOD GetFilename(nsAString& aFilename) MOZ_OVERRIDE;
   NS_IMETHOD GetName(nsAString& aFunction) MOZ_OVERRIDE;
   NS_IMETHOD GetCaller(nsIStackFrame** aCaller) MOZ_OVERRIDE;
   NS_IMETHOD GetFormattedStack(nsAString& aStack) MOZ_OVERRIDE;
+  virtual bool CallerSubsumes(JSContext* aCx) MOZ_OVERRIDE;
 
 protected:
   virtual bool IsJSFrame() const MOZ_OVERRIDE {
     return true;
   }
 
   virtual nsresult GetLineno(int32_t* aLineNo) MOZ_OVERRIDE;
   virtual nsresult GetColNo(int32_t* aColNo) MOZ_OVERRIDE;
@@ -608,16 +609,47 @@ NS_IMETHODIMP StackFrame::ToString(nsACS
   static const char format[] = "%s frame :: %s :: %s :: line %d";
   _retval.AppendPrintf(format, frametype,
                        NS_ConvertUTF16toUTF8(filename).get(),
                        NS_ConvertUTF16toUTF8(funname).get(),
                        lineno);
   return NS_OK;
 }
 
+/* virtual */ bool
+StackFrame::CallerSubsumes(JSContext* aCx)
+{
+  return true;
+}
+
+/* virtual */ bool
+JSStackFrame::CallerSubsumes(JSContext* aCx)
+{
+  if (!NS_IsMainThread()) {
+    return true;
+  }
+
+  if (!mStack) {
+    // No problem here, there's no data to leak.
+    return true;
+  }
+
+  nsIPrincipal* callerPrincipal = nsContentUtils::SubjectPrincipal();
+
+  JS::Rooted<JSObject*> unwrappedStack(aCx, js::CheckedUnwrap(mStack));
+  if (!unwrappedStack) {
+    // We can't leak data here either.
+    return true;
+  }
+
+  nsIPrincipal* stackPrincipal =
+    nsJSPrincipals::get(js::GetSavedFramePrincipals(unwrappedStack));
+  return callerPrincipal->SubsumesConsideringDomain(stackPrincipal);
+}
+
 /* static */ already_AddRefed<nsIStackFrame>
 JSStackFrame::CreateStack(JSContext* aCx, int32_t aMaxDepth)
 {
   static const unsigned MAX_FRAMES = 100;
   if (aMaxDepth < 0) {
     aMaxDepth = MAX_FRAMES;
   }
 
--- a/dom/bindings/test/TestInterfaceJS.js
+++ b/dom/bindings/test/TestInterfaceJS.js
@@ -1,16 +1,17 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
-"use strict";
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 function TestInterfaceJS(anyArg, objectArg) {}
 
 TestInterfaceJS.prototype = {
   classID: Components.ID("{2ac4e026-cf25-47d5-b067-78d553c3cad8}"),
   contractID: "@mozilla.org/dom/test-interface-js;1",
@@ -66,11 +67,79 @@ TestInterfaceJS.prototype = {
   testThrowDOMError: function() {
     throw new this._win.DOMError("NotSupportedError", "We are a DOMError");
   },
 
   testThrowDOMException: function() {
     throw new this._win.DOMException("We are a DOMException",
                                      "NotSupportedError");
   },
+
+  testPromiseWithThrowingChromePromiseInit: function() {
+    return new this._win.Promise(function() {
+      noSuchMethodExistsYo1();
+    })
+  },
+
+  testPromiseWithThrowingContentPromiseInit: function(func) {
+      return new this._win.Promise(func);
+  },
+
+  testPromiseWithDOMExceptionThrowingPromiseInit: function() {
+    return new this._win.Promise(() => {
+      throw new this._win.DOMException("We are a second DOMException",
+                                       "NotFoundError");
+    })
+  },
+
+  testPromiseWithThrowingChromeThenFunction: function() {
+    return this._win.Promise.resolve(5).then(function() {
+      noSuchMethodExistsYo2();
+    });
+  },
+
+  testPromiseWithThrowingContentThenFunction: function(func) {
+    return this._win.Promise.resolve(10).then(func);
+  },
+
+  testPromiseWithDOMExceptionThrowingThenFunction: function() {
+    return this._win.Promise.resolve(5).then(() => {
+      throw new this._win.DOMException("We are a third DOMException",
+                                       "NetworkError");
+    });
+  },
+
+  testPromiseWithThrowingChromeThenable: function() {
+    // We need to produce a thing that has a "then" property in the page
+    // compartment, since we plan to call the page-provided resolve function.
+    var thenable = new this._win.Object();
+    Cu.waiveXrays(thenable).then = function() {
+      noSuchMethodExistsYo3()
+    }
+    return new this._win.Promise(function(resolve) {
+      resolve(thenable)
+    });
+  },
+
+  testPromiseWithThrowingContentThenable: function(thenable) {
+    // Waive Xrays on the thenable, because we're calling resolve() in the
+    // chrome compartment, so that's the compartment the "then" property get
+    // will happen in, and if we leave the Xray in place the function-valued
+    // property won't return the function.
+    return this._win.Promise.resolve(Cu.waiveXrays(thenable));
+  },
+
+  testPromiseWithDOMExceptionThrowingThenable: function() {
+    // We need to produce a thing that has a "then" property in the page
+    // compartment, since we plan to call the page-provided resolve function.
+    var thenable = new this._win.Object();
+    Cu.waiveXrays(thenable).then = () => {
+      throw new this._win.DOMException("We are a fourth DOMException",
+                                       "TypeMismatchError");
+    }
+    return new this._win.Promise(function(resolve) {
+      resolve(thenable)
+    });
+  },
+
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJS])
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -50,8 +50,10 @@ skip-if = debug == false
 [test_setWithNamedGetterNoNamedSetter.html]
 [test_throwing_method_noDCE.html]
 [test_treat_non_object_as_null.html]
 [test_traceProtos.html]
 [test_sequence_detection.html]
 skip-if = debug == false
 [test_exception_options_from_jsimplemented.html]
 skip-if = debug == false
+[test_promise_rejections_from_jsimplemented.html]
+skip-if = debug == false
copy from dom/bindings/test/test_exception_options_from_jsimplemented.html
copy to dom/bindings/test/test_promise_rejections_from_jsimplemented.html
--- a/dom/bindings/test/test_exception_options_from_jsimplemented.html
+++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
@@ -9,57 +9,99 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1107592 **/
 
   SimpleTest.waitForExplicitFinish();
 
+  function checkExn(lineNumber, name, message, code, filename, testNumber, stack, exn) {
+    ise(exn.lineNumber, lineNumber,
+        "Should have the right line number in test " + testNumber);
+    ise(exn.name, name,
+        "Should have the right exception name in test " + testNumber);
+    ise("filename" in exn ? exn.filename : exn.fileName, filename,
+        "Should have the right file name in test " + testNumber);
+    ise(exn.message, message,
+        "Should have the right message in test " + testNumber);
+    ise(exn.code, code, "Should have the right .code in test " + testNumber);
+    if (message === "") {
+      ise(exn.name, "NS_ERROR_UNEXPECTED",
+          "Should have one of our synthetic exceptions in test " + testNumber);
+    }
+    ise(exn.stack, stack, "Should have the right stack in test " + testNumber);
+  }
+
+  function ensurePromiseFail(testNumber, value) {
+    ok(false, "Test " + testNumber + " should not have a fulfilled promise");
+  }
+
   function doTest() {
     var t = new TestInterfaceJS();
-    try {
-      t.testThrowDOMError();
-    } catch (e) {
-      ok(e instanceof Error, "Should have an Error here");
-      ok(!(e instanceof DOMException), "Should not have DOMException here");
-      ok(!("code" in e), "Should not have a 'code' property");
-      ise(e.name, "Error", "Should not have an interesting name here");
-      ise(e.message, "We are a DOMError", "Should have the right message");
-      ise(e.stack,
-          "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:20:7\n",
-          "Exception stack should still only show our code");
-      ise(e.fileName,
-          "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
-          "Should have the right file name");
-      ise(e.lineNumber, 20, "Should have the right line number");
-      ise(e.columnNumber, 6, "Should have the right column number");
-    }
+
+    var ourFile = "http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html";
 
-    try {
-      t.testThrowDOMException();
-    } catch (e) {
-      ok(e instanceof Error, "Should also have an Error here");
-      ok(e instanceof DOMException, "Should have DOMException here");
-      ise(e.name, "NotSupportedError", "Should have the right name here");
-      ise(e.message, "We are a DOMException",
-          "Should also have the right message");
-      ise(e.code, DOMException.NOT_SUPPORTED_ERR,
-          "Should have the right 'code'");
-      ise(e.stack,
-          "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:38:6\n",
-          "Exception stack should still only show our code");
-      ise(e.filename,
-          "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
-          "Should still have the right file name");
-      ise(e.lineNumber, 38, "Should still have the right line number");
-      todo_is(e.columnNumber, 6,
-              "No column number support for DOMException yet");
-    }
-    SimpleTest.finish();
+    Promise.all([
+      t.testPromiseWithThrowingChromePromiseInit().then(
+          ensurePromiseFail.bind(null, 1),
+          checkExn.bind(null, 44, "NS_ERROR_UNEXPECTED", "", undefined,
+                        ourFile, 1,
+                        "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:44:6\n")),
+      t.testPromiseWithThrowingContentPromiseInit(function() {
+          thereIsNoSuchContentFunction1();
+        }).then(
+          ensurePromiseFail.bind(null, 2),
+          checkExn.bind(null, 50, "ReferenceError",
+                        "thereIsNoSuchContentFunction1 is not defined",
+                        undefined, ourFile, 2,
+                        "doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:50:11\ndoTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:49:7\n")),
+      t.testPromiseWithThrowingChromeThenFunction().then(
+          ensurePromiseFail.bind(null, 3),
+          checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 3, "")),
+      t.testPromiseWithThrowingContentThenFunction(function() {
+          thereIsNoSuchContentFunction2();
+        }).then(
+          ensurePromiseFail.bind(null, 4),
+          checkExn.bind(null, 61, "ReferenceError",
+                        "thereIsNoSuchContentFunction2 is not defined",
+                        undefined, ourFile, 4,
+                        "doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:61:11\n")),
+      t.testPromiseWithThrowingChromeThenable().then(
+          ensurePromiseFail.bind(null, 5),
+          checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 5, "")),
+      t.testPromiseWithThrowingContentThenable({
+            then: function() { thereIsNoSuchContentFunction3(); }
+        }).then(
+          ensurePromiseFail.bind(null, 6),
+          checkExn.bind(null, 72, "ReferenceError",
+                        "thereIsNoSuchContentFunction3 is not defined",
+                        undefined, ourFile, 6,
+                        "doTest/<.then@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:72:32\n")),
+      t.testPromiseWithDOMExceptionThrowingPromiseInit().then(
+          ensurePromiseFail.bind(null, 7),
+          checkExn.bind(null, 79, "NotFoundError",
+                        "We are a second DOMException",
+                        DOMException.NOT_FOUND_ERR, ourFile, 7,
+                        "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:79:6\n")),
+      t.testPromiseWithDOMExceptionThrowingThenFunction().then(
+          ensurePromiseFail.bind(null, 8),
+          checkExn.bind(null, 0, "NetworkError",
+                        "We are a third DOMException",
+                        DOMException.NETWORK_ERR, "", 8, "")),
+      t.testPromiseWithDOMExceptionThrowingThenable().then(
+          ensurePromiseFail.bind(null, 9),
+          checkExn.bind(null, 0, "TypeMismatchError",
+                        "We are a fourth DOMException",
+                         DOMException.TYPE_MISMATCH_ERR, "", 9, "")),
+    ]).then(SimpleTest.finish,
+            function() {
+              ok(false, "One of our catch statements totally failed");
+              SimpleTest.finish();
+            });
   }
 
   SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
                             doTest);
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1107592">Mozilla Bug 1107592</a>
--- a/dom/bluetooth2/BluetoothPairingListener.cpp
+++ b/dom/bluetooth2/BluetoothPairingListener.cpp
@@ -87,28 +87,31 @@ BluetoothPairingListener::Notify(const B
   BluetoothValue value = aData.value();
   if (aData.name().EqualsLiteral("PairingRequest")) {
 
     MOZ_ASSERT(value.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
 
     const InfallibleTArray<BluetoothNamedValue>& arr =
       value.get_ArrayOfBluetoothNamedValue();
 
-    MOZ_ASSERT(arr.Length() == 3 &&
+    MOZ_ASSERT(arr.Length() == 4 &&
                arr[0].value().type() == BluetoothValue::TnsString && // address
-               arr[1].value().type() == BluetoothValue::TnsString && // passkey
-               arr[2].value().type() == BluetoothValue::TnsString);  // type
+               arr[1].value().type() == BluetoothValue::TnsString && // name
+               arr[2].value().type() == BluetoothValue::TnsString && // passkey
+               arr[3].value().type() == BluetoothValue::TnsString);  // type
 
     nsString deviceAddress = arr[0].value().get_nsString();
-    nsString passkey = arr[1].value().get_nsString();
-    nsString type = arr[2].value().get_nsString();
+    nsString deviceName = arr[1].value().get_nsString();
+    nsString passkey = arr[2].value().get_nsString();
+    nsString type = arr[3].value().get_nsString();
 
-    // Create a temporary device with deviceAddress for searching
+    // Create a temporary device with deviceAddress and deviceName
     InfallibleTArray<BluetoothNamedValue> props;
     BT_APPEND_NAMED_VALUE(props, "Address", deviceAddress);
+    BT_APPEND_NAMED_VALUE(props, "Name", deviceName);
     nsRefPtr<BluetoothDevice> device =
       BluetoothDevice::Create(GetOwner(), props);
 
     // Notify pairing listener of pairing requests
     DispatchPairingEvent(device, passkey, type);
   } else {
     BT_WARNING("Not handling pairing listener signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
@@ -1495,16 +1495,17 @@ BluetoothServiceBluedroid::PinRequestNot
                                                   const nsAString& aBdName,
                                                   uint32_t aCod)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   InfallibleTArray<BluetoothNamedValue> propertiesArray;
 
   BT_APPEND_NAMED_VALUE(propertiesArray, "address", nsString(aRemoteBdAddr));
+  BT_APPEND_NAMED_VALUE(propertiesArray, "name", nsString(aBdName));
   BT_APPEND_NAMED_VALUE(propertiesArray, "passkey", EmptyString());
   BT_APPEND_NAMED_VALUE(propertiesArray, "type",
                         NS_LITERAL_STRING(PAIRING_REQ_TYPE_ENTERPINCODE));
 
   DistributeSignal(BluetoothSignal(NS_LITERAL_STRING("PairingRequest"),
                                    NS_LITERAL_STRING(KEY_PAIRING_LISTENER),
                                    BluetoothValue(propertiesArray)));
 }
@@ -1541,16 +1542,17 @@ BluetoothServiceBluedroid::SspRequestNot
       pairingType.AssignLiteral(PAIRING_REQ_TYPE_CONSENT);
       break;
     default:
       BT_WARNING("Unhandled SSP Bonding Variant: %d", aPairingVariant);
       return;
   }
 
   BT_APPEND_NAMED_VALUE(propertiesArray, "address", nsString(aRemoteBdAddr));
+  BT_APPEND_NAMED_VALUE(propertiesArray, "name", nsString(aBdName));
   BT_APPEND_NAMED_VALUE(propertiesArray, "passkey", passkey);
   BT_APPEND_NAMED_VALUE(propertiesArray, "type", pairingType);
 
   DistributeSignal(BluetoothSignal(NS_LITERAL_STRING("PairingRequest"),
                                    NS_LITERAL_STRING(KEY_PAIRING_LISTENER),
                                    BluetoothValue(propertiesArray)));
 }
 
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -147,12 +147,14 @@ WebGLContext::InitWebGL2()
     mBoundUniformBuffers =
         MakeUnique<WebGLRefPtr<WebGLBuffer>[]>(mGLMaxUniformBufferBindings);
 
     mDefaultTransformFeedback = new WebGLTransformFeedback(this, 0);
     mBoundTransformFeedback = mDefaultTransformFeedback;
     auto xfBuffers = new WebGLRefPtr<WebGLBuffer>[mGLMaxTransformFeedbackSeparateAttribs];
     mBoundTransformFeedbackBuffers.reset(xfBuffers);
 
+    mBypassShaderValidation = true;
+
     return true;
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -207,48 +207,50 @@ WebGL2Context::TransformFeedbackVaryings
 
     GLsizei count = varyings.Length();
     GLchar** tmpVaryings = (GLchar**) nsMemory::Alloc(count * sizeof(GLchar*));
 
     for (GLsizei n = 0; n < count; n++) {
         tmpVaryings[n] = (GLchar*) ToNewCString(varyings[n]);
     }
 
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
     MakeContextCurrent();
     gl->fTransformFeedbackVaryings(progname, count, tmpVaryings, bufferMode);
 
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, tmpVaryings);
 }
 
-
 already_AddRefed<WebGLActiveInfo>
 WebGL2Context::GetTransformFeedbackVarying(WebGLProgram* program, GLuint index)
 {
     if (IsContextLost())
         return nullptr;
 
     if (!ValidateObject("getTransformFeedbackVarying: program", program))
         return nullptr;
 
     MakeContextCurrent();
 
     GLint len = 0;
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
     gl->fGetProgramiv(progname, LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &len);
     if (!len)
         return nullptr;
 
     UniquePtr<char[]> name(new char[len]);
     GLint tfsize = 0;
     GLuint tftype = 0;
 
     gl->fGetTransformFeedbackVarying(progname, index, len, &len, &tfsize, &tftype, name.get());
     if (len == 0 || tfsize == 0 || tftype == 0)
         return nullptr;
 
-    // TODO(djg): Reverse lookup of name
-    // nsCString reverseMappedName;
-    // prog->ReverveMapIdentifier(nsDependentCString(name), &reverseMappedName);
+    MOZ_CRASH("todo");
+    /*
+    // Reverse lookup of name
+    nsCString reverseMappedName;
+    prog->ReverveMapIdentifier(nsDependentCString(name), &reverseMappedName);
 
     nsRefPtr<WebGLActiveInfo> result = new WebGLActiveInfo(tfsize, tftype, nsDependentCString(name.get()));
     return result.forget();
+    */
 }
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -391,17 +391,17 @@ WebGL2Context::GetUniformIndices(WebGLPr
         return;
 
     if (!ValidateObject("getUniformIndices: program", program))
         return;
 
     if (!uniformNames.Length())
         return;
 
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
     size_t count = uniformNames.Length();
     nsTArray<GLuint>& arr = retval.SetValue();
 
     MakeContextCurrent();
 
     for (size_t n = 0; n < count; n++) {
         NS_LossyConvertUTF16toASCII name(uniformNames[n]);
         //        const GLchar* glname = name.get();
@@ -426,17 +426,17 @@ WebGL2Context::GetActiveUniforms(WebGLPr
 
     if (!ValidateObject("getActiveUniforms: program", program))
         return;
 
     size_t count = uniformIndices.Length();
     if (!count)
         return;
 
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
     nsTArray<GLint>& arr = retval.SetValue();
     arr.SetLength(count);
 
     MakeContextCurrent();
     gl->fGetActiveUniformsiv(progname, count, uniformIndices.Elements(), pname,
                              arr.Elements());
 }
 
@@ -445,27 +445,24 @@ WebGL2Context::GetUniformBlockIndex(WebG
                                     const nsAString& uniformBlockName)
 {
     if (IsContextLost())
         return 0;
 
     if (!ValidateObject("getUniformBlockIndex: program", program))
         return 0;
 
-    if (!ValidateGLSLVariableName(uniformBlockName, "getUniformBlockIndex"))
-        return 0;
+    // Leave this unchecked for now.
 
-    NS_LossyConvertUTF16toASCII cname(uniformBlockName);
-    nsCString mappedName;
-    program->MapIdentifier(cname, &mappedName);
+    const NS_LossyConvertUTF16toASCII cname(uniformBlockName);
 
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
 
     MakeContextCurrent();
-    return gl->fGetUniformBlockIndex(progname, mappedName.get());
+    return gl->fGetUniformBlockIndex(progname, cname.BeginReading());
 }
 
 static bool
 GetUniformBlockActiveUniforms(gl::GLContext* gl, JSContext* cx,
                               WebGL2Context* owner, GLuint progname,
                               GLuint uniformBlockIndex,
                               JS::MutableHandleObject out_array)
 {
@@ -496,17 +493,17 @@ WebGL2Context::GetActiveUniformBlockPara
 {
     retval.SetNull();
     if (IsContextLost())
         return;
 
     if (!ValidateObject("getActiveUniformBlockParameter: program", program))
         return;
 
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
     GLint param = 0;
 
     MakeContextCurrent();
 
     switch(pname) {
     case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
     case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
         gl->fGetActiveUniformBlockiv(progname, uniformBlockIndex, pname, &param);
@@ -546,17 +543,17 @@ WebGL2Context::GetActiveUniformBlockName
                                          nsAString& retval)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("getActiveUniformBlockName: program", program))
         return;
 
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
     GLchar nameBuffer[WEBGL_MAX_UNIFORM_BLOCK_NAME_LENGTH];
     GLsizei length = 0;
 
     MakeContextCurrent();
     gl->fGetActiveUniformBlockName(progname, uniformBlockIndex,
                                    WEBGL_MAX_UNIFORM_BLOCK_NAME_LENGTH, &length,
                                    nameBuffer);
     retval.Assign(NS_ConvertASCIItoUTF16(nsDependentCString(nameBuffer)));
@@ -569,13 +566,13 @@ WebGL2Context::UniformBlockBinding(WebGL
                                    GLuint uniformBlockBinding)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("uniformBlockBinding: program", program))
         return;
 
-    GLuint progname = program->GLName();
+    GLuint progname = program->mGLName;
 
     MakeContextCurrent();
     gl->fUniformBlockBinding(progname, uniformBlockIndex, uniformBlockBinding);
 }
--- a/dom/canvas/WebGLActiveInfo.cpp
+++ b/dom/canvas/WebGLActiveInfo.cpp
@@ -1,21 +1,100 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "WebGLActiveInfo.h"
 
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
-#include "WebGLContext.h"
-#include "WebGLTexture.h"
 
 namespace mozilla {
 
-bool
-WebGLActiveInfo::WrapObject(JSContext* aCx,
-                            JS::MutableHandle<JSObject*> aReflector)
+static uint8_t
+ElemSizeFromType(GLenum elemType)
 {
-    return dom::WebGLActiveInfoBinding::Wrap(aCx, this, aReflector);
+    switch (elemType) {
+    case LOCAL_GL_BOOL:
+    case LOCAL_GL_FLOAT:
+    case LOCAL_GL_INT:
+    case LOCAL_GL_INT_SAMPLER_2D:
+    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_INT_SAMPLER_3D:
+    case LOCAL_GL_INT_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+        return 1;
+
+    case LOCAL_GL_BOOL_VEC2:
+    case LOCAL_GL_FLOAT_VEC2:
+    case LOCAL_GL_INT_VEC2:
+        return 2;
+
+    case LOCAL_GL_BOOL_VEC3:
+    case LOCAL_GL_FLOAT_VEC3:
+    case LOCAL_GL_INT_VEC3:
+        return 3;
+
+
+    case LOCAL_GL_BOOL_VEC4:
+    case LOCAL_GL_FLOAT_MAT2:
+    case LOCAL_GL_FLOAT_VEC4:
+    case LOCAL_GL_INT_VEC4:
+        return 4;
+
+    case LOCAL_GL_FLOAT_MAT2x3:
+    case LOCAL_GL_FLOAT_MAT3x2:
+        return 6;
+
+    case LOCAL_GL_FLOAT_MAT2x4:
+    case LOCAL_GL_FLOAT_MAT4x2:
+        return 8;
+
+    case LOCAL_GL_FLOAT_MAT3:
+        return 9;
+
+    case LOCAL_GL_FLOAT_MAT3x4:
+    case LOCAL_GL_FLOAT_MAT4x3:
+        return 12;
+
+    case LOCAL_GL_FLOAT_MAT4:
+        return 16;
+
+    default:
+        MOZ_CRASH("Bad `elemType`.");
+    }
 }
 
+WebGLActiveInfo::WebGLActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType,
+                                 bool isArray, const nsACString& baseUserName,
+                                 const nsACString& baseMappedName)
+    : mWebGL(webgl)
+    , mElemCount(elemCount)
+    , mElemType(elemType)
+    , mBaseUserName(baseUserName)
+    , mIsArray(isArray)
+    , mElemSize(ElemSizeFromType(elemType))
+    , mBaseMappedName(baseMappedName)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+JSObject*
+WebGLActiveInfo::WrapObject(JSContext* js)
+{
+    return dom::WebGLActiveInfoBinding::Wrap(js, this);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLActiveInfo)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLActiveInfo, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLActiveInfo, Release)
+
 } // namespace mozilla
--- a/dom/canvas/WebGLActiveInfo.h
+++ b/dom/canvas/WebGLActiveInfo.h
@@ -1,55 +1,94 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 WEBGL_ACTIVE_INFO_H_
 #define WEBGL_ACTIVE_INFO_H_
 
+#include "GLDefs.h"
 #include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS
+#include "nsISupportsImpl.h" // NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING
 #include "nsString.h"
-#include "WebGLObjectModel.h"
+#include "nsWrapperCache.h"
 
 namespace mozilla {
 
+class WebGLContext;
+
 class WebGLActiveInfo MOZ_FINAL
+    : public nsWrapperCache
 {
 public:
-    WebGLActiveInfo(GLint size, GLenum type, const nsACString& name)
-        : mSize(size)
-        , mType(type)
-        , mName(NS_ConvertASCIItoUTF16(name))
-    {}
+    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLActiveInfo)
+    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLActiveInfo)
+
+    virtual JSObject* WrapObject(JSContext* js) MOZ_OVERRIDE;
+
+    WebGLContext* GetParentObject() const {
+        return mWebGL;
+    }
+
+
+    WebGLContext* const mWebGL;
+
+    // ActiveInfo state:
+    const GLint mElemCount; // `size`
+    const GLenum mElemType; // `type`
+    const nsCString mBaseUserName; // `name`, but ASCII, and without any final "[0]".
+
+    // Not actually part of ActiveInfo:
+    const bool mIsArray;
+    const uint8_t mElemSize;
+    const nsCString mBaseMappedName; // Without any final "[0]".
+
+    WebGLActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType, bool isArray,
+                    const nsACString& baseUserName, const nsACString& baseMappedName);
+
+    /* GLES 2.0.25, p33:
+     *   This command will return as much information about active
+     *   attributes as possible. If no information is available, length will
+     *   be set to zero and name will be an empty string. This situation
+     *   could arise if GetActiveAttrib is issued after a failed link.
+     *
+     * It's the same for GetActiveUniform.
+     */
+    static WebGLActiveInfo* CreateInvalid(WebGLContext* webgl) {
+        return new WebGLActiveInfo(webgl);
+    }
 
     // WebIDL attributes
-
     GLint Size() const {
-        return mSize;
+        return mElemCount;
     }
 
     GLenum Type() const {
-        return mType;
+        return mElemType;
     }
 
     void GetName(nsString& retval) const {
-        retval = mName;
+        CopyASCIItoUTF16(mBaseUserName, retval);
+        if (mIsArray)
+            retval.AppendLiteral("[0]");
     }
 
-    bool WrapObject(JSContext* aCx, JS::MutableHandle<JSObject*> aReflector);
-
-   NS_INLINE_DECL_REFCOUNTING(WebGLActiveInfo)
-
 private:
+    explicit WebGLActiveInfo(WebGLContext* webgl)
+        : mWebGL(webgl)
+        , mElemCount(0)
+        , mElemType(0)
+        , mBaseUserName("")
+        , mIsArray(false)
+        , mElemSize(0)
+        , mBaseMappedName("")
+    { }
+
     // Private destructor, to discourage deletion outside of Release():
-    ~WebGLActiveInfo()
-    {
-    }
-
-    GLint mSize;
-    GLenum mType;
-    nsString mName;
+    ~WebGLActiveInfo() { }
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_ACTIVE_INFO_H_
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -43,17 +43,17 @@ public:
 
     bool Validate(GLenum type, uint32_t max_allowed, size_t first, size_t count,
                   uint32_t* const out_upperBound);
 
     bool IsElementArrayUsedWithMultipleTypes() const;
 
     WebGLContext* GetParentObject() const {
         return Context();
-    };
+    }
 
     virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLBuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLBuffer)
 
 protected:
     ~WebGLBuffer();
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -197,31 +197,30 @@ WebGLContextOptions::WebGLContextOptions
 {
     // Set default alpha state based on preference.
     if (Preferences::GetBool("webgl.default-no-alpha", false))
         alpha = false;
 }
 
 WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
+    , mBypassShaderValidation(false)
     , mNeedsFakeNoAlpha(false)
 {
     mGeneration = 0;
     mInvalidated = false;
     mShouldPresent = true;
     mResetLayer = true;
     mOptionsFrozen = false;
 
     mActiveTexture = 0;
     mPixelStoreFlipY = false;
     mPixelStorePremultiplyAlpha = false;
     mPixelStoreColorspaceConversion = BROWSER_DEFAULT_WEBGL;
 
-    mShaderValidation = true;
-
     mFakeBlackStatus = WebGLContextFakeBlackStatus::NotNeeded;
 
     mVertexAttrib0Vector[0] = 0;
     mVertexAttrib0Vector[1] = 0;
     mVertexAttrib0Vector[2] = 0;
     mVertexAttrib0Vector[3] = 1;
     mFakeVertexAttrib0BufferObjectVector[0] = 0;
     mFakeVertexAttrib0BufferObjectVector[1] = 0;
@@ -325,16 +324,17 @@ WebGLContext::DestroyResourcesAndContext
     mBoundArrayBuffer = nullptr;
     mBoundCopyReadBuffer = nullptr;
     mBoundCopyWriteBuffer = nullptr;
     mBoundPixelPackBuffer = nullptr;
     mBoundPixelUnpackBuffer = nullptr;
     mBoundTransformFeedbackBuffer = nullptr;
     mBoundUniformBuffer = nullptr;
     mCurrentProgram = nullptr;
+    mActiveProgramLinkInfo = nullptr;
     mBoundDrawFramebuffer = nullptr;
     mBoundReadFramebuffer = nullptr;
     mActiveOcclusionQuery = nullptr;
     mBoundRenderbuffer = nullptr;
     mBoundVertexArray = nullptr;
     mDefaultVertexArray = nullptr;
     mBoundTransformFeedback = nullptr;
     mDefaultTransformFeedback = nullptr;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -14,16 +14,17 @@
 #include "mozilla/WeakPtr.h"
 
 #include "GLDefs.h"
 #include "WebGLActiveInfo.h"
 #include "WebGLContextUnchecked.h"
 #include "WebGLObjectModel.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
+#include "WebGLShaderValidator.h"
 #include "WebGLStrongTypes.h"
 #include <stdarg.h>
 
 #include "nsTArray.h"
 #include "nsCycleCollectionNoteChild.h"
 
 #include "nsIDOMWebGLRenderingContext.h"
 #include "nsICanvasRenderingContextInternal.h"
@@ -91,16 +92,20 @@ class ImageData;
 struct WebGLContextAttributes;
 template<typename> struct Nullable;
 }
 
 namespace gfx {
 class SourceSurface;
 }
 
+namespace webgl {
+struct LinkedProgramInfo;
+}
+
 WebGLTexelFormat GetWebGLTexelFormat(TexInternalFormat format);
 
 void AssertUintParamCorrect(gl::GLContext* gl, GLenum pname, GLuint shadow);
 
 struct WebGLContextOptions
 {
     // these are defaults
     WebGLContextOptions();
@@ -370,17 +375,19 @@ public:
                            GLenum srcAlpha, GLenum dstAlpha);
     GLenum CheckFramebufferStatus(GLenum target);
     void Clear(GLbitfield mask);
     void ClearColor(GLclampf r, GLclampf g, GLclampf b, GLclampf a);
     void ClearDepth(GLclampf v);
     void ClearStencil(GLint v);
     void ColorMask(WebGLboolean r, WebGLboolean g, WebGLboolean b, WebGLboolean a);
     void CompileShader(WebGLShader* shader);
-    void CompressedTexImage2D(GLenum texImageTarget, GLint level,
+    void CompileShaderANGLE(WebGLShader* shader);
+    void CompileShaderBypass(WebGLShader* shader, const nsCString& shaderSource);
+    void CompressedTexImage2D(GLenum target, GLint level,
                               GLenum internalformat, GLsizei width,
                               GLsizei height, GLint border,
                               const dom::ArrayBufferView& view);
     void CompressedTexSubImage2D(GLenum texImageTarget, GLint level,
                                  GLint xoffset, GLint yoffset, GLsizei width,
                                  GLsizei height, GLenum format,
                                  const dom::ArrayBufferView& view);
     void CopyTexImage2D(GLenum texImageTarget, GLint level,
@@ -832,18 +839,20 @@ public:
         UniformMatrix4fv_base(loc, transpose, value.Length(),
                               value.Elements());
     }
     void UniformMatrix4fv_base(WebGLUniformLocation* loc,
                                WebGLboolean transpose, size_t arrayLength,
                                const float* data);
 
     void UseProgram(WebGLProgram* prog);
+
     bool ValidateAttribArraySetter(const char* name, uint32_t count,
                                    uint32_t arrayLength);
+    bool ValidateUniformLocation(WebGLUniformLocation* loc, const char* funcName);
     bool ValidateUniformSetter(WebGLUniformLocation* loc, uint8_t setterSize,
                                GLenum setterType, const char* info,
                                GLuint* out_rawLoc);
     bool ValidateUniformArraySetter(WebGLUniformLocation* loc,
                                     uint8_t setterElemSize, GLenum setterType,
                                     size_t setterArraySize, const char* info,
                                     GLuint* out_rawLoc,
                                     GLsizei* out_numElementsToUpload);
@@ -1113,18 +1122,19 @@ protected:
     GLuint mActiveTexture;
 
     // glGetError sources:
     bool mEmitContextLostErrorOnce;
     GLenum mWebGLError;
     GLenum mUnderlyingGLError;
     GLenum GetAndFlushUnderlyingGLErrors();
 
-    // whether shader validation is supported
-    bool mShaderValidation;
+    bool mBypassShaderValidation;
+
+    webgl::ShaderValidator* CreateShaderValidator(GLenum shaderType) const;
 
     // some GL constants
     int32_t mGLMaxVertexAttribs;
     int32_t mGLMaxTextureUnits;
     int32_t mGLMaxTextureSize;
     int32_t mGLMaxTextureSizeLog2;
     int32_t mGLMaxCubeMapTextureSize;
     int32_t mGLMaxCubeMapTextureSizeLog2;
@@ -1139,16 +1149,20 @@ protected:
     GLuint  mGLMaxTransformFeedbackSeparateAttribs;
     GLuint  mGLMaxUniformBufferBindings;
 
 public:
     GLuint MaxVertexAttribs() const {
         return mGLMaxVertexAttribs;
     }
 
+    GLuint GLMaxTextureUnits() const {
+        return mGLMaxTextureUnits;
+    }
+
 
     bool IsFormatValidForFB(GLenum sizedFormat) const;
 
 protected:
     // Represents current status of the context with respect to context loss.
     // That is, whether the context is lost, and what part of the context loss
     // process we currently are at.
     // This is used to support the WebGL spec's asyncronous nature in handling
@@ -1220,20 +1234,16 @@ protected:
                               WebGLTexImageFunc func, WebGLTexDimensions dims);
     bool ValidateDrawModeEnum(GLenum mode, const char* info);
     bool ValidateAttribIndex(GLuint index, const char* info);
     bool ValidateAttribPointer(bool integerMode, GLuint index, GLint size, GLenum type,
                                WebGLboolean normalized, GLsizei stride,
                                WebGLintptr byteOffset, const char* info);
     bool ValidateStencilParamsForDrawCall();
 
-    bool ValidateGLSLVariableName(const nsAString& name, const char* info);
-    bool ValidateGLSLCharacter(char16_t c);
-    bool ValidateGLSLString(const nsAString& string, const char* info);
-
     bool ValidateCopyTexImage(GLenum internalFormat, WebGLTexImageFunc func,
                               WebGLTexDimensions dims);
 
     bool ValidateSamplerParameterName(GLenum pname, const char* info);
     bool ValidateSamplerParameterParams(GLenum pname, const WebGLIntOrFloat& param, const char* info);
 
     bool ValidateTexImage(TexImageTarget texImageTarget,
                           GLint level, GLenum internalFormat,
@@ -1270,16 +1280,20 @@ protected:
                                   GLsizei levelHeight, WebGLTexImageFunc func,
                                   WebGLTexDimensions dims);
     bool ValidateCompTexImageDataSize(GLint level, GLenum internalFormat,
                                       GLsizei width, GLsizei height,
                                       uint32_t byteLength,
                                       WebGLTexImageFunc func,
                                       WebGLTexDimensions dims);
 
+    bool ValidateUniformLocationForProgram(WebGLUniformLocation* location,
+                                           WebGLProgram* program,
+                                           const char* funcName);
+
     void Invalidate();
     void DestroyResourcesAndContext();
 
     void MakeContextCurrent() const;
 
     // helpers
 
     // If jsArrayType is MaxTypedArrayViewType, it means no array.
@@ -1398,16 +1412,17 @@ protected:
     void ForceLoseContext(bool simulateLoss = false);
     void ForceRestoreContext();
 
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound2DTextures;
     nsTArray<WebGLRefPtr<WebGLTexture> > mBoundCubeMapTextures;
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound3DTextures;
 
     WebGLRefPtr<WebGLProgram> mCurrentProgram;
+    RefPtr<const webgl::LinkedProgramInfo> mActiveProgramLinkInfo;
 
     uint32_t mMaxFramebufferColorAttachments;
 
     bool ValidateFramebufferTarget(GLenum target, const char* const info);
 
     WebGLRefPtr<WebGLFramebuffer> mBoundDrawFramebuffer;
     WebGLRefPtr<WebGLFramebuffer> mBoundReadFramebuffer;
     WebGLRefPtr<WebGLRenderbuffer> mBoundRenderbuffer;
@@ -1590,17 +1605,17 @@ inline bool
 WebGLContext::ValidateObjectAssumeNonNull(const char* info, ObjectType* object)
 {
     MOZ_ASSERT(object);
 
     if (!ValidateObjectAllowDeletedOrNull(info, object))
         return false;
 
     if (object->IsDeleted()) {
-        ErrorInvalidValue("%s: deleted object passed as argument", info);
+        ErrorInvalidValue("%s: Deleted object passed as argument.", info);
         return false;
     }
 
     return true;
 }
 
 template<class ObjectType>
 inline bool
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -9,17 +9,16 @@
 #include "mozilla/CheckedInt.h"
 #include "WebGLBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShader.h"
 #include "WebGLTexture.h"
-#include "WebGLUniformInfo.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gl;
 
 // For a Tegra workaround.
@@ -405,21 +404,25 @@ void WebGLContext::Draw_cleanup()
 /*
  * Verify that state is consistent for drawing, and compute max number of elements (maxAllowedCount)
  * that will be legal to be read from bound VBOs.
  */
 
 bool
 WebGLContext::ValidateBufferFetching(const char* info)
 {
+    MOZ_ASSERT(mCurrentProgram);
+    // Note that mCurrentProgram->IsLinked() is NOT GUARANTEED.
+    MOZ_ASSERT(mActiveProgramLinkInfo);
+
 #ifdef DEBUG
     GLint currentProgram = 0;
     MakeContextCurrent();
     gl->fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, &currentProgram);
-    MOZ_ASSERT(GLuint(currentProgram) == mCurrentProgram->GLName(),
+    MOZ_ASSERT(GLuint(currentProgram) == mCurrentProgram->mGLName,
                "WebGL: current program doesn't agree with GL state");
 #endif
 
     if (mBufferFetchingIsVerified)
         return true;
 
     bool hasPerVertex = false;
     uint32_t maxVertices = UINT32_MAX;
@@ -436,17 +439,17 @@ WebGLContext::ValidateBufferFetching(con
 
         if (vd.buf == nullptr) {
             ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %d!", info, i);
             return false;
         }
 
         // If the attrib is not in use, then we don't have to validate
         // it, just need to make sure that the binding is non-null.
-        if (!mCurrentProgram->IsAttribInUse(i))
+        if (!mActiveProgramLinkInfo->HasActiveAttrib(i))
             continue;
 
         // the base offset
         CheckedUint32 checked_byteLength = CheckedUint32(vd.buf->ByteLength()) - vd.byteOffset;
         CheckedUint32 checked_sizeOfLastElement = CheckedUint32(vd.componentSize()) * vd.size;
 
         if (!checked_byteLength.isValid() ||
             !checked_sizeOfLastElement.isValid())
@@ -493,34 +496,35 @@ WebGLContext::ValidateBufferFetching(con
 
     return true;
 }
 
 WebGLVertexAttrib0Status
 WebGLContext::WhatDoesVertexAttrib0Need()
 {
     MOZ_ASSERT(mCurrentProgram);
+    MOZ_ASSERT(mActiveProgramLinkInfo);
 
     // work around Mac OSX crash, see bug 631420
 #ifdef XP_MACOSX
     if (gl->WorkAroundDriverBugs() &&
         mBoundVertexArray->IsAttribArrayEnabled(0) &&
-        !mCurrentProgram->IsAttribInUse(0))
+        !mActiveProgramLinkInfo->HasActiveAttrib(0))
     {
         return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
     }
 #endif
 
     if (MOZ_LIKELY(gl->IsGLES() ||
                    mBoundVertexArray->IsAttribArrayEnabled(0)))
     {
         return WebGLVertexAttrib0Status::Default;
     }
 
-    return mCurrentProgram->IsAttribInUse(0)
+    return mActiveProgramLinkInfo->HasActiveAttrib(0)
            ? WebGLVertexAttrib0Status::EmulatedInitializedArray
            : WebGLVertexAttrib0Status::EmulatedUninitializedArray;
 }
 
 bool
 WebGLContext::DoFakeVertexAttrib0(GLuint vertexCount)
 {
     WebGLVertexAttrib0Status whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -47,21 +47,18 @@
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/Endian.h"
 #include "mozilla/fallible.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::gfx;
 using namespace mozilla::gl;
-using namespace mozilla::gfx;
-
-static bool BaseTypeAndSizeFromUniformType(GLenum uType, GLenum* baseType,
-                                           GLint* unitSize);
 
 static const WebGLRectangleObject*
 CurValidFBRectObject(const WebGLContext* webgl,
                      const WebGLFramebuffer* boundFB)
 {
     const WebGLRectangleObject* rect = nullptr;
 
     if (boundFB) {
@@ -117,63 +114,34 @@ WebGLContext::ActiveTexture(GLenum textu
 void
 WebGLContext::AttachShader(WebGLProgram* program, WebGLShader* shader)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("attachShader: program", program) ||
         !ValidateObject("attachShader: shader", shader))
+    {
         return;
-
-    // Per GLSL ES 2.0, we can only have one of each type of shader
-    // attached.  This renders the next test somewhat moot, but we'll
-    // leave it for when we support more than one shader of each type.
-    if (program->HasAttachedShaderOfType(shader->ShaderType()))
-        return ErrorInvalidOperation("attachShader: only one of each type of shader may be attached to a program");
-
-    if (!program->AttachShader(shader))
-        return ErrorInvalidOperation("attachShader: shader is already attached");
+    }
+
+    program->AttachShader(shader);
 }
 
 void
 WebGLContext::BindAttribLocation(WebGLProgram* prog, GLuint location,
                                  const nsAString& name)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("bindAttribLocation: program", prog))
         return;
 
-    GLuint progname = prog->GLName();
-
-    if (!ValidateGLSLVariableName(name, "bindAttribLocation"))
-        return;
-
-    if (location >= MaxVertexAttribs()) {
-        return ErrorInvalidValue("bindAttribLocation: `location` must be less"
-                                 " than MAX_VERTEX_ATTRIBS.");
-    }
-
-    if (StringBeginsWith(name, NS_LITERAL_STRING("gl_")))
-        return ErrorInvalidOperation("bindAttribLocation: can't set the"
-                                     " location of a name that starts with"
-                                     " 'gl_'.");
-
-    NS_LossyConvertUTF16toASCII cname(name);
-    nsCString mappedName;
-    if (mShaderValidation) {
-        WebGLProgram::HashMapIdentifier(cname, &mappedName);
-    } else {
-        mappedName.Assign(cname);
-    }
-
-    MakeContextCurrent();
-    gl->fBindAttribLocation(progname, location, mappedName.get());
+    prog->BindAttribLocation(location, name);
 }
 
 void
 WebGLContext::BindFramebuffer(GLenum target, WebGLFramebuffer* wfb)
 {
     if (IsContextLost())
         return;
 
@@ -824,24 +792,25 @@ WebGLContext::DeleteShader(WebGLShader* 
 }
 
 void
 WebGLContext::DetachShader(WebGLProgram* program, WebGLShader* shader)
 {
     if (IsContextLost())
         return;
 
+    // It's valid to attempt to detach a deleted shader, since it's still a
+    // shader.
     if (!ValidateObject("detachShader: program", program) ||
-        // it's valid to attempt to detach a deleted shader, since it's
-        // still a shader
         !ValidateObjectAllowDeleted("detashShader: shader", shader))
+    {
         return;
-
-    if (!program->DetachShader(shader))
-        return ErrorInvalidOperation("detachShader: shader is not attached");
+    }
+
+    program->DetachShader(shader);
 }
 
 void
 WebGLContext::DepthFunc(GLenum func)
 {
     if (IsContextLost())
         return;
 
@@ -969,60 +938,16 @@ WebGLContext::FrontFace(GLenum mode)
         default:
             return ErrorInvalidEnumInfo("frontFace: mode", mode);
     }
 
     MakeContextCurrent();
     gl->fFrontFace(mode);
 }
 
-already_AddRefed<WebGLActiveInfo>
-WebGLContext::GetActiveAttrib(WebGLProgram* prog, uint32_t index)
-{
-    if (IsContextLost())
-        return nullptr;
-
-    if (!ValidateObject("getActiveAttrib: program", prog))
-        return nullptr;
-
-    MakeContextCurrent();
-    GLuint progname = prog->GLName();
-
-    GLuint activeAttribs = 0;
-    gl->fGetProgramiv(progname, LOCAL_GL_ACTIVE_ATTRIBUTES,
-                      (GLint*)&activeAttribs);
-    if (index >= activeAttribs) {
-        ErrorInvalidValue("`index` (%i) must be less than ACTIVE_ATTRIBUTES"
-                          " (%i).",
-                          index, activeAttribs);
-        return nullptr;
-    }
-
-    GLint len = 0;
-    gl->fGetProgramiv(progname, LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &len);
-    if (len == 0)
-        return nullptr;
-
-    nsAutoArrayPtr<char> name(new char[len]);
-    GLint attrsize = 0;
-    GLuint attrtype = 0;
-
-    gl->fGetActiveAttrib(progname, index, len, &len, &attrsize, &attrtype, name);
-    if (attrsize == 0 || attrtype == 0) {
-        return nullptr;
-    }
-
-    nsCString reverseMappedName;
-    prog->ReverseMapIdentifier(nsDependentCString(name), &reverseMappedName);
-
-    nsRefPtr<WebGLActiveInfo> retActiveInfo =
-        new WebGLActiveInfo(attrsize, attrtype, reverseMappedName);
-    return retActiveInfo.forget();
-}
-
 void
 WebGLContext::GenerateMipmap(GLenum rawTarget)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateTextureTargetEnum(rawTarget, "generateMipmap"))
         return;
@@ -1078,119 +1003,68 @@ WebGLContext::GenerateMipmap(GLenum rawT
         gl->fGenerateMipmap(target.get());
         gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, tex->MinFilter().get());
     } else {
         gl->fGenerateMipmap(target.get());
     }
 }
 
 already_AddRefed<WebGLActiveInfo>
-WebGLContext::GetActiveUniform(WebGLProgram* prog, uint32_t index)
+WebGLContext::GetActiveAttrib(WebGLProgram* prog, GLuint index)
+{
+    if (IsContextLost())
+        return nullptr;
+
+    if (!ValidateObject("getActiveAttrib: program", prog))
+        return nullptr;
+
+    return prog->GetActiveAttrib(index);
+}
+
+already_AddRefed<WebGLActiveInfo>
+WebGLContext::GetActiveUniform(WebGLProgram* prog, GLuint index)
 {
     if (IsContextLost())
         return nullptr;
 
     if (!ValidateObject("getActiveUniform: program", prog))
         return nullptr;
 
-    MakeContextCurrent();
-    GLuint progname = prog->GLName();
-
-    GLuint activeUniforms = 0;
-    gl->fGetProgramiv(progname, LOCAL_GL_ACTIVE_UNIFORMS,
-                      (GLint*)&activeUniforms);
-    if (index >= activeUniforms) {
-        ErrorInvalidValue("`index` (%i) must be less than ACTIVE_UNIFORMS"
-                          " (%i).",
-                          index, activeUniforms);
-        return nullptr;
-    }
-
-    GLint len = 0;
-    gl->fGetProgramiv(progname, LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH, &len);
-    if (len == 0)
-        return nullptr;
-
-    nsAutoArrayPtr<char> name(new char[len]);
-
-    GLint usize = 0;
-    GLuint utype = 0;
-
-    gl->fGetActiveUniform(progname, index, len, &len, &usize, &utype, name);
-    if (len == 0 || usize == 0 || utype == 0) {
-        return nullptr;
-    }
-
-    nsCString reverseMappedName;
-    prog->ReverseMapIdentifier(nsDependentCString(name), &reverseMappedName);
-
-    // OpenGL ES 2.0 specifies that if foo is a uniform array, GetActiveUniform returns its name as "foo[0]".
-    // See section 2.10 page 35 in the OpenGL ES 2.0.24 specification:
-    //
-    // > If the active uniform is an array, the uniform name returned in name will always
-    // > be the name of the uniform array appended with "[0]".
-    //
-    // There is no such requirement in the OpenGL (non-ES) spec and indeed we have OpenGL implementations returning
-    // "foo" instead of "foo[0]". So, when implementing WebGL on top of desktop OpenGL, we must check if the
-    // returned name ends in [0], and if it doesn't, append that.
-    //
-    // In principle we don't need to do that on OpenGL ES, but this is such a tricky difference between the ES and non-ES
-    // specs that it seems probable that some ES implementers will overlook it. Since the work-around is quite cheap,
-    // we do it unconditionally.
-    if (usize > 1 && reverseMappedName.CharAt(reverseMappedName.Length()-1) != ']')
-        reverseMappedName.AppendLiteral("[0]");
-
-    nsRefPtr<WebGLActiveInfo> retActiveInfo =
-        new WebGLActiveInfo(usize, utype, reverseMappedName);
-    return retActiveInfo.forget();
+    return prog->GetActiveUniform(index);
 }
 
 void
 WebGLContext::GetAttachedShaders(WebGLProgram* prog,
                                  Nullable<nsTArray<nsRefPtr<WebGLShader>>>& retval)
 {
     retval.SetNull();
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowNull("getAttachedShaders", prog))
+    if (!prog) {
+        ErrorInvalidValue("getAttachedShaders: Invalid program.");
         return;
-
-    MakeContextCurrent();
-
-    if (!prog) {
-        retval.SetNull();
-        ErrorInvalidValue("getAttachedShaders: invalid program");
-    } else if (prog->AttachedShaders().Length() == 0) {
-        retval.SetValue().TruncateLength(0);
-    } else {
-        retval.SetValue().AppendElements(prog->AttachedShaders());
     }
+
+    if (!ValidateObject("getAttachedShaders", prog))
+        return;
+
+    prog->GetAttachedShaders(&retval.SetValue());
 }
 
 GLint
 WebGLContext::GetAttribLocation(WebGLProgram* prog, const nsAString& name)
 {
     if (IsContextLost())
         return -1;
 
     if (!ValidateObject("getAttribLocation: program", prog))
         return -1;
 
-    if (!ValidateGLSLVariableName(name, "getAttribLocation"))
-        return -1;
-
-    NS_LossyConvertUTF16toASCII cname(name);
-    nsCString mappedName;
-    prog->MapIdentifier(cname, &mappedName);
-
-    GLuint progname = prog->GLName();
-
-    MakeContextCurrent();
-    return gl->fGetAttribLocation(progname, mappedName.get());
+    return prog->GetAttribLocation(name);
 }
 
 JS::Value
 WebGLContext::GetBufferParameter(GLenum target, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
 
@@ -1541,110 +1415,33 @@ JS::Value
 WebGLContext::GetProgramParameter(WebGLProgram* prog, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
 
     if (!ValidateObjectAllowDeleted("getProgramParameter: program", prog))
         return JS::NullValue();
 
-    GLuint progname = prog->GLName();
-
-    MakeContextCurrent();
-
-    GLint i = 0;
-
-    if (IsWebGL2()) {
-        switch (pname) {
-        case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
-        case LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH:
-            gl->fGetProgramiv(progname, pname, &i);
-            return JS::Int32Value(i);
-        }
-    }
-
-    switch (pname) {
-        case LOCAL_GL_ATTACHED_SHADERS:
-        case LOCAL_GL_ACTIVE_UNIFORMS:
-        case LOCAL_GL_ACTIVE_ATTRIBUTES:
-            gl->fGetProgramiv(progname, pname, &i);
-            return JS::Int32Value(i);
-
-        case LOCAL_GL_DELETE_STATUS:
-            return JS::BooleanValue(prog->IsDeleteRequested());
-
-        case LOCAL_GL_LINK_STATUS:
-            return JS::BooleanValue(prog->LinkStatus());
-
-        case LOCAL_GL_VALIDATE_STATUS:
-#ifdef XP_MACOSX
-            // See comment in ValidateProgram below.
-            if (gl->WorkAroundDriverBugs())
-                i = 1;
-            else
-                gl->fGetProgramiv(progname, pname, &i);
-#else
-            gl->fGetProgramiv(progname, pname, &i);
-#endif
-            return JS::BooleanValue(bool(i));
-
-        default:
-            ErrorInvalidEnumInfo("getProgramParameter: parameter", pname);
-    }
-
-    return JS::NullValue();
+    return prog->GetProgramParameter(pname);
 }
 
 void
 WebGLContext::GetProgramInfoLog(WebGLProgram* prog, nsAString& retval)
 {
-    nsAutoCString s;
-    GetProgramInfoLog(prog, s);
-    if (s.IsVoid())
-        retval.SetIsVoid(true);
-    else
-        CopyASCIItoUTF16(s, retval);
-}
-
-void
-WebGLContext::GetProgramInfoLog(WebGLProgram* prog, nsACString& retval)
-{
+    retval.SetIsVoid(true);
+
     if (IsContextLost())
-    {
-        retval.SetIsVoid(true);
-        return;
-    }
-
-    if (!ValidateObject("getProgramInfoLog: program", prog)) {
-        retval.Truncate();
         return;
-    }
-
-    GLuint progname = prog->GLName();
-
-    MakeContextCurrent();
-
-    GLint k = -1;
-    gl->fGetProgramiv(progname, LOCAL_GL_INFO_LOG_LENGTH, &k);
-    if (k == -1) {
-        // If GetProgramiv doesn't modify |k|,
-        // it's because there was a GL error.
-        // GetProgramInfoLog should return null on error. (Bug 746740)
-        retval.SetIsVoid(true);
+
+    if (!ValidateObject("getProgramInfoLog: program", prog))
         return;
-    }
-
-    if (k == 0) {
-        retval.Truncate();
-        return;
-    }
-
-    retval.SetCapacity(k);
-    gl->fGetProgramInfoLog(progname, k, &k, (char*) retval.BeginWriting());
-    retval.SetLength(k);
+
+    prog->GetProgramInfoLog(&retval);
+
+    retval.SetIsVoid(false);
 }
 
 // here we have to support all pnames with both int and float params.
 // See this discussion:
 //  https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html
 void WebGLContext::TexParameter_base(GLenum rawTarget, GLenum pname,
                                      GLint* intParamPtr,
                                      GLfloat* floatParamPtr)
@@ -1842,181 +1639,44 @@ WebGLContext::GetTexParameterInternal(co
         default:
             ErrorInvalidEnumInfo("getTexParameter: parameter", pname);
     }
 
     return JS::NullValue();
 }
 
 JS::Value
-WebGLContext::GetUniform(JSContext* cx, WebGLProgram* prog,
-                         WebGLUniformLocation* location)
+WebGLContext::GetUniform(JSContext* js, WebGLProgram* prog,
+                         WebGLUniformLocation* loc)
 {
     if (IsContextLost())
         return JS::NullValue();
 
-    if (!ValidateObject("getUniform: program", prog))
-        return JS::NullValue();
-
-    if (!ValidateObject("getUniform: location", location))
+    if (!ValidateObject("getUniform: `program`", prog))
         return JS::NullValue();
 
-    if (location->Program() != prog) {
-        ErrorInvalidValue("getUniform: this uniform location corresponds to another program");
-        return JS::NullValue();
-    }
-
-    if (location->ProgramGeneration() != prog->Generation()) {
-        ErrorInvalidOperation("getUniform: this uniform location is obsolete since the program has been relinked");
+    if (!ValidateObject("getUniform: `location`", loc))
         return JS::NullValue();
-    }
-
-    GLuint progname = prog->GLName();
-
-    MakeContextCurrent();
-
-    GLint uniforms = 0;
-    GLint uniformNameMaxLength = 0;
-    gl->fGetProgramiv(progname, LOCAL_GL_ACTIVE_UNIFORMS, &uniforms);
-    gl->fGetProgramiv(progname, LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformNameMaxLength);
-
-    // we now need the type info to switch between fGetUniformfv and fGetUniformiv
-    // the only way to get that is to iterate through all active uniforms by index until
-    // one matches the given uniform location.
-    GLenum uniformType = 0;
-    nsAutoArrayPtr<GLchar> uniformName(new GLchar[uniformNameMaxLength]);
-    // this buffer has 16 more bytes to be able to store [index] at the end.
-    nsAutoArrayPtr<GLchar> uniformNameBracketIndex(new GLchar[uniformNameMaxLength + 16]);
-
-    GLint index;
-    for (index = 0; index < uniforms; ++index) {
-        GLsizei length;
-        GLint size;
-        gl->fGetActiveUniform(progname, index, uniformNameMaxLength, &length,
-                              &size, &uniformType, uniformName);
-        if (gl->fGetUniformLocation(progname, uniformName) == location->Location())
-            break;
-
-        // now we handle the case of array uniforms. In that case, fGetActiveUniform returned as 'size'
-        // the biggest index used plus one, so we need to loop over that. The 0 index has already been handled above,
-        // so we can start at one. For each index, we construct the string uniformName + "[" + index + "]".
-        if (size > 1) {
-            bool found_it = false;
-            if (uniformName[length - 1] == ']') { // if uniformName ends in [0]
-                // remove the [0] at the end
-                length -= 3;
-                uniformName[length] = 0;
-            }
-            for (GLint arrayIndex = 1; arrayIndex < size; arrayIndex++) {
-                sprintf(uniformNameBracketIndex.get(), "%s[%d]", uniformName.get(), arrayIndex);
-                if (gl->fGetUniformLocation(progname, uniformNameBracketIndex) == location->Location()) {
-                    found_it = true;
-                    break;
-                }
-            }
-            if (found_it) break;
-        }
-    }
-
-    if (index == uniforms) {
-        GenerateWarning("getUniform: internal error: hit an OpenGL driver bug");
+
+    if (!loc->ValidateForProgram(prog, this, "getUniform"))
         return JS::NullValue();
-    }
-
-    GLenum baseType;
-    GLint unitSize;
-    if (!BaseTypeAndSizeFromUniformType(uniformType, &baseType, &unitSize)) {
-        GenerateWarning("getUniform: internal error: unknown uniform type 0x%x", uniformType);
-        return JS::NullValue();
-    }
-
-    // this should never happen
-    if (unitSize > 16) {
-        GenerateWarning("getUniform: internal error: unexpected uniform unit size %d", unitSize);
-        return JS::NullValue();
-    }
-
-    if (baseType == LOCAL_GL_FLOAT) {
-        GLfloat fv[16] = { GLfloat(0) };
-        gl->fGetUniformfv(progname, location->Location(), fv);
-        if (unitSize == 1) {
-            return JS::DoubleValue(fv[0]);
-        } else {
-            JSObject* obj = Float32Array::Create(cx, this, unitSize, fv);
-            if (!obj) {
-                ErrorOutOfMemory("getUniform: out of memory");
-                return JS::NullValue();
-            }
-            return JS::ObjectOrNullValue(obj);
-        }
-    } else if (baseType == LOCAL_GL_INT) {
-        GLint iv[16] = { 0 };
-        gl->fGetUniformiv(progname, location->Location(), iv);
-        if (unitSize == 1) {
-            return JS::Int32Value(iv[0]);
-        } else {
-            JSObject* obj = Int32Array::Create(cx, this, unitSize, iv);
-            if (!obj) {
-                ErrorOutOfMemory("getUniform: out of memory");
-                return JS::NullValue();
-            }
-            return JS::ObjectOrNullValue(obj);
-        }
-    } else if (baseType == LOCAL_GL_BOOL) {
-        GLint iv[16] = { 0 };
-        gl->fGetUniformiv(progname, location->Location(), iv);
-        if (unitSize == 1) {
-            return JS::BooleanValue(iv[0] ? true : false);
-        } else {
-            bool uv[16];
-            for (int k = 0; k < unitSize; k++)
-                uv[k] = iv[k];
-            JS::Rooted<JS::Value> val(cx);
-            // Be careful: we don't want to convert all of |uv|!
-            if (!ToJSValue(cx, uv, unitSize, &val)) {
-                ErrorOutOfMemory("getUniform: out of memory");
-                return JS::NullValue();
-            }
-            return val;
-        }
-    }
-
-    // Else preserving behavior, but I'm not sure this is correct per spec
-    return JS::UndefinedValue();
+
+    return loc->GetUniform(js, this);
 }
 
 already_AddRefed<WebGLUniformLocation>
 WebGLContext::GetUniformLocation(WebGLProgram* prog, const nsAString& name)
 {
     if (IsContextLost())
         return nullptr;
 
     if (!ValidateObject("getUniformLocation: program", prog))
         return nullptr;
 
-    if (!ValidateGLSLVariableName(name, "getUniformLocation"))
-        return nullptr;
-
-    NS_LossyConvertUTF16toASCII cname(name);
-    nsCString mappedName;
-    prog->MapIdentifier(cname, &mappedName);
-
-    GLuint progname = prog->GLName();
-    MakeContextCurrent();
-    GLint intlocation = gl->fGetUniformLocation(progname, mappedName.get());
-
-    nsRefPtr<WebGLUniformLocation> loc;
-    if (intlocation >= 0) {
-        WebGLUniformInfo info = prog->GetUniformInfoForMappedIdentifier(mappedName);
-        loc = new WebGLUniformLocation(this,
-                                       prog,
-                                       intlocation,
-                                       info);
-    }
-    return loc.forget();
+    return prog->GetUniformLocation(name);
 }
 
 void
 WebGLContext::Hint(GLenum target, GLenum mode)
 {
     if (IsContextLost())
         return;
 
@@ -2086,175 +1746,37 @@ WebGLContext::IsTexture(WebGLTexture* te
     if (IsContextLost())
         return false;
 
     return ValidateObjectAllowDeleted("isTexture", tex) &&
         !tex->IsDeleted() &&
         tex->HasEverBeenBound();
 }
 
-// Try to bind an attribute that is an array to location 0:
-bool
-WebGLContext::BindArrayAttribToLocation0(WebGLProgram* program)
-{
-    if (mBoundVertexArray->IsAttribArrayEnabled(0)) {
-        return false;
-    }
-
-    GLint leastArrayLocation = -1;
-
-    std::map<GLint, nsCString>::iterator itr;
-    for (itr = program->mActiveAttribMap.begin();
-         itr != program->mActiveAttribMap.end();
-         itr++) {
-        int32_t index = itr->first;
-        if (mBoundVertexArray->IsAttribArrayEnabled(index) &&
-            index < leastArrayLocation)
-        {
-            leastArrayLocation = index;
-        }
-    }
-
-    if (leastArrayLocation > 0) {
-        nsCString& attrName = program->mActiveAttribMap.find(leastArrayLocation)->second;
-        const char* attrNameCStr = attrName.get();
-        gl->fBindAttribLocation(program->GLName(), 0, attrNameCStr);
-        return true;
-    }
-    return false;
-}
-
-static void
-LinkAndUpdateProgram(GLContext* gl, WebGLProgram* prog)
-{
-    GLuint name = prog->GLName();
-    gl->fLinkProgram(name);
-
-    prog->SetLinkStatus(false);
-
-    GLint ok = 0;
-    gl->fGetProgramiv(name, LOCAL_GL_LINK_STATUS, &ok);
-    if (!ok)
-        return;
-
-    if (!prog->UpdateInfo())
-        return;
-
-    prog->SetLinkStatus(true);
-}
-
 void
-WebGLContext::LinkProgram(WebGLProgram* program)
+WebGLContext::LinkProgram(WebGLProgram* prog)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateObject("linkProgram", program))
-        return;
-
-    InvalidateBufferFetching(); // we do it early in this function
-    // as some of the validation below changes program state
-
-    if (!program->NextGeneration()) {
-        // XXX throw?
-        return;
-    }
-
-    if (!program->HasBothShaderTypesAttached()) {
-        GenerateWarning("linkProgram: this program doesn't have both a vertex"
-                        " shader and a fragment shader");
-        program->SetLinkStatus(false);
-        return;
-    }
-
-    if (program->HasBadShaderAttached()) {
-        GenerateWarning("linkProgram: The program has bad shaders attached.");
-        program->SetLinkStatus(false);
+    if (!ValidateObject("linkProgram", prog))
         return;
-    }
-
-    // bug 777028
-    // Mesa can't handle more than 16 samplers per program, counting each array entry.
-    if (gl->WorkAroundDriverBugs() &&
-        mIsMesa &&
-        program->UpperBoundNumSamplerUniforms() > 16)
-    {
-        GenerateWarning("Programs with more than 16 samplers are disallowed on"
-                        " Mesa drivers to avoid a Mesa crasher.");
-        program->SetLinkStatus(false);
-        return;
-    }
-
-    MakeContextCurrent();
-    LinkAndUpdateProgram(gl, program);
-
-    if (program->LinkStatus()) {
-        if (BindArrayAttribToLocation0(program)) {
-            GenerateWarning("linkProgram: Relinking program to make attrib0 an"
-                            " array.");
-            LinkAndUpdateProgram(gl, program);
+
+    prog->LinkProgram();
+
+    if (prog->IsLinked()) {
+        mActiveProgramLinkInfo = prog->LinkInfo();
+
+        if (gl->WorkAroundDriverBugs() &&
+            gl->Vendor() == gl::GLVendor::NVIDIA)
+        {
+            if (mCurrentProgram == prog)
+                gl->fUseProgram(prog->mGLName);
         }
     }
-
-    if (!program->LinkStatus()) {
-        if (ShouldGenerateWarnings()) {
-            // report shader/program infoLogs as warnings.
-            // note that shader compilation errors can be deferred to linkProgram,
-            // which is why we can't do anything in compileShader. In practice we could
-            // report in compileShader the translation errors generated by ANGLE,
-            // but it seems saner to keep a single way of obtaining shader infologs.
-
-            nsAutoCString log;
-
-            bool alreadyReportedShaderInfoLog = false;
-
-            for (size_t i = 0; i < program->AttachedShaders().Length(); i++) {
-
-                WebGLShader* shader = program->AttachedShaders()[i];
-
-                if (shader->CompileStatus())
-                    continue;
-
-                const char* shaderTypeName = nullptr;
-                if (shader->ShaderType() == LOCAL_GL_VERTEX_SHADER) {
-                    shaderTypeName = "vertex";
-                } else if (shader->ShaderType() == LOCAL_GL_FRAGMENT_SHADER) {
-                    shaderTypeName = "fragment";
-                } else {
-                    // should have been validated earlier
-                    MOZ_ASSERT(false);
-                    shaderTypeName = "<unknown>";
-                }
-
-                GetShaderInfoLog(shader, log);
-
-                GenerateWarning("linkProgram: a %s shader used in this program failed to "
-                                "compile, with this log:\n%s\n",
-                                shaderTypeName,
-                                log.get());
-                alreadyReportedShaderInfoLog = true;
-            }
-
-            if (!alreadyReportedShaderInfoLog) {
-                GetProgramInfoLog(program, log);
-                if (!log.IsEmpty()) {
-                    GenerateWarning("linkProgram failed, with this log:\n%s\n",
-                                    log.get());
-                }
-            }
-        }
-        return;
-    }
-
-    if (gl->WorkAroundDriverBugs() &&
-        gl->Vendor() == gl::GLVendor::NVIDIA)
-    {
-        if (program == mCurrentProgram)
-            gl->fUseProgram(program->GLName());
-    }
 }
 
 void
 WebGLContext::PixelStorei(GLenum pname, GLint param)
 {
     if (IsContextLost())
         return;
 
@@ -2883,17 +2405,17 @@ WebGLContext::SurfaceFromElementResultTo
 void
 WebGLContext::Uniform1i(WebGLUniformLocation* loc, GLint a1)
 {
     GLuint rawLoc;
     if (!ValidateUniformSetter(loc, 1, LOCAL_GL_INT, "uniform1i", &rawLoc))
         return;
 
     // Only uniform1i can take sampler settings.
-    if (!ValidateSamplerUniformSetter("Uniform1i", loc, a1))
+    if (!loc->ValidateSamplerSetter(a1, this, "uniform1i"))
         return;
 
     MakeContextCurrent();
     gl->fUniform1i(rawLoc, a1);
 }
 
 void
 WebGLContext::Uniform2i(WebGLUniformLocation* loc, GLint a1, GLint a2)
@@ -2986,17 +2508,17 @@ WebGLContext::Uniform1iv_base(WebGLUnifo
     GLsizei numElementsToUpload;
     if (!ValidateUniformArraySetter(loc, 1, LOCAL_GL_INT, arrayLength,
                                     "uniform1iv", &rawLoc,
                                     &numElementsToUpload))
     {
         return;
     }
 
-    if (!ValidateSamplerUniformSetter("uniform1iv", loc, data[0]))
+    if (!loc->ValidateSamplerSetter(data[0], this, "uniform1iv"))
         return;
 
     MakeContextCurrent();
     gl->fUniform1iv(rawLoc, numElementsToUpload, data);
 }
 
 void
 WebGLContext::Uniform2iv_base(WebGLUniformLocation* loc, size_t arrayLength,
@@ -3006,18 +2528,18 @@ WebGLContext::Uniform2iv_base(WebGLUnifo
     GLsizei numElementsToUpload;
     if (!ValidateUniformArraySetter(loc, 2, LOCAL_GL_INT, arrayLength,
                                     "uniform2iv", &rawLoc,
                                     &numElementsToUpload))
     {
         return;
     }
 
-    if (!ValidateSamplerUniformSetter("uniform2iv", loc, data[0]) ||
-        !ValidateSamplerUniformSetter("uniform2iv", loc, data[1]))
+    if (!loc->ValidateSamplerSetter(data[0], this, "uniform2iv") ||
+        !loc->ValidateSamplerSetter(data[1], this, "uniform2iv"))
     {
         return;
     }
 
     MakeContextCurrent();
     gl->fUniform2iv(rawLoc, numElementsToUpload, data);
 }
 
@@ -3029,19 +2551,19 @@ WebGLContext::Uniform3iv_base(WebGLUnifo
     GLsizei numElementsToUpload;
     if (!ValidateUniformArraySetter(loc, 3, LOCAL_GL_INT, arrayLength,
                                     "uniform3iv", &rawLoc,
                                     &numElementsToUpload))
     {
         return;
     }
 
-    if (!ValidateSamplerUniformSetter("uniform3iv", loc, data[0]) ||
-        !ValidateSamplerUniformSetter("uniform3iv", loc, data[1]) ||
-        !ValidateSamplerUniformSetter("uniform3iv", loc, data[2]))
+    if (!loc->ValidateSamplerSetter(data[0], this, "uniform3iv") ||
+        !loc->ValidateSamplerSetter(data[1], this, "uniform3iv") ||
+        !loc->ValidateSamplerSetter(data[2], this, "uniform3iv"))
     {
         return;
     }
 
     MakeContextCurrent();
     gl->fUniform3iv(rawLoc, numElementsToUpload, data);
 }
 
@@ -3053,20 +2575,20 @@ WebGLContext::Uniform4iv_base(WebGLUnifo
     GLsizei numElementsToUpload;
     if (!ValidateUniformArraySetter(loc, 4, LOCAL_GL_INT, arrayLength,
                                     "uniform4iv", &rawLoc,
                                     &numElementsToUpload))
     {
         return;
     }
 
-    if (!ValidateSamplerUniformSetter("uniform4iv", loc, data[0]) ||
-        !ValidateSamplerUniformSetter("uniform4iv", loc, data[1]) ||
-        !ValidateSamplerUniformSetter("uniform4iv", loc, data[2]) ||
-        !ValidateSamplerUniformSetter("uniform4iv", loc, data[3]))
+    if (!loc->ValidateSamplerSetter(data[0], this, "uniform4iv") ||
+        !loc->ValidateSamplerSetter(data[1], this, "uniform4iv") ||
+        !loc->ValidateSamplerSetter(data[2], this, "uniform4iv") ||
+        !loc->ValidateSamplerSetter(data[3], this, "uniform4iv"))
     {
         return;
     }
 
     MakeContextCurrent();
     gl->fUniform4iv(rawLoc, numElementsToUpload, data);
 }
 
@@ -3195,54 +2717,41 @@ WebGLContext::UniformMatrix4fv_base(WebG
 ////////////////////////////////////////////////////////////////////////////////
 
 void
 WebGLContext::UseProgram(WebGLProgram* prog)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowNull("useProgram", prog))
+    if (!prog) {
+        mCurrentProgram = nullptr;
+        mActiveProgramLinkInfo = nullptr;
         return;
-
-    MakeContextCurrent();
-
-    InvalidateBufferFetching();
-
-    GLuint progname = prog ? prog->GLName() : 0;
-
-    if (prog && !prog->LinkStatus())
-        return ErrorInvalidOperation("useProgram: program was not linked successfully");
-
-    gl->fUseProgram(progname);
-
-    mCurrentProgram = prog;
+    }
+
+    if (!ValidateObject("useProgram", prog))
+        return;
+
+    if (prog->UseProgram()) {
+        mCurrentProgram = prog;
+        mActiveProgramLinkInfo = mCurrentProgram->LinkInfo();
+    }
 }
 
 void
 WebGLContext::ValidateProgram(WebGLProgram* prog)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("validateProgram", prog))
         return;
 
-    MakeContextCurrent();
-
-#ifdef XP_MACOSX
-    // see bug 593867 for NVIDIA and bug 657201 for ATI. The latter is confirmed with Mac OS 10.6.7
-    if (gl->WorkAroundDriverBugs()) {
-        GenerateWarning("validateProgram: implemented as a no-operation on Mac to work around crashes");
-        return;
-    }
-#endif
-
-    GLuint progname = prog->GLName();
-    gl->fValidateProgram(progname);
+    prog->ValidateProgram();
 }
 
 already_AddRefed<WebGLFramebuffer>
 WebGLContext::CreateFramebuffer()
 {
     if (IsContextLost())
         return nullptr;
 
@@ -3285,315 +2794,17 @@ void
 WebGLContext::CompileShader(WebGLShader* shader)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("compileShader", shader))
         return;
 
-    GLuint shadername = shader->GLName();
-
-    shader->SetCompileStatus(false);
-
-    // nothing to do if the validator is disabled
-    if (!mShaderValidation)
-        return;
-
-    // nothing to do if translation was already done
-    if (!shader->NeedsTranslation())
-        return;
-
-    MakeContextCurrent();
-
-    ShShaderOutput targetShaderSourceLanguage = gl->IsGLES() ? SH_ESSL_OUTPUT : SH_GLSL_OUTPUT;
-
-    ShHandle compiler = 0;
-    ShBuiltInResources resources;
-
-    memset(&resources, 0, sizeof(ShBuiltInResources));
-
-    ShInitBuiltInResources(&resources);
-
-    resources.MaxVertexAttribs = mGLMaxVertexAttribs;
-    resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
-    resources.MaxVaryingVectors = mGLMaxVaryingVectors;
-    resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits;
-    resources.MaxCombinedTextureImageUnits = mGLMaxTextureUnits;
-    resources.MaxTextureImageUnits = mGLMaxTextureImageUnits;
-    resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors;
-    resources.MaxDrawBuffers = mGLMaxDrawBuffers;
-
-    if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth))
-        resources.EXT_frag_depth = 1;
-
-    if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives))
-        resources.OES_standard_derivatives = 1;
-
-    if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
-        resources.EXT_draw_buffers = 1;
-
-    if (IsExtensionEnabled(WebGLExtensionID::EXT_shader_texture_lod))
-        resources.EXT_shader_texture_lod = 1;
-
-    // Tell ANGLE to allow highp in frag shaders. (unless disabled)
-    // If underlying GLES doesn't have highp in frag shaders, it should complain anyways.
-    resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1;
-
-    resources.HashFunction = WebGLProgram::IdentifierHashFunction;
-
-    if (gl->WorkAroundDriverBugs()) {
-#ifdef XP_MACOSX
-        if (gl->Vendor() == gl::GLVendor::NVIDIA) {
-            // Work around bug 890432
-            resources.MaxExpressionComplexity = 1000;
-        }
-#endif
-    }
-
-    // We're storing an actual instance of StripComments because, if we don't, the
-    // cleanSource nsAString instance will be destroyed before the reference is
-    // actually used.
-    StripComments stripComments(shader->Source());
-    const nsAString& cleanSource = Substring(stripComments.result().Elements(), stripComments.length());
-    if (!ValidateGLSLString(cleanSource, "compileShader"))
-        return;
-
-    // shaderSource() already checks that the source stripped of comments is in the
-    // 7-bit ASCII range, so we can skip the NS_IsAscii() check.
-    NS_LossyConvertUTF16toASCII sourceCString(cleanSource);
-
-    if (gl->WorkAroundDriverBugs()) {
-        const uint32_t maxSourceLength = 0x3ffff;
-        if (sourceCString.Length() > maxSourceLength)
-            return ErrorInvalidValue("compileShader: source has more than %d characters",
-                                     maxSourceLength);
-    }
-
-    const char* s = sourceCString.get();
-
-#define WEBGL2_BYPASS_ANGLE
-#ifdef WEBGL2_BYPASS_ANGLE
-    /*
-     * The bypass don't bring a full support for GLSL ES 3.0, but the main purpose
-     * is to natively bring gl_InstanceID (to do instanced rendering) and gl_FragData
-     *
-     * To remove the bypass code, just comment #define WEBGL2_BYPASS_ANGLE above
-     *
-     * To bypass angle, the context must be a WebGL 2 and the shader must have the
-     * following line at the very top :
-     *      #version proto-200
-     *
-     * In this case, byPassANGLE == true and here is what we do :
-     *  We create two shader source code:
-     *    - one for the driver, that enable GL_EXT_gpu_shader4
-     *    - one for the angle compilor, to get informations about vertex attributes
-     *      and uniforms
-     */
-    static const char* bypassPrefixSearch = "#version proto-200";
-    static const char* bypassANGLEPrefix[2] = {"precision mediump float;\n"
-                                               "#define gl_VertexID 0\n"
-                                               "#define gl_InstanceID 0\n",
-
-                                               "precision mediump float;\n"
-                                               "#extension GL_EXT_draw_buffers : enable\n"
-                                               "#define gl_PrimitiveID 0\n"};
-
-    const bool bypassANGLE = IsWebGL2() && (strstr(s, bypassPrefixSearch) != 0);
-
-    const char* angleShaderCode = s;
-    nsTArray<char> bypassANGLEShaderCode;
-    nsTArray<char> bypassDriverShaderCode;
-
-    if (bypassANGLE) {
-        const int bypassStage = (shader->ShaderType() == LOCAL_GL_FRAGMENT_SHADER) ? 1 : 0;
-        const char* originalShader = strstr(s, bypassPrefixSearch) + strlen(bypassPrefixSearch);
-        int originalShaderSize = strlen(s) - (originalShader - s);
-        int bypassShaderCodeSize = originalShaderSize + 4096 + 1;
-
-        bypassANGLEShaderCode.SetLength(bypassShaderCodeSize);
-        strcpy(bypassANGLEShaderCode.Elements(), bypassANGLEPrefix[bypassStage]);
-        strcat(bypassANGLEShaderCode.Elements(), originalShader);
-
-        bypassDriverShaderCode.SetLength(bypassShaderCodeSize);
-        strcpy(bypassDriverShaderCode.Elements(), "#extension GL_EXT_gpu_shader4 : enable\n");
-        strcat(bypassDriverShaderCode.Elements(), originalShader);
-
-        angleShaderCode = bypassANGLEShaderCode.Elements();
-    }
-#endif
-
-    compiler = ShConstructCompiler(shader->ShaderType(),
-                                   SH_WEBGL_SPEC,
-                                   targetShaderSourceLanguage,
-                                   &resources);
-
-    int compileOptions = SH_VARIABLES |
-                         SH_ENFORCE_PACKING_RESTRICTIONS |
-                         SH_INIT_VARYINGS_WITHOUT_STATIC_USE |
-                         SH_OBJECT_CODE |
-                         SH_LIMIT_CALL_STACK_DEPTH;
-
-    if (resources.MaxExpressionComplexity > 0) {
-        compileOptions |= SH_LIMIT_EXPRESSION_COMPLEXITY;
-    }
-
-#ifndef XP_MACOSX
-    // We want to do this everywhere, but to do this on Mac, we need
-    // to do it only on Mac OSX > 10.6 as this causes the shader
-    // compiler in 10.6 to crash
-    compileOptions |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS;
-#endif
-
-#ifdef XP_MACOSX
-    if (gl->WorkAroundDriverBugs()) {
-        // Work around bug 665578 and bug 769810
-        if (gl->Vendor() == gl::GLVendor::ATI) {
-            compileOptions |= SH_EMULATE_BUILT_IN_FUNCTIONS;
-        }
-
-        // Work around bug 735560
-        if (gl->Vendor() == gl::GLVendor::Intel) {
-            compileOptions |= SH_EMULATE_BUILT_IN_FUNCTIONS;
-        }
-
-        // Work around bug 636926
-        if (gl->Vendor() == gl::GLVendor::NVIDIA) {
-            compileOptions |= SH_UNROLL_FOR_LOOP_WITH_SAMPLER_ARRAY_INDEX;
-        }
-
-        // Work around https://bugs.webkit.org/show_bug.cgi?id=124684,
-        // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb
-        compileOptions |= SH_UNFOLD_SHORT_CIRCUIT;
-    }
-#endif
-
-#ifdef WEBGL2_BYPASS_ANGLE
-    if (!ShCompile(compiler, &angleShaderCode, 1, compileOptions)) {
-#else
-    if (!ShCompile(compiler, &s, 1, compileOptions)) {
-#endif
-        size_t lenWithNull = 0;
-        ShGetInfo(compiler, SH_INFO_LOG_LENGTH, &lenWithNull);
-
-        if (!lenWithNull) {
-            // Error in ShGetInfo.
-            shader->SetTranslationFailure(NS_LITERAL_CSTRING("Internal error: failed to get shader info log"));
-        } else {
-            size_t len = lenWithNull - 1;
-
-            nsAutoCString info;
-            if (len) {
-                // Don't allocate or try to write to zero length string
-                info.SetLength(len); // Allocates len+1, for the null-term.
-                ShGetInfoLog(compiler, info.BeginWriting());
-            }
-            shader->SetTranslationFailure(info);
-        }
-        ShDestruct(compiler);
-        shader->SetCompileStatus(false);
-        return;
-    }
-
-    size_t num_attributes = 0;
-    ShGetInfo(compiler, SH_ACTIVE_ATTRIBUTES, &num_attributes);
-    size_t num_uniforms = 0;
-    ShGetInfo(compiler, SH_ACTIVE_UNIFORMS, &num_uniforms);
-    size_t attrib_max_length = 0;
-    ShGetInfo(compiler, SH_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attrib_max_length);
-    size_t uniform_max_length = 0;
-    ShGetInfo(compiler, SH_ACTIVE_UNIFORM_MAX_LENGTH, &uniform_max_length);
-    size_t mapped_max_length = 0;
-    ShGetInfo(compiler, SH_MAPPED_NAME_MAX_LENGTH, &mapped_max_length);
-
-    shader->mAttribMaxNameLength = attrib_max_length;
-
-    shader->mAttributes.Clear();
-    shader->mUniforms.Clear();
-    shader->mUniformInfos.Clear();
-
-    nsAutoArrayPtr<char> attribute_name(new char[attrib_max_length+1]);
-    nsAutoArrayPtr<char> uniform_name(new char[uniform_max_length+1]);
-    nsAutoArrayPtr<char> mapped_name(new char[mapped_max_length+1]);
-
-    for (size_t i = 0; i < num_uniforms; i++) {
-        size_t length;
-        int size;
-        sh::GLenum type;
-        ShPrecisionType precision;
-        int staticUse;
-        ShGetVariableInfo(compiler, SH_ACTIVE_UNIFORMS, (int)i,
-                          &length, &size, &type,
-                          &precision, &staticUse,
-                          uniform_name,
-                          mapped_name);
-
-        shader->mUniforms.AppendElement(WebGLMappedIdentifier(
-                                            nsDependentCString(uniform_name),
-                                            nsDependentCString(mapped_name)));
-
-        // we need uniform info to validate uniform setter calls
-        char mappedNameLength = strlen(mapped_name);
-        char mappedNameLastChar = mappedNameLength > 1
-                                  ? mapped_name[mappedNameLength - 1]
-                                  : 0;
-        shader->mUniformInfos.AppendElement(WebGLUniformInfo(
-                                                size,
-                                                mappedNameLastChar == ']',
-                                                type));
-    }
-
-    for (size_t i = 0; i < num_attributes; i++) {
-        size_t length;
-        int size;
-        sh::GLenum type;
-        ShPrecisionType precision;
-        int staticUse;
-        ShGetVariableInfo(compiler, SH_ACTIVE_ATTRIBUTES, (int)i,
-                          &length, &size, &type,
-                          &precision, &staticUse,
-                          attribute_name,
-                          mapped_name);
-        shader->mAttributes.AppendElement(WebGLMappedIdentifier(
-                                              nsDependentCString(attribute_name),
-                                              nsDependentCString(mapped_name)));
-    }
-
-    size_t lenWithNull = 0;
-    ShGetInfo(compiler, SH_OBJECT_CODE_LENGTH, &lenWithNull);
-    MOZ_ASSERT(lenWithNull >= 1);
-    size_t len = lenWithNull - 1;
-
-    nsAutoCString translatedSrc;
-    translatedSrc.SetLength(len); // Allocates len+1, for the null-term.
-    ShGetObjectCode(compiler, translatedSrc.BeginWriting());
-
-    CopyASCIItoUTF16(translatedSrc, shader->mTranslatedSource);
-
-    const char* ts = translatedSrc.get();
-
-#ifdef WEBGL2_BYPASS_ANGLE
-    if (bypassANGLE) {
-        const char* driverShaderCode = bypassDriverShaderCode.Elements();
-        gl->fShaderSource(shadername, 1, (const GLchar**) &driverShaderCode, nullptr);
-    } else {
-        gl->fShaderSource(shadername, 1, &ts, nullptr);
-    }
-#else
-    gl->fShaderSource(shadername, 1, &ts, nullptr);
-#endif
-
-    shader->SetTranslationSuccess();
-
-    ShDestruct(compiler);
-
-    gl->fCompileShader(shadername);
-    GLint ok;
-    gl->fGetShaderiv(shadername, LOCAL_GL_COMPILE_STATUS, &ok);
-    shader->SetCompileStatus(ok);
+    shader->CompileShader();
 }
 
 void
 WebGLContext::CompressedTexImage2D(GLenum rawTexImgTarget,
                                    GLint level,
                                    GLenum internalformat,
                                    GLsizei width, GLsizei height, GLint border,
                                    const ArrayBufferView& view)
@@ -3715,91 +2926,33 @@ JS::Value
 WebGLContext::GetShaderParameter(WebGLShader* shader, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
 
     if (!ValidateObject("getShaderParameter: shader", shader))
         return JS::NullValue();
 
-    GLuint shadername = shader->GLName();
-
-    MakeContextCurrent();
-
-    switch (pname) {
-        case LOCAL_GL_SHADER_TYPE:
-        {
-            GLint i = 0;
-            gl->fGetShaderiv(shadername, pname, &i);
-            return JS::NumberValue(uint32_t(i));
-        }
-            break;
-        case LOCAL_GL_DELETE_STATUS:
-            return JS::BooleanValue(shader->IsDeleteRequested());
-            break;
-        case LOCAL_GL_COMPILE_STATUS:
-        {
-            GLint i = 0;
-            gl->fGetShaderiv(shadername, pname, &i);
-            return JS::BooleanValue(bool(i));
-        }
-            break;
-        default:
-            ErrorInvalidEnumInfo("getShaderParameter: parameter", pname);
-    }
-
-    return JS::NullValue();
+    return shader->GetShaderParameter(pname);
 }
 
 void
 WebGLContext::GetShaderInfoLog(WebGLShader* shader, nsAString& retval)
 {
-    nsAutoCString s;
-    GetShaderInfoLog(shader, s);
-    if (s.IsVoid())
-        retval.SetIsVoid(true);
-    else
-        CopyASCIItoUTF16(s, retval);
-}
-
-void
-WebGLContext::GetShaderInfoLog(WebGLShader* shader, nsACString& retval)
-{
+    retval.SetIsVoid(true);
+
     if (IsContextLost())
-    {
-        retval.SetIsVoid(true);
         return;
-    }
 
     if (!ValidateObject("getShaderInfoLog: shader", shader))
         return;
 
-    retval = shader->TranslationLog();
-    if (!retval.IsVoid()) {
-        return;
-    }
-
-    MakeContextCurrent();
-
-    GLuint shadername = shader->GLName();
-    GLint k = -1;
-    gl->fGetShaderiv(shadername, LOCAL_GL_INFO_LOG_LENGTH, &k);
-    if (k == -1) {
-        // XXX GL Error? should never happen.
-        return;
-    }
-
-    if (k == 0) {
-        retval.Truncate();
-        return;
-    }
-
-    retval.SetCapacity(k);
-    gl->fGetShaderInfoLog(shadername, k, &k, (char*) retval.BeginWriting());
-    retval.SetLength(k);
+    shader->GetShaderInfoLog(&retval);
+
+    retval.SetIsVoid(false);
 }
 
 already_AddRefed<WebGLShaderPrecisionFormat>
 WebGLContext::GetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype)
 {
     if (IsContextLost())
         return nullptr;
 
@@ -3843,61 +2996,51 @@ WebGLContext::GetShaderPrecisionFormat(G
     nsRefPtr<WebGLShaderPrecisionFormat> retShaderPrecisionFormat
         = new WebGLShaderPrecisionFormat(this, range[0], range[1], precision);
     return retShaderPrecisionFormat.forget();
 }
 
 void
 WebGLContext::GetShaderSource(WebGLShader* shader, nsAString& retval)
 {
-    if (IsContextLost()) {
-        retval.SetIsVoid(true);
+    retval.SetIsVoid(true);
+
+    if (IsContextLost())
         return;
-    }
 
     if (!ValidateObject("getShaderSource: shader", shader))
         return;
 
-    retval.Assign(shader->Source());
+    shader->GetShaderSource(&retval);
 }
 
 void
 WebGLContext::ShaderSource(WebGLShader* shader, const nsAString& source)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateObject("shaderSource: shader", shader))
         return;
 
-    // We're storing an actual instance of StripComments because, if we don't, the
-    // cleanSource nsAString instance will be destroyed before the reference is
-    // actually used.
-    StripComments stripComments(source);
-    const nsAString& cleanSource = Substring(stripComments.result().Elements(), stripComments.length());
-    if (!ValidateGLSLString(cleanSource, "compileShader"))
-        return;
-
-    shader->SetSource(source);
-
-    shader->SetNeedsTranslation();
+    shader->ShaderSource(source);
 }
 
 void
 WebGLContext::GetShaderTranslatedSource(WebGLShader* shader, nsAString& retval)
 {
-    if (IsContextLost()) {
-        retval.SetIsVoid(true);
+    retval.SetIsVoid(true);
+
+    if (IsContextLost())
         return;
-    }
 
     if (!ValidateObject("getShaderTranslatedSource: shader", shader))
         return;
 
-    retval.Assign(shader->TranslatedSource());
+    shader->GetShaderTranslatedSource(&retval);
 }
 
 GLenum WebGLContext::CheckedTexImage2D(TexImageTarget texImageTarget,
                                        GLint level,
                                        TexInternalFormat internalformat,
                                        GLsizei width,
                                        GLsizei height,
                                        GLint border,
@@ -4365,87 +3508,16 @@ WebGLContext::RestoreContext()
     // restoreContext().
 
     if (!mAllowContextRestore)
         return ErrorInvalidOperation("restoreContext: Context cannot be restored.");
 
     ForceRestoreContext();
 }
 
-bool
-BaseTypeAndSizeFromUniformType(GLenum uType, GLenum* baseType, GLint* unitSize)
-{
-    switch (uType) {
-        case LOCAL_GL_INT:
-        case LOCAL_GL_INT_VEC2:
-        case LOCAL_GL_INT_VEC3:
-        case LOCAL_GL_INT_VEC4:
-        case LOCAL_GL_SAMPLER_2D:
-        case LOCAL_GL_SAMPLER_CUBE:
-            *baseType = LOCAL_GL_INT;
-            break;
-        case LOCAL_GL_FLOAT:
-        case LOCAL_GL_FLOAT_VEC2:
-        case LOCAL_GL_FLOAT_VEC3:
-        case LOCAL_GL_FLOAT_VEC4:
-        case LOCAL_GL_FLOAT_MAT2:
-        case LOCAL_GL_FLOAT_MAT3:
-        case LOCAL_GL_FLOAT_MAT4:
-            *baseType = LOCAL_GL_FLOAT;
-            break;
-        case LOCAL_GL_BOOL:
-        case LOCAL_GL_BOOL_VEC2:
-        case LOCAL_GL_BOOL_VEC3:
-        case LOCAL_GL_BOOL_VEC4:
-            *baseType = LOCAL_GL_BOOL; // pretend these are int
-            break;
-        default:
-            return false;
-    }
-
-    switch (uType) {
-        case LOCAL_GL_INT:
-        case LOCAL_GL_FLOAT:
-        case LOCAL_GL_BOOL:
-        case LOCAL_GL_SAMPLER_2D:
-        case LOCAL_GL_SAMPLER_CUBE:
-            *unitSize = 1;
-            break;
-        case LOCAL_GL_INT_VEC2:
-        case LOCAL_GL_FLOAT_VEC2:
-        case LOCAL_GL_BOOL_VEC2:
-            *unitSize = 2;
-            break;
-        case LOCAL_GL_INT_VEC3:
-        case LOCAL_GL_FLOAT_VEC3:
-        case LOCAL_GL_BOOL_VEC3:
-            *unitSize = 3;
-            break;
-        case LOCAL_GL_INT_VEC4:
-        case LOCAL_GL_FLOAT_VEC4:
-        case LOCAL_GL_BOOL_VEC4:
-            *unitSize = 4;
-            break;
-        case LOCAL_GL_FLOAT_MAT2:
-            *unitSize = 4;
-            break;
-        case LOCAL_GL_FLOAT_MAT3:
-            *unitSize = 9;
-            break;
-        case LOCAL_GL_FLOAT_MAT4:
-            *unitSize = 16;
-            break;
-        default:
-            return false;
-    }
-
-    return true;
-}
-
-
 WebGLTexelFormat
 mozilla::GetWebGLTexelFormat(TexInternalFormat effectiveInternalFormat)
 {
     switch (effectiveInternalFormat.get()) {
         case LOCAL_GL_RGBA8:                  return WebGLTexelFormat::RGBA8;
         case LOCAL_GL_SRGB8_ALPHA8:           return WebGLTexelFormat::RGBA8;
         case LOCAL_GL_RGB8:                   return WebGLTexelFormat::RGB8;
         case LOCAL_GL_SRGB8:                  return WebGLTexelFormat::RGB8;
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -1038,17 +1038,17 @@ WebGLContext::AssertCachedBindings()
         AssertUintParamCorrect(gl, LOCAL_GL_READ_FRAMEBUFFER_BINDING, bound);
     } else {
         MOZ_ASSERT(mBoundDrawFramebuffer == mBoundReadFramebuffer);
         GLuint bound = mBoundDrawFramebuffer ? mBoundDrawFramebuffer->GLName()
                                              : 0;
         AssertUintParamCorrect(gl, LOCAL_GL_FRAMEBUFFER_BINDING, bound);
     }
 
-    GLuint bound = mCurrentProgram ? mCurrentProgram->GLName() : 0;
+    GLuint bound = mCurrentProgram ? mCurrentProgram->mGLName : 0;
     AssertUintParamCorrect(gl, LOCAL_GL_CURRENT_PROGRAM, bound);
 
     // Textures
     GLenum activeTexture = mActiveTexture + LOCAL_GL_TEXTURE0;
     AssertUintParamCorrect(gl, LOCAL_GL_ACTIVE_TEXTURE, activeTexture);
 
     WebGLTexture* curTex = ActiveBoundTextureForTarget(LOCAL_GL_TEXTURE_2D);
     bound = curTex ? curTex->GLName() : 0;
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -17,16 +17,17 @@
 #include "WebGLBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShader.h"
 #include "WebGLTexture.h"
 #include "WebGLUniformLocation.h"
+#include "WebGLValidateStrings.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 #if defined(MOZ_WIDGET_COCOA)
 #include "nsCocoaFeatures.h"
 #endif
 
 namespace mozilla {
@@ -362,60 +363,16 @@ WebGLContext::ValidateDrawModeEnum(GLenu
         return true;
 
     default:
         ErrorInvalidEnumInfo(info, mode);
         return false;
     }
 }
 
-bool
-WebGLContext::ValidateGLSLVariableName(const nsAString& name, const char* info)
-{
-    if (name.IsEmpty())
-        return false;
-
-    const uint32_t maxSize = 256;
-    if (name.Length() > maxSize) {
-        ErrorInvalidValue("%s: Identifier is %d characters long, exceeds the"
-                          " maximum allowed length of %d characters.", info,
-                          name.Length(), maxSize);
-        return false;
-    }
-
-    if (!ValidateGLSLString(name, info))
-        return false;
-
-    nsString prefix1 = NS_LITERAL_STRING("webgl_");
-    nsString prefix2 = NS_LITERAL_STRING("_webgl_");
-
-    if (Substring(name, 0, prefix1.Length()).Equals(prefix1) ||
-        Substring(name, 0, prefix2.Length()).Equals(prefix2))
-    {
-        ErrorInvalidOperation("%s: String contains a reserved GLSL prefix.",
-                              info);
-        return false;
-    }
-
-    return true;
-}
-
-bool WebGLContext::ValidateGLSLString(const nsAString& string, const char* info)
-{
-    for (uint32_t i = 0; i < string.Length(); ++i) {
-        if (!ValidateGLSLCharacter(string.CharAt(i))) {
-             ErrorInvalidValue("%s: String contains the illegal character"
-                               " '%d'.", info, string.CharAt(i));
-             return false;
-        }
-    }
-
-    return true;
-}
-
 /**
  * Return true if the framebuffer attachment is valid. Attachment must
  * be one of depth/stencil/depth_stencil/color attachment.
  */
 bool
 WebGLContext::ValidateFramebufferAttachment(const WebGLFramebuffer* fb, GLenum attachment,
                                             const char* funcName)
 {
@@ -1507,261 +1464,129 @@ WebGLContext::ValidateTexImage(TexImageT
         return false;
     }
 
     // Parameters are OK
     return true;
 }
 
 bool
-WebGLContext::ValidateUniformLocation(const char* info,
-                                      WebGLUniformLocation* loc)
+WebGLContext::ValidateUniformLocation(WebGLUniformLocation* loc, const char* funcName)
 {
-    if (!ValidateObjectAllowNull(info, loc))
-        return false;
-
+    /* GLES 2.0.25, p38:
+     *   If the value of location is -1, the Uniform* commands will silently
+     *   ignore the data passed in, and the current uniform values will not be
+     *   changed.
+     */
     if (!loc)
         return false;
 
-    // The need to check specifically for !mCurrentProgram here is explained in
-    // bug 657556.
-    if (!mCurrentProgram) {
-        ErrorInvalidOperation("%s: No program is currently bound.", info);
+    if (!ValidateObject(funcName, loc))
         return false;
-    }
 
-    if (mCurrentProgram != loc->Program()) {
-        ErrorInvalidOperation("%s: This uniform location doesn't correspond to"
-                              " the current program.", info);
+    if (!mCurrentProgram) {
+        ErrorInvalidOperation("%s: No program is currently bound.", funcName);
         return false;
     }
 
-    if (mCurrentProgram->Generation() != loc->ProgramGeneration()) {
-        ErrorInvalidOperation("%s: This uniform location is obsolete since the"
-                              " program has been relinked.", info);
-        return false;
-    }
-
-    return true;
+    return loc->ValidateForProgram(mCurrentProgram, this, funcName);
 }
 
 bool
-WebGLContext::ValidateSamplerUniformSetter(const char* info,
-                                           WebGLUniformLocation* loc,
-                                           GLint value)
-{
-    if (loc->Info().type != LOCAL_GL_SAMPLER_2D &&
-        loc->Info().type != LOCAL_GL_SAMPLER_CUBE)
-    {
-        return true;
-    }
-
-    if (value >= 0 && value < mGLMaxTextureUnits)
-        return true;
-
-    ErrorInvalidValue("%s: This uniform location is a sampler, but %d is not a"
-                      " valid texture unit.", info, value);
-    return false;
-}
-
-bool
-WebGLContext::ValidateAttribArraySetter(const char* name, uint32_t cnt,
+WebGLContext::ValidateAttribArraySetter(const char* name, uint32_t setterElemSize,
                                         uint32_t arrayLength)
 {
     if (IsContextLost())
         return false;
 
-    if (arrayLength < cnt) {
-        ErrorInvalidOperation("%s: Array must be >= %d elements.", name, cnt);
-        return false;
-    }
-
-    return true;
-}
-
-static bool
-IsUniformSetterTypeValid(GLenum setterType, GLenum uniformType)
-{
-    switch (uniformType) {
-    case LOCAL_GL_BOOL:
-    case LOCAL_GL_BOOL_VEC2:
-    case LOCAL_GL_BOOL_VEC3:
-    case LOCAL_GL_BOOL_VEC4:
-        return true; // GLfloat(0.0) sets a bool to false.
-
-    case LOCAL_GL_INT:
-    case LOCAL_GL_INT_SAMPLER_2D:
-    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
-    case LOCAL_GL_INT_SAMPLER_3D:
-    case LOCAL_GL_INT_SAMPLER_CUBE:
-    case LOCAL_GL_INT_VEC2:
-    case LOCAL_GL_INT_VEC3:
-    case LOCAL_GL_INT_VEC4:
-    case LOCAL_GL_SAMPLER_2D:
-    case LOCAL_GL_SAMPLER_2D_ARRAY:
-    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
-    case LOCAL_GL_SAMPLER_2D_SHADOW:
-    case LOCAL_GL_SAMPLER_CUBE:
-    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
-        return setterType == LOCAL_GL_INT;
-
-    case LOCAL_GL_FLOAT:
-    case LOCAL_GL_FLOAT_MAT2:
-    case LOCAL_GL_FLOAT_MAT2x3:
-    case LOCAL_GL_FLOAT_MAT2x4:
-    case LOCAL_GL_FLOAT_MAT3:
-    case LOCAL_GL_FLOAT_MAT3x2:
-    case LOCAL_GL_FLOAT_MAT3x4:
-    case LOCAL_GL_FLOAT_MAT4:
-    case LOCAL_GL_FLOAT_MAT4x2:
-    case LOCAL_GL_FLOAT_MAT4x3:
-    case LOCAL_GL_FLOAT_VEC2:
-    case LOCAL_GL_FLOAT_VEC3:
-    case LOCAL_GL_FLOAT_VEC4:
-        return setterType == LOCAL_GL_FLOAT;
-
-    default:
-        MOZ_ASSERT(false); // should never get here
-        return false;
-    }
-}
-
-static bool
-CheckUniformSizeAndType(WebGLContext& webgl, WebGLUniformLocation* loc,
-                        uint8_t setterElemSize, GLenum setterType,
-                        const char* info)
-{
-    if (setterElemSize != loc->ElementSize()) {
-        webgl.ErrorInvalidOperation("%s: Bad uniform size: %i", info,
-                                    loc->ElementSize());
-        return false;
-    }
-
-    if (!IsUniformSetterTypeValid(setterType, loc->Info().type)) {
-        webgl.ErrorInvalidOperation("%s: Bad uniform type: %i", info,
-                                    loc->Info().type);
-        return false;
-    }
-
-    return true;
-}
-
-static bool
-CheckUniformArrayLength(WebGLContext& webgl, WebGLUniformLocation* loc,
-                        uint8_t setterElemSize, size_t setterArraySize,
-                        const char* info)
-{
-    if (setterArraySize == 0 ||
-        setterArraySize % setterElemSize)
-    {
-        webgl.ErrorInvalidValue("%s: expected an array of length a multiple of"
-                                " %d, got an array of length %d.", info,
-                                setterElemSize, setterArraySize);
-        return false;
-    }
-
-    if (!loc->Info().isArray &&
-        setterArraySize != setterElemSize)
-    {
-        webgl.ErrorInvalidOperation("%s: expected an array of length exactly %d"
-                                    " (since this uniform is not an array"
-                                    " uniform), got an array of length %d.",
-                                    info, setterElemSize, setterArraySize);
+    if (arrayLength < setterElemSize) {
+        ErrorInvalidOperation("%s: Array must have >= %d elements.", name,
+                              setterElemSize);
         return false;
     }
 
     return true;
 }
 
 bool
 WebGLContext::ValidateUniformSetter(WebGLUniformLocation* loc,
                                     uint8_t setterElemSize, GLenum setterType,
-                                    const char* info, GLuint* out_rawLoc)
+                                    const char* funcName, GLuint* out_rawLoc)
 {
     if (IsContextLost())
         return false;
 
-    if (!ValidateUniformLocation(info, loc))
+    if (!ValidateUniformLocation(loc, funcName))
         return false;
 
-    if (!CheckUniformSizeAndType(*this, loc, setterElemSize, setterType, info))
+    if (!loc->ValidateSizeAndType(setterElemSize, setterType, this, funcName))
         return false;
 
-    *out_rawLoc = loc->Location();
+    *out_rawLoc = loc->mLoc;
     return true;
 }
 
 bool
 WebGLContext::ValidateUniformArraySetter(WebGLUniformLocation* loc,
                                          uint8_t setterElemSize,
                                          GLenum setterType,
                                          size_t setterArraySize,
-                                         const char* info,
+                                         const char* funcName,
                                          GLuint* const out_rawLoc,
                                          GLsizei* const out_numElementsToUpload)
 {
     if (IsContextLost())
         return false;
 
-    if (!ValidateUniformLocation(info, loc))
+    if (!ValidateUniformLocation(loc, funcName))
         return false;
 
-    if (!CheckUniformSizeAndType(*this, loc, setterElemSize, setterType, info))
+    if (!loc->ValidateSizeAndType(setterElemSize, setterType, this, funcName))
         return false;
 
-    if (!CheckUniformArrayLength(*this, loc, setterElemSize, setterArraySize,
-                                 info))
-    {
+    if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, this, funcName))
         return false;
-    }
 
-    *out_rawLoc = loc->Location();
-    *out_numElementsToUpload = std::min((size_t)loc->Info().arraySize,
+    *out_rawLoc = loc->mLoc;
+    *out_numElementsToUpload = std::min((size_t)loc->mActiveInfo->mElemCount,
                                         setterArraySize / setterElemSize);
     return true;
 }
 
 bool
 WebGLContext::ValidateUniformMatrixArraySetter(WebGLUniformLocation* loc,
                                                uint8_t setterDims,
                                                GLenum setterType,
                                                size_t setterArraySize,
                                                bool setterTranspose,
-                                               const char* info,
+                                               const char* funcName,
                                                GLuint* const out_rawLoc,
                                                GLsizei* const out_numElementsToUpload)
 {
     uint8_t setterElemSize = setterDims * setterDims;
 
     if (IsContextLost())
         return false;
 
-    if (!ValidateUniformLocation(info, loc))
+    if (!ValidateUniformLocation(loc, funcName))
+        return false;
+
+    if (!loc->ValidateSizeAndType(setterElemSize, setterType, this, funcName))
         return false;
 
-    if (!CheckUniformSizeAndType(*this, loc, setterElemSize, setterType, info))
+    if (!loc->ValidateArrayLength(setterElemSize, setterArraySize, this, funcName))
         return false;
 
-    if (!CheckUniformArrayLength(*this, loc, setterElemSize, setterArraySize,
-                                 info))
-    {
+    if (setterTranspose) {
+        ErrorInvalidValue("%s: `transpose` must be false.", funcName);
         return false;
     }
 
-    if (setterTranspose) {
-        ErrorInvalidValue("%s: `transpose` must be false.", info);
-        return false;
-    }
-
-    *out_rawLoc = loc->Location();
-    *out_numElementsToUpload = std::min((size_t)loc->Info().arraySize,
+    *out_rawLoc = loc->mLoc;
+    *out_numElementsToUpload = std::min((size_t)loc->mActiveInfo->mElemCount,
                                         setterArraySize / setterElemSize);
     return true;
 }
 
 bool
 WebGLContext::ValidateAttribIndex(GLuint index, const char* info)
 {
     bool valid = (index < MaxVertexAttribs());
@@ -2090,25 +1915,23 @@ WebGLContext::InitAndValidateGL()
         gl->fPointParameterf(LOCAL_GL_POINT_SPRITE_COORD_ORIGIN,
                              LOCAL_GL_LOWER_LEFT);
     }
 #endif
 
     // Check the shader validator pref
     NS_ENSURE_TRUE(Preferences::GetRootBranch(), false);
 
-    mShaderValidation = Preferences::GetBool("webgl.shader_validator",
-                                             mShaderValidation);
+    mBypassShaderValidation = Preferences::GetBool("webgl.bypass-shader-validation",
+                                                   mBypassShaderValidation);
 
     // initialize shader translator
-    if (mShaderValidation) {
-        if (!ShInitialize()) {
-            GenerateWarning("GLSL translator initialization failed!");
-            return false;
-        }
+    if (!ShInitialize()) {
+        GenerateWarning("GLSL translator initialization failed!");
+        return false;
     }
 
     // Mesa can only be detected with the GL_VERSION string, of the form
     // "2.1 Mesa 7.11.0"
     const char* versionStr = (const char*)(gl->fGetString(LOCAL_GL_VERSION));
     mIsMesa = strstr(versionStr, "Mesa");
 
     // Notice that the point of calling fGetError here is not only to check for
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -8,17 +8,16 @@
 #include "GLContext.h"
 #include "mozilla/CheckedInt.h"
 #include "WebGLBuffer.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShader.h"
 #include "WebGLTexture.h"
-#include "WebGLUniformInfo.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 using namespace mozilla;
 using namespace dom;
 
 void
 WebGLContext::VertexAttrib1f(GLuint index, GLfloat x0)
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -2,371 +2,698 @@
 /* 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 "WebGLProgram.h"
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
-#include "MurmurHash3.h"
 #include "WebGLContext.h"
 #include "WebGLShader.h"
+#include "WebGLUniformLocation.h"
+#include "WebGLValidateStrings.h"
 
 namespace mozilla {
 
-/** Takes an ASCII string like "foo[i]", turns it into "foo" and returns "[i]"
-  * in bracketPart.
-  *
-  * \param string input/output: The string to split, becomes the string without
-  *                             the bracket part.
-  * \param bracketPart output: Gets the bracket part.
-  *
-  * Notice that if there are multiple brackets like "foo[i].bar[j]", only the
-  * last bracket is split.
-  */
+/* If `name`: "foo[3]"
+ * Then returns true, with
+ *     `out_baseName`: "foo"
+ *     `out_isArray`: true
+ *     `out_index`: 3
+ *
+ * If `name`: "foo"
+ * Then returns true, with
+ *     `out_baseName`: "foo"
+ *     `out_isArray`: false
+ *     `out_index`: <not written>
+ */
 static bool
-SplitLastSquareBracket(nsACString& string, nsCString& bracketPart)
+ParseName(const nsCString& name, nsCString* const out_baseName,
+          bool* const out_isArray, size_t* const out_arrayIndex)
 {
-    MOZ_ASSERT(bracketPart.IsEmpty(),
-               "SplitLastSquareBracket must be called with empty bracketPart"
-               " string.");
+    int32_t indexEnd = name.RFind("]");
+    if (indexEnd == -1 ||
+        (uint32_t)indexEnd != name.Length() - 1)
+    {
+        *out_baseName = name;
+        *out_isArray = false;
+        return true;
+    }
 
-    if (string.IsEmpty())
+    int32_t indexOpenBracket = name.RFind("[");
+    if (indexOpenBracket == -1)
         return false;
 
-    char* string_start = string.BeginWriting();
-    char* s = string_start + string.Length() - 1;
-
-    if (*s != ']')
+    uint32_t indexStart = indexOpenBracket + 1;
+    uint32_t indexLen = indexEnd - indexStart;
+    if (indexLen == 0)
         return false;
 
-    while (*s != '[' && s != string_start)
-        s--;
+    const nsAutoCString indexStr(Substring(name, indexStart, indexLen));
 
-    if (*s != '[')
+    nsresult errorcode;
+    int32_t indexNum = indexStr.ToInteger(&errorcode);
+    if (NS_FAILED(errorcode))
         return false;
 
-    bracketPart.Assign(s);
-    *s = 0;
-    string.EndWriting();
-    string.SetLength(s - string_start);
+    if (indexNum < 0)
+        return false;
+
+    *out_baseName = StringHead(name, indexOpenBracket);
+    *out_isArray = true;
+    *out_arrayIndex = indexNum;
     return true;
 }
 
-JSObject*
-WebGLProgram::WrapObject(JSContext* cx) {
-    return dom::WebGLProgramBinding::Wrap(cx, this);
+static void
+AddActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType, bool isArray,
+              const nsACString& baseUserName, const nsACString& baseMappedName,
+              std::vector<nsRefPtr<WebGLActiveInfo>>* activeInfoList,
+              std::map<nsCString, const WebGLActiveInfo*>* infoLocMap)
+{
+    nsRefPtr<WebGLActiveInfo> info = new WebGLActiveInfo(webgl, elemCount, elemType,
+                                                         isArray, baseUserName,
+                                                         baseMappedName);
+    activeInfoList->push_back(info);
+
+    infoLocMap->insert(std::make_pair(info->mBaseUserName, info.get()));
+}
+
+//#define DUMP_SHADERVAR_MAPPINGS
+
+static TemporaryRef<const webgl::LinkedProgramInfo>
+QueryProgramInfo(WebGLProgram* prog, gl::GLContext* gl)
+{
+    RefPtr<webgl::LinkedProgramInfo> info(new webgl::LinkedProgramInfo(prog));
+
+    GLuint maxAttribLenWithNull = 0;
+    gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
+                      (GLint*)&maxAttribLenWithNull);
+    if (maxAttribLenWithNull < 1)
+        maxAttribLenWithNull = 1;
+
+    GLuint maxUniformLenWithNull = 0;
+    gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH,
+                      (GLint*)&maxUniformLenWithNull);
+    if (maxUniformLenWithNull < 1)
+        maxUniformLenWithNull = 1;
+
+#ifdef DUMP_SHADERVAR_MAPPINGS
+    printf_stderr("maxAttribLenWithNull: %d\n", maxAttribLenWithNull);
+    printf_stderr("maxUniformLenWithNull: %d\n", maxUniformLenWithNull);
+#endif
+
+    // Attribs
+
+    GLuint numActiveAttribs = 0;
+    gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES,
+                      (GLint*)&numActiveAttribs);
+
+    for (GLuint i = 0; i < numActiveAttribs; i++) {
+        nsAutoCString mappedName;
+        mappedName.SetLength(maxAttribLenWithNull - 1);
+
+        GLsizei lengthWithoutNull = 0;
+        GLint elemCount = 0; // `size`
+        GLenum elemType = 0; // `type`
+        gl->fGetActiveAttrib(prog->mGLName, i, mappedName.Length()+1, &lengthWithoutNull,
+                             &elemCount, &elemType, mappedName.BeginWriting());
+
+        mappedName.SetLength(lengthWithoutNull);
+
+        // Collect ActiveInfos:
+
+        // Attribs can't be arrays, so we can skip some of the mess we have in the Uniform
+        // path.
+        nsDependentCString userName;
+        if (!prog->FindAttribUserNameByMappedName(mappedName, &userName))
+            userName.Rebind(mappedName, 0);
+
+#ifdef DUMP_SHADERVAR_MAPPINGS
+        printf_stderr("[attrib %i] %s/%s\n", i, mappedName.BeginReading(),
+                      userName.BeginReading());
+        printf_stderr("    lengthWithoutNull: %d\n", lengthWithoutNull);
+#endif
+
+        const bool isArray = false;
+        AddActiveInfo(prog->Context(), elemCount, elemType, isArray, userName, mappedName,
+                      &info->activeAttribs, &info->attribMap);
+
+        // Collect active locations:
+        GLint loc = gl->fGetAttribLocation(prog->mGLName, mappedName.BeginReading());
+        if (loc == -1)
+            MOZ_CRASH("Active attrib has no location.");
+
+        info->activeAttribLocs.insert(loc);
+    }
+
+    // Uniforms
+
+    const bool needsCheckForArrays = true;
+
+    GLuint numActiveUniforms = 0;
+    gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORMS,
+                      (GLint*)&numActiveUniforms);
+
+    for (GLuint i = 0; i < numActiveUniforms; i++) {
+        nsAutoCString mappedName;
+        mappedName.SetLength(maxUniformLenWithNull - 1);
+
+        GLsizei lengthWithoutNull = 0;
+        GLint elemCount = 0; // `size`
+        GLenum elemType = 0; // `type`
+        gl->fGetActiveUniform(prog->mGLName, i, mappedName.Length()+1, &lengthWithoutNull,
+                              &elemCount, &elemType, mappedName.BeginWriting());
+
+        mappedName.SetLength(lengthWithoutNull);
+
+        nsAutoCString baseMappedName;
+        bool isArray;
+        size_t arrayIndex;
+        if (!ParseName(mappedName, &baseMappedName, &isArray, &arrayIndex))
+            MOZ_CRASH("Failed to parse `mappedName` received from driver.");
+
+        // Note that for good drivers, `isArray` should already be correct.
+        // However, if FindUniform succeeds, it will be validator-guaranteed correct.
+
+        nsAutoCString baseUserName;
+        if (!prog->FindUniformByMappedName(baseMappedName, &baseUserName, &isArray)) {
+            baseUserName = baseMappedName;
+
+            if (needsCheckForArrays && !isArray) {
+                // By GLES 3, GetUniformLocation("foo[0]") should return -1 if `foo` is
+                // not an array. Our current linux Try slaves return the location of `foo`
+                // anyways, though.
+                std::string mappedName = baseMappedName.BeginReading();
+                mappedName += "[0]";
+
+                GLint loc = gl->fGetUniformLocation(prog->mGLName, mappedName.c_str());
+                if (loc != -1)
+                    isArray = true;
+            }
+        }
+
+#ifdef DUMP_SHADERVAR_MAPPINGS
+        printf_stderr("[uniform %i] %s/%i/%s/%s\n", i, mappedName.BeginReading(),
+                      (int)isArray, baseMappedName.BeginReading(),
+                      baseUserName.BeginReading());
+        printf_stderr("    lengthWithoutNull: %d\n", lengthWithoutNull);
+        printf_stderr("    isArray: %d\n", (int)isArray);
+#endif
+
+        AddActiveInfo(prog->Context(), elemCount, elemType, isArray, baseUserName,
+                      baseMappedName, &info->activeUniforms, &info->uniformMap);
+    }
+
+    return info.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* aProg)
+    : prog(aProg)
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+// WebGLProgram
+
+static GLuint
+CreateProgram(gl::GLContext* gl)
+{
+    gl->MakeCurrent();
+    return gl->fCreateProgram();
 }
 
 WebGLProgram::WebGLProgram(WebGLContext* webgl)
     : WebGLContextBoundObject(webgl)
-    , mLinkStatus(false)
-    , mGeneration(0)
-    , mIdentifierMap(new CStringMap)
-    , mIdentifierReverseMap(new CStringMap)
-    , mUniformInfoMap(new CStringToUniformInfoMap)
-    , mAttribMaxNameLength(0)
+    , mGLName(CreateProgram(webgl->GL()))
 {
-    mContext->MakeContextCurrent();
-    mGLName = mContext->gl->fCreateProgram();
     mContext->mPrograms.insertBack(this);
 }
 
 void
 WebGLProgram::Delete()
 {
-    DetachShaders();
+    gl::GLContext* gl = mContext->GL();
+
+    gl->MakeCurrent();
+    gl->fDeleteProgram(mGLName);
+
+    mVertShader = nullptr;
+    mFragShader = nullptr;
+
+    mMostRecentLinkInfo = nullptr;
+
+    LinkedListElement<WebGLProgram>::removeFrom(mContext->mPrograms);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GL funcs
+
+void
+WebGLProgram::AttachShader(WebGLShader* shader)
+{
+    WebGLRefPtr<WebGLShader>* shaderSlot;
+    switch (shader->mType) {
+    case LOCAL_GL_VERTEX_SHADER:
+        shaderSlot = &mVertShader;
+        break;
+    case LOCAL_GL_FRAGMENT_SHADER:
+        shaderSlot = &mFragShader;
+        break;
+    default:
+        mContext->ErrorInvalidOperation("attachShader: Bad type for shader.");
+        return;
+    }
+
+    if (*shaderSlot) {
+        if (shader == *shaderSlot) {
+            mContext->ErrorInvalidOperation("attachShader: `shader` is already attached.");
+        } else {
+            mContext->ErrorInvalidOperation("attachShader: Only one of each type of"
+                                            " shader may be attached to a program.");
+        }
+        return;
+    }
+
+    *shaderSlot = shader;
+
+    mContext->MakeContextCurrent();
+    mContext->gl->fAttachShader(mGLName, shader->mGLName);
+}
+
+void
+WebGLProgram::BindAttribLocation(GLuint loc, const nsAString& name)
+{
+    if (!ValidateGLSLVariableName(name, mContext, "bindAttribLocation"))
+        return;
+
+    if (loc >= mContext->MaxVertexAttribs()) {
+        mContext->ErrorInvalidValue("bindAttribLocation: `location` must be less than"
+                                    " MAX_VERTEX_ATTRIBS.");
+        return;
+    }
+
+    if (StringBeginsWith(name, NS_LITERAL_STRING("gl_"))) {
+        mContext->ErrorInvalidOperation("bindAttribLocation: Can't set the  location of a"
+                                        " name that starts with 'gl_'.");
+        return;
+    }
+
+    NS_LossyConvertUTF16toASCII asciiName(name);
+
+    auto res = mBoundAttribLocs.insert(std::pair<nsCString, GLuint>(asciiName, loc));
+
+    const bool wasInserted = res.second;
+    if (!wasInserted) {
+        auto itr = res.first;
+        itr->second = loc;
+    }
+}
+
+void
+WebGLProgram::DetachShader(WebGLShader* shader)
+{
+    MOZ_ASSERT(shader);
+
+    WebGLRefPtr<WebGLShader>* shaderSlot;
+    switch (shader->mType) {
+    case LOCAL_GL_VERTEX_SHADER:
+        shaderSlot = &mVertShader;
+        break;
+    case LOCAL_GL_FRAGMENT_SHADER:
+        shaderSlot = &mFragShader;
+        break;
+    default:
+        mContext->ErrorInvalidOperation("attachShader: Bad type for shader.");
+        return;
+    }
+
+    if (*shaderSlot != shader) {
+        mContext->ErrorInvalidOperation("detachShader: `shader` is not attached.");
+        return;
+    }
+
+    *shaderSlot = nullptr;
+
     mContext->MakeContextCurrent();
-    mContext->gl->fDeleteProgram(mGLName);
-    LinkedListElement<WebGLProgram>::removeFrom(mContext->mPrograms);
+    mContext->gl->fDetachShader(mGLName, shader->mGLName);
+}
+
+already_AddRefed<WebGLActiveInfo>
+WebGLProgram::GetActiveAttrib(GLuint index) const
+{
+    if (!mMostRecentLinkInfo) {
+        nsRefPtr<WebGLActiveInfo> ret = WebGLActiveInfo::CreateInvalid(mContext);
+        return ret.forget();
+    }
+
+    const auto& activeList = mMostRecentLinkInfo->activeAttribs;
+
+    if (index >= activeList.size()) {
+        mContext->ErrorInvalidValue("`index` (%i) must be less than %s (%i).",
+                                    index, "ACTIVE_ATTRIBS", activeList.size());
+        return nullptr;
+    }
+
+    nsRefPtr<WebGLActiveInfo> ret =  activeList[index];
+    return ret.forget();
+}
+
+already_AddRefed<WebGLActiveInfo>
+WebGLProgram::GetActiveUniform(GLuint index) const
+{
+    if (!mMostRecentLinkInfo) {
+        nsRefPtr<WebGLActiveInfo> ret = WebGLActiveInfo::CreateInvalid(mContext);
+        return ret.forget();
+    }
+
+    const auto& activeList = mMostRecentLinkInfo->activeUniforms;
+
+    if (index >= activeList.size()) {
+        mContext->ErrorInvalidValue("`index` (%i) must be less than %s (%i).",
+                                    index, "ACTIVE_UNIFORMS", activeList.size());
+        return nullptr;
+    }
+
+    nsRefPtr<WebGLActiveInfo> ret = activeList[index];
+    return ret.forget();
+}
+
+void
+WebGLProgram::GetAttachedShaders(nsTArray<nsRefPtr<WebGLShader>>* const out) const
+{
+    out->TruncateLength(0);
+
+    if (mVertShader)
+        out->AppendElement(mVertShader);
+
+    if (mFragShader)
+        out->AppendElement(mFragShader);
+}
+
+GLint
+WebGLProgram::GetAttribLocation(const nsAString& userName_wide) const
+{
+    if (!ValidateGLSLVariableName(userName_wide, mContext, "getAttribLocation"))
+        return -1;
+
+    if (!IsLinked()) {
+        mContext->ErrorInvalidOperation("getAttribLocation: `program` must be linked.");
+        return -1;
+    }
+
+    const NS_LossyConvertUTF16toASCII userName(userName_wide);
+
+    const WebGLActiveInfo* info;
+    if (!LinkInfo()->FindAttrib(userName, &info))
+        return -1;
+
+    const nsCString& mappedName = info->mBaseMappedName;
+
+    gl::GLContext* gl = mContext->GL();
+    gl->MakeCurrent();
+
+    return gl->fGetAttribLocation(mGLName, mappedName.BeginReading());
+}
+
+void
+WebGLProgram::GetProgramInfoLog(nsAString* const out) const
+{
+    CopyASCIItoUTF16(mLinkLog, *out);
+}
+
+static GLint
+GetProgramiv(gl::GLContext* gl, GLuint program, GLenum pname)
+{
+    GLint ret = 0;
+    gl->fGetProgramiv(program, pname, &ret);
+    return ret;
+}
+
+JS::Value
+WebGLProgram::GetProgramParameter(GLenum pname) const
+{
+    gl::GLContext* gl = mContext->gl;
+    gl->MakeCurrent();
+
+    if (mContext->IsWebGL2()) {
+        switch (pname) {
+        case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
+            return JS::Int32Value(GetProgramiv(gl, mGLName, pname));
+        }
+    }
+
+
+    switch (pname) {
+    case LOCAL_GL_ATTACHED_SHADERS:
+    case LOCAL_GL_ACTIVE_UNIFORMS:
+    case LOCAL_GL_ACTIVE_ATTRIBUTES:
+        return JS::Int32Value(GetProgramiv(gl, mGLName, pname));
+
+    case LOCAL_GL_DELETE_STATUS:
+        return JS::BooleanValue(IsDeleteRequested());
+
+    case LOCAL_GL_LINK_STATUS:
+        return JS::BooleanValue(IsLinked());
+
+    case LOCAL_GL_VALIDATE_STATUS:
+#ifdef XP_MACOSX
+        // See comment in ValidateProgram.
+        if (gl->WorkAroundDriverBugs())
+            return JS::BooleanValue(true);
+#endif
+        return JS::BooleanValue(bool(GetProgramiv(gl, mGLName, pname)));
+
+    default:
+        mContext->ErrorInvalidEnumInfo("getProgramParameter: `pname`",
+                                       pname);
+        return JS::NullValue();
+    }
+}
+
+already_AddRefed<WebGLUniformLocation>
+WebGLProgram::GetUniformLocation(const nsAString& userName_wide) const
+{
+    if (!ValidateGLSLVariableName(userName_wide, mContext, "getUniformLocation"))
+        return nullptr;
+
+    if (!IsLinked()) {
+        mContext->ErrorInvalidOperation("getUniformLocation: `program` must be linked.");
+        return nullptr;
+    }
+
+    const NS_LossyConvertUTF16toASCII userName(userName_wide);
+
+    nsDependentCString baseUserName;
+    bool isArray;
+    size_t arrayIndex;
+    if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex))
+        return nullptr;
+
+    const WebGLActiveInfo* activeInfo;
+    if (!LinkInfo()->FindUniform(baseUserName, &activeInfo))
+        return nullptr;
+
+    const nsCString& baseMappedName = activeInfo->mBaseMappedName;
+
+    nsAutoCString mappedName(baseMappedName);
+    if (isArray) {
+        mappedName.AppendLiteral("[");
+        mappedName.AppendInt(uint32_t(arrayIndex));
+        mappedName.AppendLiteral("]");
+    }
+
+    gl::GLContext* gl = mContext->GL();
+    gl->MakeCurrent();
+
+    GLint loc = gl->fGetUniformLocation(mGLName, mappedName.BeginReading());
+    if (loc == -1)
+        return nullptr;
+
+    nsRefPtr<WebGLUniformLocation> locObj = new WebGLUniformLocation(mContext, LinkInfo(),
+                                                                     loc, activeInfo);
+    return locObj.forget();
 }
 
 bool
-WebGLProgram::AttachShader(WebGLShader* shader)
-{
-    if (ContainsShader(shader))
-        return false;
-
-    mAttachedShaders.AppendElement(shader);
-
-    mContext->MakeContextCurrent();
-    mContext->gl->fAttachShader(GLName(), shader->GLName());
-
-    return true;
-}
-
-bool
-WebGLProgram::DetachShader(WebGLShader* shader)
+WebGLProgram::LinkProgram()
 {
-    if (!mAttachedShaders.RemoveElement(shader))
+    mContext->InvalidateBufferFetching(); // we do it early in this function
+    // as some of the validation below changes program state
+
+    mLinkLog.Truncate();
+    mMostRecentLinkInfo = nullptr;
+
+    if (!mVertShader || !mVertShader->IsCompiled()) {
+        mLinkLog.AssignLiteral("Must have a compiled vertex shader attached.");
+        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
         return false;
+    }
 
-    mContext->MakeContextCurrent();
-    mContext->gl->fDetachShader(GLName(), shader->GLName());
+    if (!mFragShader || !mFragShader->IsCompiled()) {
+        mLinkLog.AssignLiteral("Must have an compiled fragment shader attached.");
+        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+        return false;
+    }
+
+    if (!mFragShader->CanLinkTo(mVertShader, &mLinkLog)) {
+        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+        return false;
+    }
+
+    gl::GLContext* gl = mContext->gl;
+    gl->MakeCurrent();
 
-    return true;
-}
+    // Bug 777028: Mesa can't handle more than 16 samplers per program,
+    // counting each array entry.
+    size_t numSamplerUniforms_upperBound = mVertShader->CalcNumSamplerUniforms() +
+                                           mFragShader->CalcNumSamplerUniforms();
+    if (gl->WorkAroundDriverBugs() &&
+        mContext->mIsMesa &&
+        numSamplerUniforms_upperBound > 16)
+    {
+        mLinkLog.AssignLiteral("Programs with more than 16 samplers are disallowed on"
+                               " Mesa drivers to avoid crashing.");
+        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+        return false;
+    }
 
-bool
-WebGLProgram::HasAttachedShaderOfType(GLenum shaderType)
-{
-    for (uint32_t i = 0; i < mAttachedShaders.Length(); ++i) {
-        if (mAttachedShaders[i] && mAttachedShaders[i]->ShaderType() == shaderType)
-            return true;
+    // Bind the attrib locations.
+    // This can't be done trivially, because we have to deal with mapped attrib names.
+    for (auto itr = mBoundAttribLocs.begin(); itr != mBoundAttribLocs.end(); ++itr) {
+        const nsCString& name = itr->first;
+        GLuint index = itr->second;
+
+        mVertShader->BindAttribLocation(mGLName, name, index);
+    }
+
+    if (LinkAndUpdate())
+        return true;
+
+    // Failed link.
+    if (mContext->ShouldGenerateWarnings()) {
+        // report shader/program infoLogs as warnings.
+        // note that shader compilation errors can be deferred to linkProgram,
+        // which is why we can't do anything in compileShader. In practice we could
+        // report in compileShader the translation errors generated by ANGLE,
+        // but it seems saner to keep a single way of obtaining shader infologs.
+        if (!mLinkLog.IsEmpty()) {
+            mContext->GenerateWarning("linkProgram: Failed to link, leaving the following"
+                                      " log:\n%s\n",
+                                      mLinkLog.BeginReading());
+        }
     }
 
     return false;
 }
 
 bool
-WebGLProgram::HasBadShaderAttached()
+WebGLProgram::UseProgram() const
+{
+    if (!mMostRecentLinkInfo) {
+        mContext->ErrorInvalidOperation("useProgram: Program has not been successfully"
+                                        " linked.");
+        return false;
+    }
+
+    mContext->MakeContextCurrent();
+
+    mContext->InvalidateBufferFetching();
+
+    mContext->gl->fUseProgram(mGLName);
+    return true;
+}
+
+void
+WebGLProgram::ValidateProgram() const
 {
-    for (uint32_t i = 0; i < mAttachedShaders.Length(); ++i) {
-        if (mAttachedShaders[i] && !mAttachedShaders[i]->CompileStatus())
-            return true;
+    mContext->MakeContextCurrent();
+    gl::GLContext* gl = mContext->gl;
+
+#ifdef XP_MACOSX
+    // See bug 593867 for NVIDIA and bug 657201 for ATI. The latter is confirmed
+    // with Mac OS 10.6.7.
+    if (gl->WorkAroundDriverBugs()) {
+        mContext->GenerateWarning("validateProgram: Implemented as a no-op on"
+                                  " Mac to work around crashes.");
+        return;
     }
+#endif
+
+    gl->fValidateProgram(mGLName);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool
+WebGLProgram::LinkAndUpdate()
+{
+    mMostRecentLinkInfo = nullptr;
+
+    gl::GLContext* gl = mContext->gl;
+    gl->fLinkProgram(mGLName);
+
+    // Grab the program log.
+    GLuint logLenWithNull = 0;
+    gl->fGetProgramiv(mGLName, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&logLenWithNull);
+    if (logLenWithNull > 1) {
+        mLinkLog.SetLength(logLenWithNull - 1);
+        gl->fGetProgramInfoLog(mGLName, logLenWithNull, nullptr, mLinkLog.BeginWriting());
+    } else {
+        mLinkLog.SetLength(0);
+    }
+
+    GLint ok = 0;
+    gl->fGetProgramiv(mGLName, LOCAL_GL_LINK_STATUS, &ok);
+    if (!ok)
+        return false;
+
+    mMostRecentLinkInfo = QueryProgramInfo(this, gl);
+
+    MOZ_ASSERT(mMostRecentLinkInfo);
+    if (!mMostRecentLinkInfo)
+        mLinkLog.AssignLiteral("Failed to gather program info.");
+
+    return mMostRecentLinkInfo;
+}
+
+bool
+WebGLProgram::FindAttribUserNameByMappedName(const nsACString& mappedName,
+                                             nsDependentCString* const out_userName) const
+{
+    if (mVertShader->FindAttribUserNameByMappedName(mappedName, out_userName))
+        return true;
 
     return false;
 }
 
-size_t
-WebGLProgram::UpperBoundNumSamplerUniforms()
+bool
+WebGLProgram::FindUniformByMappedName(const nsACString& mappedName,
+                                      nsCString* const out_userName,
+                                      bool* const out_isArray) const
 {
-    size_t numSamplerUniforms = 0;
-
-    for (size_t i = 0; i < mAttachedShaders.Length(); ++i) {
-        const WebGLShader* shader = mAttachedShaders[i];
-        if (!shader)
-            continue;
-
-        for (size_t j = 0; j < shader->mUniformInfos.Length(); ++j) {
-            WebGLUniformInfo u = shader->mUniformInfos[j];
-            if (u.type == LOCAL_GL_SAMPLER_2D ||
-                u.type == LOCAL_GL_SAMPLER_CUBE)
-            {
-                numSamplerUniforms += u.arraySize;
-            }
-        }
-    }
-
-    return numSamplerUniforms;
-}
-
-void
-WebGLProgram::MapIdentifier(const nsACString& name,
-                            nsCString* const out_mappedName)
-{
-    MOZ_ASSERT(mIdentifierMap);
-
-    nsCString mutableName(name);
-    nsCString bracketPart;
-    bool hadBracketPart = SplitLastSquareBracket(mutableName, bracketPart);
-    if (hadBracketPart)
-        mutableName.AppendLiteral("[0]");
-
-    if (mIdentifierMap->Get(mutableName, out_mappedName)) {
-        if (hadBracketPart) {
-            nsCString mappedBracketPart;
-            bool mappedHadBracketPart = SplitLastSquareBracket(*out_mappedName,
-                                                               mappedBracketPart);
-            if (mappedHadBracketPart)
-                out_mappedName->Append(bracketPart);
-        }
-        return;
-    }
-
-    // Not found? We might be in the situation we have a uniform array name and
-    // the GL's glGetActiveUniform returned its name without [0], as is allowed
-    // by desktop GL but not in ES. Let's then try with [0].
-    mutableName.AppendLiteral("[0]");
-    if (mIdentifierMap->Get(mutableName, out_mappedName))
-        return;
+    if (mVertShader->FindUniformByMappedName(mappedName, out_userName, out_isArray))
+        return true;
 
-    /* Not found? Return name unchanged. This case happens e.g. on bad user
-     * input, or when we're not using identifier mapping, or if we didn't store
-     * an identifier in the map because e.g. its mapping is trivial. (as happens
-     * for short identifiers)
-     */
-    out_mappedName->Assign(name);
-}
-
-void
-WebGLProgram::ReverseMapIdentifier(const nsACString& name,
-                                   nsCString* const out_reverseMappedName)
-{
-    MOZ_ASSERT(mIdentifierReverseMap);
-
-    nsCString mutableName(name);
-    nsCString bracketPart;
-    bool hadBracketPart = SplitLastSquareBracket(mutableName, bracketPart);
-    if (hadBracketPart)
-        mutableName.AppendLiteral("[0]");
-
-    if (mIdentifierReverseMap->Get(mutableName, out_reverseMappedName)) {
-        if (hadBracketPart) {
-            nsCString reverseMappedBracketPart;
-            bool reverseMappedHadBracketPart = SplitLastSquareBracket(*out_reverseMappedName,
-                                                                      reverseMappedBracketPart);
-            if (reverseMappedHadBracketPart)
-                out_reverseMappedName->Append(bracketPart);
-        }
-        return;
-    }
+    if (mFragShader->FindUniformByMappedName(mappedName, out_userName, out_isArray))
+        return true;
 
-    // Not found? We might be in the situation we have a uniform array name and
-    // the GL's glGetActiveUniform returned its name without [0], as is allowed
-    // by desktop GL but not in ES. Let's then try with [0].
-    mutableName.AppendLiteral("[0]");
-    if (mIdentifierReverseMap->Get(mutableName, out_reverseMappedName))
-        return;
-
-    /* Not found? Return name unchanged. This case happens e.g. on bad user
-     * input, or when we're not using identifier mapping, or if we didn't store
-     * an identifier in the map because e.g. its mapping is trivial. (as happens
-     * for short identifiers)
-     */
-    out_reverseMappedName->Assign(name);
-}
-
-WebGLUniformInfo
-WebGLProgram::GetUniformInfoForMappedIdentifier(const nsACString& name)
-{
-    MOZ_ASSERT(mUniformInfoMap);
-
-    nsCString mutableName(name);
-    nsCString bracketPart;
-    bool hadBracketPart = SplitLastSquareBracket(mutableName, bracketPart);
-    // If there is a bracket, we're either an array or an entry in an array.
-    if (hadBracketPart)
-        mutableName.AppendLiteral("[0]");
-
-    WebGLUniformInfo info;
-    mUniformInfoMap->Get(mutableName, &info);
-    // We don't check if that Get failed, as if it did, it left info with
-    // default values.
-
-    return info;
+    return false;
 }
 
-bool
-WebGLProgram::UpdateInfo()
-{
-    mAttribMaxNameLength = 0;
-    for (size_t i = 0; i < mAttachedShaders.Length(); i++) {
-        mAttribMaxNameLength = std::max(mAttribMaxNameLength,
-                                        mAttachedShaders[i]->mAttribMaxNameLength);
-    }
-
-    GLint attribCount;
-    mContext->gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES, &attribCount);
-
-    if (!mAttribsInUse.SetLength(mContext->mGLMaxVertexAttribs)) {
-        mContext->ErrorOutOfMemory("updateInfo: Out of memory to allocate %d"
-                                   " attribs.", mContext->mGLMaxVertexAttribs);
-        return false;
-    }
-
-    for (size_t i = 0; i < mAttribsInUse.Length(); i++)
-        mAttribsInUse[i] = false;
-
-    nsAutoArrayPtr<char> nameBuf(new char[mAttribMaxNameLength]);
-
-    for (int i = 0; i < attribCount; ++i) {
-        GLint attrnamelen;
-        GLint attrsize;
-        GLenum attrtype;
-        mContext->gl->fGetActiveAttrib(mGLName, i, mAttribMaxNameLength,
-                                       &attrnamelen, &attrsize, &attrtype,
-                                       nameBuf);
-        if (attrnamelen > 0) {
-            GLint loc = mContext->gl->fGetAttribLocation(mGLName, nameBuf);
-            MOZ_ASSERT(loc >= 0, "Major oops in managing the attributes of a"
-                                 " WebGL program.");
-            if (loc < mContext->mGLMaxVertexAttribs) {
-                mAttribsInUse[loc] = true;
-            } else {
-                mContext->GenerateWarning("Program exceeds MAX_VERTEX_ATTRIBS.");
-                return false;
-            }
-        }
-    }
+////////////////////////////////////////////////////////////////////////////////
 
-    // nsAutoPtr will delete old version first
-    mIdentifierMap = new CStringMap;
-    mIdentifierReverseMap = new CStringMap;
-    mUniformInfoMap = new CStringToUniformInfoMap;
-    for (size_t i = 0; i < mAttachedShaders.Length(); i++) {
-        // Loop through ATTRIBUTES
-        for (size_t j = 0; j < mAttachedShaders[i]->mAttributes.Length(); j++) {
-            const WebGLMappedIdentifier& attrib = mAttachedShaders[i]->mAttributes[j];
-
-            // FORWARD MAPPING
-            mIdentifierMap->Put(attrib.original, attrib.mapped);
-            // REVERSE MAPPING
-            mIdentifierReverseMap->Put(attrib.mapped, attrib.original);
-        }
-
-        // Loop through UNIFORMS
-        for (size_t j = 0; j < mAttachedShaders[i]->mUniforms.Length(); j++) {
-            // Add the uniforms name mapping to mIdentifier[Reverse]Map
-            const WebGLMappedIdentifier& uniform = mAttachedShaders[i]->mUniforms[j];
-
-            // FOWARD MAPPING
-            mIdentifierMap->Put(uniform.original, uniform.mapped);
-            // REVERSE MAPPING
-            mIdentifierReverseMap->Put(uniform.mapped, uniform.original);
-
-            // Add uniform info to mUniformInfoMap
-            const WebGLUniformInfo& info = mAttachedShaders[i]->mUniformInfos[j];
-            mUniformInfoMap->Put(uniform.mapped, info);
-        }
-    }
-
-    mActiveAttribMap.clear();
-
-    GLint numActiveAttrs = 0;
-    mContext->gl->fGetProgramiv(mGLName, LOCAL_GL_ACTIVE_ATTRIBUTES, &numActiveAttrs);
-
-    // Spec says the maximum attrib name length is 256 chars, so this is
-    // sufficient to hold any attrib name.
-    char attrName[257];
-
-    GLint dummySize;
-    GLenum dummyType;
-    for (GLint i = 0; i < numActiveAttrs; i++) {
-        mContext->gl->fGetActiveAttrib(mGLName, i, 257, nullptr, &dummySize,
-                                       &dummyType, attrName);
-        GLint attrLoc = mContext->gl->fGetAttribLocation(mGLName, attrName);
-        MOZ_ASSERT(attrLoc >= 0);
-        mActiveAttribMap.insert(std::make_pair(attrLoc, nsCString(attrName)));
-    }
-
-    return true;
+JSObject*
+WebGLProgram::WrapObject(JSContext* js)
+{
+    return dom::WebGLProgramBinding::Wrap(js, this);
 }
 
-/*static*/ uint64_t
-WebGLProgram::IdentifierHashFunction(const char* ident, size_t size)
-{
-    uint64_t outhash[2];
-    // NB: we use the x86 function everywhere, even though it's suboptimal perf
-    // on x64.  They return different results; not sure if that's a requirement.
-    MurmurHash3_x86_128(ident, size, 0, &outhash[0]);
-    return outhash[0];
-}
-
-/*static*/ void
-WebGLProgram::HashMapIdentifier(const nsACString& name,
-                                nsCString* const out_hashedName)
-{
-    uint64_t hash = IdentifierHashFunction(name.BeginReading(), name.Length());
-    out_hashedName->Truncate();
-    // This MUST MATCH HASHED_NAME_PREFIX from
-    // angle/src/compiler/translator/HashNames.h .
-    out_hashedName->AppendPrintf("webgl_%llx", hash);
-}
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgram, mAttachedShaders)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgram, mVertShader, mFragShader)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLProgram, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLProgram, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -4,135 +4,147 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WEBGL_PROGRAM_H_
 #define WEBGL_PROGRAM_H_
 
 #include <map>
 #include "mozilla/CheckedInt.h"
 #include "mozilla/LinkedList.h"
+#include "nsString.h"
 #include "nsWrapperCache.h"
+#include <set>
+#include <vector>
 #include "WebGLObjectModel.h"
 #include "WebGLShader.h"
-#include "WebGLUniformInfo.h"
 
 namespace mozilla {
 
+class WebGLActiveInfo;
+class WebGLProgram;
+class WebGLUniformLocation;
+
+namespace webgl {
+
+struct LinkedProgramInfo MOZ_FINAL
+    : public RefCounted<LinkedProgramInfo>
+    , public SupportsWeakPtr<LinkedProgramInfo>
+{
+    MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)
+
+    WebGLProgram* const prog;
+    std::vector<nsRefPtr<WebGLActiveInfo>> activeAttribs;
+    std::vector<nsRefPtr<WebGLActiveInfo>> activeUniforms;
+
+    // Needed for Get{Attrib,Uniform}Location. The keys for these are non-mapped
+    // user-facing `GLActiveInfo::name`s, without any final "[0]".
+    std::map<nsCString, const WebGLActiveInfo*> attribMap;
+    std::map<nsCString, const WebGLActiveInfo*> uniformMap;
+
+    // Needed for draw call validation.
+    std::set<GLuint> activeAttribLocs;
+
+    explicit LinkedProgramInfo(WebGLProgram* aProg);
+
+    bool FindAttrib(const nsCString& baseUserName,
+                    const WebGLActiveInfo** const out_activeInfo) const
+    {
+        auto itr = attribMap.find(baseUserName);
+        if (itr == attribMap.end())
+            return false;
+
+        *out_activeInfo = itr->second;
+        return true;
+    }
+
+    bool FindUniform(const nsCString& baseUserName,
+                     const WebGLActiveInfo** const out_activeInfo) const
+    {
+        auto itr = uniformMap.find(baseUserName);
+        if (itr == uniformMap.end())
+            return false;
+
+        *out_activeInfo = itr->second;
+        return true;
+    }
+
+    bool HasActiveAttrib(GLuint loc) const {
+        auto itr = activeAttribLocs.find(loc);
+        return itr != activeAttribLocs.end();
+    }
+};
+
+} // namespace webgl
+
 class WebGLShader;
-struct WebGLUniformInfo;
 
 typedef nsDataHashtable<nsCStringHashKey, nsCString> CStringMap;
-typedef nsDataHashtable<nsCStringHashKey,
-                        WebGLUniformInfo> CStringToUniformInfoMap;
 
 class WebGLProgram MOZ_FINAL
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLProgram>
     , public LinkedListElement<WebGLProgram>
     , public WebGLContextBoundObject
 {
 public:
+    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLProgram)
+    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLProgram)
+
     explicit WebGLProgram(WebGLContext* webgl);
 
     void Delete();
 
-    void DetachShaders() {
-        mAttachedShaders.Clear();
-    }
-
-    GLuint GLName() { return mGLName; }
-    const nsTArray<WebGLRefPtr<WebGLShader> >& AttachedShaders() const {
-        return mAttachedShaders;
-    }
-    bool LinkStatus() { return mLinkStatus; }
-    uint32_t Generation() const { return mGeneration.value(); }
-    void SetLinkStatus(bool val) { mLinkStatus = val; }
-
-    bool ContainsShader(WebGLShader* shader) {
-        return mAttachedShaders.Contains(shader);
-    }
-
-    // return true if the shader wasn't already attached
-    bool AttachShader(WebGLShader* shader);
-
-    // return true if the shader was found and removed
-    bool DetachShader(WebGLShader* shader);
-
-    bool HasAttachedShaderOfType(GLenum shaderType);
-
-    bool HasBothShaderTypesAttached() {
-        return HasAttachedShaderOfType(LOCAL_GL_VERTEX_SHADER) &&
-               HasAttachedShaderOfType(LOCAL_GL_FRAGMENT_SHADER);
-    }
-
-    bool HasBadShaderAttached();
+    // GL funcs
+    void AttachShader(WebGLShader* shader);
+    void BindAttribLocation(GLuint index, const nsAString& name);
+    void DetachShader(WebGLShader* shader);
+    already_AddRefed<WebGLActiveInfo> GetActiveAttrib(GLuint index) const;
+    already_AddRefed<WebGLActiveInfo> GetActiveUniform(GLuint index) const;
+    void GetAttachedShaders(nsTArray<nsRefPtr<WebGLShader>>* const out) const;
+    GLint GetAttribLocation(const nsAString& name) const;
+    void GetProgramInfoLog(nsAString* const out) const;
+    JS::Value GetProgramParameter(GLenum pname) const;
+    already_AddRefed<WebGLUniformLocation> GetUniformLocation(const nsAString& name) const;
+    bool LinkProgram();
+    bool UseProgram() const;
+    void ValidateProgram() const;
 
-    size_t UpperBoundNumSamplerUniforms();
-
-    bool NextGeneration() {
-        if (!(mGeneration + 1).isValid())
-            return false; // must exit without changing mGeneration
-
-        ++mGeneration;
-        return true;
-    }
-
-    // Called only after LinkProgram
-    bool UpdateInfo();
-
-    // Getters for cached program info
-    bool IsAttribInUse(uint32_t i) const { return mAttribsInUse[i]; }
+    ////////////////
 
-    // Maps identifier |name| to the mapped identifier |*mappedName|
-    // Both are ASCII strings.
-    void MapIdentifier(const nsACString& name, nsCString* out_mappedName);
+    bool FindAttribUserNameByMappedName(const nsACString& mappedName,
+                                        nsDependentCString* const out_userName) const;
+    bool FindUniformByMappedName(const nsACString& mappedName,
+                                 nsCString* const out_userName,
+                                 bool* const out_isArray) const;
 
-    // Un-maps mapped identifier |name| to the original identifier
-    // |*reverseMappedName|.
-    // Both are ASCII strings.
-    void ReverseMapIdentifier(const nsACString& name,
-                              nsCString* out_reverseMappedName);
+    bool IsLinked() const { return mMostRecentLinkInfo; }
 
-    /* Returns the uniform array size (or 1 if the uniform is not an array) of
-     * the uniform with given mapped identifier.
-     *
-     * Note: The input string |name| is the mapped identifier, not the original
-     * identifier.
-     */
-    WebGLUniformInfo GetUniformInfoForMappedIdentifier(const nsACString& name);
+    const webgl::LinkedProgramInfo* LinkInfo() const {
+        return mMostRecentLinkInfo.get();
+    }
 
     WebGLContext* GetParentObject() const {
         return Context();
     }
 
-    virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
-
-    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLProgram)
-    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLProgram)
+    virtual JSObject* WrapObject(JSContext* js) MOZ_OVERRIDE;
 
-    // public post-link data
-    std::map<GLint, nsCString> mActiveAttribMap;
-
-    static uint64_t IdentifierHashFunction(const char* ident, size_t size);
-    static void HashMapIdentifier(const nsACString& name,
-                                  nsCString* const out_hashedName);
-
-protected:
+private:
     ~WebGLProgram() {
         DeleteOnce();
     }
 
-    GLuint mGLName;
-    bool mLinkStatus;
-    // attached shaders of the program object
-    nsTArray<WebGLRefPtr<WebGLShader> > mAttachedShaders;
-    CheckedUint32 mGeneration;
+    bool LinkAndUpdate();
+
+public:
+    const GLuint mGLName;
 
-    // post-link data
-    FallibleTArray<bool> mAttribsInUse;
-    nsAutoPtr<CStringMap> mIdentifierMap, mIdentifierReverseMap;
-    nsAutoPtr<CStringToUniformInfoMap> mUniformInfoMap;
-    int mAttribMaxNameLength;
+private:
+    WebGLRefPtr<WebGLShader> mVertShader;
+    WebGLRefPtr<WebGLShader> mFragShader;
+    std::map<nsCString, GLuint> mBoundAttribLocs;
+    nsCString mLinkLog;
+    RefPtr<const webgl::LinkedProgramInfo> mMostRecentLinkInfo;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_PROGRAM_H_
--- a/dom/canvas/WebGLShader.cpp
+++ b/dom/canvas/WebGLShader.cpp
@@ -4,61 +4,384 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLShader.h"
 
 #include "angle/ShaderLang.h"
 #include "GLContext.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/MemoryReporting.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
 #include "WebGLContext.h"
 #include "WebGLObjectModel.h"
+#include "WebGLShaderValidator.h"
+#include "WebGLValidateStrings.h"
 
 namespace mozilla {
 
-JSObject*
-WebGLShader::WrapObject(JSContext* cx) {
-    return dom::WebGLShaderBinding::Wrap(cx, this);
+// On success, writes to out_validator and out_translatedSource.
+// On failure, writes to out_translationLog.
+static bool
+Translate(const nsACString& source, webgl::ShaderValidator* validator,
+          nsACString* const out_translationLog, nsACString* const out_translatedSource)
+{
+    if (!validator->ValidateAndTranslate(source.BeginReading())) {
+        validator->GetInfoLog(out_translationLog);
+        return false;
+    }
+
+    // Success
+    validator->GetOutput(out_translatedSource);
+    return true;
+}
+
+template<size_t N>
+static bool
+SubstringStartsWith(const std::string& testStr, size_t offset, const char (& refStr)[N])
+{
+    for (size_t i = 0; i < N-1; i++) {
+        if (testStr[offset + i] != refStr[i])
+            return false;
+    }
+    return true;
+}
+
+/* On success, writes to out_translatedSource.
+ * On failure, writes to out_translationLog.
+ *
+ * Requirements:
+ *   #version is either omitted, `#version 100`, or `version 300 es`.
+ */
+static bool
+TranslateWithoutValidation(const nsACString& sourceNS, bool isWebGL2,
+                           nsACString* const out_translationLog,
+                           nsACString* const out_translatedSource)
+{
+    std::string source = sourceNS.BeginReading();
+
+    size_t versionStrStart = source.find("#version");
+    size_t versionStrLen;
+    uint32_t glesslVersion;
+
+    if (versionStrStart != std::string::npos) {
+        static const char versionStr100[] = "#version 100\n";
+        static const char versionStr300es[] = "#version 300 es\n";
+
+        if (isWebGL2 && SubstringStartsWith(source, versionStrStart, versionStr300es)) {
+            glesslVersion = 300;
+            versionStrLen = strlen(versionStr300es);
+
+        } else if (SubstringStartsWith(source, versionStrStart, versionStr100)) {
+            glesslVersion = 100;
+            versionStrLen = strlen(versionStr100);
+
+        } else {
+            nsPrintfCString error("#version, if declared, must be %s.",
+                                  isWebGL2 ? "`100` or `300 es`"
+                                           : "`100`");
+            *out_translationLog = error;
+            return false;
+        }
+    } else {
+        versionStrStart = 0;
+        versionStrLen = 0;
+        glesslVersion = 100;
+    }
+
+    std::string reversionedSource = source;
+    reversionedSource.erase(versionStrStart, versionStrLen);
+
+    switch (glesslVersion) {
+    case 100:
+        break;
+    case 300:
+        reversionedSource.insert(versionStrStart, "#version 330\n");
+        break;
+    default:
+        MOZ_CRASH("Bad `glesslVersion`.");
+    }
+
+    out_translatedSource->Assign(reversionedSource.c_str(),
+                                 reversionedSource.length());
+    return true;
+}
+
+static void
+GetCompilationStatusAndLog(gl::GLContext* gl, GLuint shader, bool* const out_success,
+                           nsACString* const out_log)
+{
+    GLint compileStatus = LOCAL_GL_FALSE;
+    gl->fGetShaderiv(shader, LOCAL_GL_COMPILE_STATUS, &compileStatus);
+
+    // It's simpler if we always get the log.
+    GLint lenWithNull = 0;
+    gl->fGetShaderiv(shader, LOCAL_GL_INFO_LOG_LENGTH, &lenWithNull);
+
+    if (lenWithNull > 1) {
+        // SetLength takes the length without the null.
+        out_log->SetLength(lenWithNull - 1);
+        gl->fGetShaderInfoLog(shader, lenWithNull, nullptr, out_log->BeginWriting());
+    } else {
+        out_log->SetLength(0);
+    }
+
+    *out_success = (compileStatus == LOCAL_GL_TRUE);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static GLuint
+CreateShader(gl::GLContext* gl, GLenum type)
+{
+    gl->MakeCurrent();
+    return gl->fCreateShader(type);
 }
 
 WebGLShader::WebGLShader(WebGLContext* webgl, GLenum type)
     : WebGLContextBoundObject(webgl)
+    , mGLName(CreateShader(webgl->GL(), type))
     , mType(type)
-    , mNeedsTranslation(true)
-    , mAttribMaxNameLength(0)
-    , mCompileStatus(false)
+{
+    mContext->mShaders.insertBack(this);
+}
+
+WebGLShader::~WebGLShader()
+{
+    DeleteOnce();
+}
+
+void
+WebGLShader::ShaderSource(const nsAString& source)
+{
+    StripComments stripComments(source);
+    const nsAString& cleanSource = Substring(stripComments.result().Elements(),
+                                             stripComments.length());
+    if (!ValidateGLSLString(cleanSource, mContext, "shaderSource"))
+        return;
+
+    // We checked that the source stripped of comments is in the
+    // 7-bit ASCII range, so we can skip the NS_IsAscii() check.
+    NS_LossyConvertUTF16toASCII sourceCString(cleanSource);
+
+    if (mContext->gl->WorkAroundDriverBugs()) {
+        const size_t maxSourceLength = 0x3ffff;
+        if (sourceCString.Length() > maxSourceLength) {
+            mContext->ErrorInvalidValue("shaderSource: Source has more than %d"
+                                        " characters. (Driver workaround)",
+                                        maxSourceLength);
+            return;
+        }
+    }
+
+    // HACK - dump shader source
+    {
+/*
+        printf_stderr("//-*- glsl -*-\n");
+        // Wow - Roll Your Own For Each Lines because printf_stderr has a hard-coded internal size, so long strings are truncated.
+        const nsString& src = shader->Source();
+        int32_t start = 0;
+        int32_t end = src.Find("\n", false, start, -1);
+        while (end > -1) {
+            printf_stderr("%s\n", NS_ConvertUTF16toUTF8(nsDependentSubstring(src, start, end - start)).get());
+            start = end + 1;
+            end = src.Find("\n", false, start, -1);
+        }
+        printf_stderr("//\n");
+*/
+    }
+    // HACK
+
+    mSource = source;
+    mCleanSource = sourceCString;
+}
+
+void
+WebGLShader::CompileShader()
+{
+    mValidator = nullptr;
+    mTranslationSuccessful = false;
+    mCompilationSuccessful = false;
+
+    gl::GLContext* gl = mContext->gl;
+
+    mValidator.reset(mContext->CreateShaderValidator(mType));
+
+    bool success;
+    if (mValidator) {
+        success = Translate(mCleanSource, mValidator.get(), &mValidationLog,
+                            &mTranslatedSource);
+    } else {
+        success = TranslateWithoutValidation(mCleanSource, mContext->IsWebGL2(),
+                                             &mValidationLog, &mTranslatedSource);
+    }
+
+    if (!success)
+        return;
+
+    mTranslationSuccessful = true;
+
+    gl->MakeCurrent();
+
+    const char* const parts[] = {
+        mTranslatedSource.BeginReading()
+    };
+    gl->fShaderSource(mGLName, ArrayLength(parts), parts, nullptr);
+
+    gl->fCompileShader(mGLName);
+
+    GetCompilationStatusAndLog(gl, mGLName, &mCompilationSuccessful, &mCompilationLog);
+}
+
+void
+WebGLShader::GetShaderInfoLog(nsAString* out) const
+{
+    const nsCString& log = !mTranslationSuccessful ? mValidationLog
+                                                   : mCompilationLog;
+    CopyASCIItoUTF16(log, *out);
+}
+
+JS::Value
+WebGLShader::GetShaderParameter(GLenum pname) const
 {
-    mContext->MakeContextCurrent();
-    mGLName = mContext->gl->fCreateShader(mType);
-    mContext->mShaders.insertBack(this);
+    switch (pname) {
+    case LOCAL_GL_SHADER_TYPE:
+        return JS::NumberValue(mType);
+
+    case LOCAL_GL_DELETE_STATUS:
+        return JS::BooleanValue(IsDeleteRequested());
+
+    case LOCAL_GL_COMPILE_STATUS:
+        return JS::BooleanValue(mCompilationSuccessful);
+
+    default:
+        mContext->ErrorInvalidEnumInfo("getShaderParameter: `pname`", pname);
+        return JS::NullValue();
+    }
+}
+
+void
+WebGLShader::GetShaderSource(nsAString* out) const
+{
+    out->SetIsVoid(false);
+    *out = mSource;
+}
+
+void
+WebGLShader::GetShaderTranslatedSource(nsAString* out) const
+{
+    if (!mCompilationSuccessful) {
+        mContext->ErrorInvalidOperation("getShaderTranslatedSource: Shader has"
+                                        " not been successfully compiled.");
+        return;
+    }
+
+    out->SetIsVoid(false);
+    CopyASCIItoUTF16(mTranslatedSource, *out);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool
+WebGLShader::CanLinkTo(const WebGLShader* prev, nsCString* const out_log) const
+{
+    if (!mValidator)
+        return true;
+
+    return mValidator->CanLinkTo(prev->mValidator.get(), out_log);
+}
+
+size_t
+WebGLShader::CalcNumSamplerUniforms() const
+{
+    if (mValidator)
+        return mValidator->CalcNumSamplerUniforms();
+
+    // TODO
+    return 0;
+}
+
+void
+WebGLShader::BindAttribLocation(GLuint prog, const nsCString& userName,
+                                GLuint index) const
+{
+    std::string userNameStr(userName.BeginReading());
+
+    const std::string* mappedNameStr = &userNameStr;
+    if (mValidator)
+        mValidator->FindAttribMappedNameByUserName(userNameStr, &mappedNameStr);
+
+    mContext->gl->fBindAttribLocation(prog, index, mappedNameStr->c_str());
+}
+
+bool
+WebGLShader::FindAttribUserNameByMappedName(const nsACString& mappedName,
+                                            nsDependentCString* const out_userName) const
+{
+    if (!mValidator)
+        return false;
+
+    const std::string mappedNameStr(mappedName.BeginReading());
+    const std::string* userNameStr;
+    if (!mValidator->FindAttribUserNameByMappedName(mappedNameStr, &userNameStr))
+        return false;
+
+    out_userName->Rebind(userNameStr->c_str());
+    return true;
+}
+
+bool
+WebGLShader::FindUniformByMappedName(const nsACString& mappedName,
+                                     nsCString* const out_userName,
+                                     bool* const out_isArray) const
+{
+    if (!mValidator)
+        return false;
+
+    const std::string mappedNameStr(mappedName.BeginReading(), mappedName.Length());
+    std::string userNameStr;
+    if (!mValidator->FindUniformByMappedName(mappedNameStr, &userNameStr, out_isArray))
+        return false;
+
+    *out_userName = userNameStr.c_str();
+    return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Boilerplate
+
+JSObject*
+WebGLShader::WrapObject(JSContext* js)
+{
+    return dom::WebGLShaderBinding::Wrap(js, this);
+}
+
+size_t
+WebGLShader::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
+{
+    size_t validatorSize = mValidator ? mallocSizeOf(mValidator.get())
+                                      : 0;
+    return mallocSizeOf(this) +
+           mSource.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
+           mCleanSource.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
+           validatorSize +
+           mValidationLog.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
+           mTranslatedSource.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
+           mCompilationLog.SizeOfExcludingThisIfUnshared(mallocSizeOf);
 }
 
 void
 WebGLShader::Delete()
 {
-    mSource.Truncate();
-    mTranslationLog.Truncate();
-    mContext->MakeContextCurrent();
-    mContext->gl->fDeleteShader(mGLName);
-    LinkedListElement<WebGLShader>::removeFrom(mContext->mShaders);
-}
+    gl::GLContext* gl = mContext->GL();
 
-size_t
-WebGLShader::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
-{
-    return mallocSizeOf(this) +
-           mSource.SizeOfExcludingThisIfUnshared(mallocSizeOf) +
-           mTranslationLog.SizeOfExcludingThisIfUnshared(mallocSizeOf);
-}
+    gl->MakeCurrent();
+    gl->fDeleteShader(mGLName);
 
-void
-WebGLShader::SetTranslationSuccess()
-{
-    mTranslationLog.SetIsVoid(true);
-    mNeedsTranslation = false;
+    LinkedListElement<WebGLShader>::removeFrom(mContext->mShaders);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLShader)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLShader, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLShader, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLShader.h
+++ b/dom/canvas/WebGLShader.h
@@ -1,106 +1,88 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 WEBGL_SHADER_H_
 #define WEBGL_SHADER_H_
 
+#include "GLDefs.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsWrapperCache.h"
 #include "WebGLObjectModel.h"
-#include "WebGLUniformInfo.h"
 
 namespace mozilla {
 
-struct WebGLMappedIdentifier
-{
-    // ASCII strings
-    nsCString original;
-    nsCString mapped;
-
-    WebGLMappedIdentifier(const nsACString& o, const nsACString& m)
-        : original(o)
-        , mapped(m)
-    {}
-};
+namespace webgl {
+class ShaderValidator;
+} // namespace webgl
 
 class WebGLShader MOZ_FINAL
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLShader>
     , public LinkedListElement<WebGLShader>
     , public WebGLContextBoundObject
 {
     friend class WebGLContext;
     friend class WebGLProgram;
 
 public:
     WebGLShader(WebGLContext* webgl, GLenum type);
 
-    size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+protected:
+    ~WebGLShader();
 
-    GLuint GLName() { return mGLName; }
-    sh::GLenum ShaderType() { return mType; }
+public:
+    // GL funcs
+    void CompileShader();
+    JS::Value GetShaderParameter(GLenum pname) const;
+    void GetShaderInfoLog(nsAString* out) const;
+    void GetShaderSource(nsAString* out) const;
+    void GetShaderTranslatedSource(nsAString* out) const;
+    void ShaderSource(const nsAString& source);
 
-    void SetSource(const nsAString& src) {
-        // TODO: Do some quick gzip here maybe? Getting this will be very rare,
-        // and we have to keep it forever.
-        mSource.Assign(src);
-    }
+    // Util funcs
+    bool CanLinkTo(const WebGLShader* prev, nsCString* const out_log) const;
+    size_t CalcNumSamplerUniforms() const;
+    void BindAttribLocation(GLuint prog, const nsCString& userName, GLuint index) const;
+    bool FindAttribUserNameByMappedName(const nsACString& mappedName,
+                                        nsDependentCString* const out_userName) const;
+    bool FindUniformByMappedName(const nsACString& mappedName,
+                                 nsCString* const out_userName,
+                                 bool* const out_isArray) const;
 
-    const nsString& Source() const { return mSource; }
-
-    void SetNeedsTranslation() { mNeedsTranslation = true; }
-    bool NeedsTranslation() const { return mNeedsTranslation; }
-
-    void SetCompileStatus (bool status) {
-        mCompileStatus = status;
+    bool IsCompiled() const {
+        return mTranslationSuccessful && mCompilationSuccessful;
     }
 
+    // Other funcs
+    size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
     void Delete();
 
-    bool CompileStatus() const {
-        return mCompileStatus;
-    }
-
-    void SetTranslationSuccess();
-
-    void SetTranslationFailure(const nsCString& msg) {
-        mTranslationLog.Assign(msg);
-    }
+    WebGLContext* GetParentObject() const { return Context(); }
 
-    const nsCString& TranslationLog() const { return mTranslationLog; }
-
-    const nsString& TranslatedSource() const { return mTranslatedSource; }
-
-    WebGLContext* GetParentObject() const {
-        return Context();
-    }
-
-    virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
+    virtual JSObject* WrapObject(JSContext* js) MOZ_OVERRIDE;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLShader)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLShader)
 
+public:
+    const GLuint mGLName;
+    const GLenum mType;
 protected:
-    ~WebGLShader() {
-        DeleteOnce();
-    }
-
-    GLuint mGLName;
-    GLenum mType;
     nsString mSource;
-    nsString mTranslatedSource;
-    nsCString mTranslationLog; // The translation log should contain only ASCII characters
-    bool mNeedsTranslation;
-    nsTArray<WebGLMappedIdentifier> mAttributes;
-    nsTArray<WebGLMappedIdentifier> mUniforms;
-    nsTArray<WebGLUniformInfo> mUniformInfos;
-    int mAttribMaxNameLength;
-    bool mCompileStatus;
+    nsCString mCleanSource;
+
+    UniquePtr<webgl::ShaderValidator> mValidator;
+    nsCString mValidationLog;
+    bool mTranslationSuccessful;
+    nsCString mTranslatedSource;
+
+    bool mCompilationSuccessful;
+    nsCString mCompilationLog;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_SHADER_H_
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "WebGLShaderValidator.h"
+
+#include "angle/ShaderLang.h"
+#include "GLContext.h"
+#include "MurmurHash3.h"
+#include "nsPrintfCString.h"
+#include <string>
+#include <vector>
+#include "WebGLContext.h"
+
+namespace mozilla {
+namespace webgl {
+
+uint64_t
+IdentifierHashFunc(const char* name, size_t len)
+{
+    // NB: we use the x86 function everywhere, even though it's suboptimal perf
+    // on x64.  They return different results; not sure if that's a requirement.
+    uint64_t hash[2];
+    MurmurHash3_x86_128(name, len, 0, hash);
+    return hash[0];
+}
+
+static int
+ChooseValidatorCompileOptions(const ShBuiltInResources& resources,
+                              const mozilla::gl::GLContext* gl)
+{
+    int options = SH_VARIABLES |
+                  SH_ENFORCE_PACKING_RESTRICTIONS |
+                  SH_INIT_VARYINGS_WITHOUT_STATIC_USE |
+                  SH_OBJECT_CODE |
+                  SH_LIMIT_CALL_STACK_DEPTH;
+
+    if (resources.MaxExpressionComplexity > 0) {
+        options |= SH_LIMIT_EXPRESSION_COMPLEXITY;
+    }
+
+#ifndef XP_MACOSX
+    // We want to do this everywhere, but to do this on Mac, we need
+    // to do it only on Mac OSX > 10.6 as this causes the shader
+    // compiler in 10.6 to crash
+    options |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS;
+#endif
+
+#ifdef XP_MACOSX
+    if (gl->WorkAroundDriverBugs()) {
+        // Work around https://bugs.webkit.org/show_bug.cgi?id=124684,
+        // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb
+        options |= SH_UNFOLD_SHORT_CIRCUIT;
+
+        // Work around bug 665578 and bug 769810
+        if (gl->Vendor() == gl::GLVendor::ATI) {
+            options |= SH_EMULATE_BUILT_IN_FUNCTIONS;
+        }
+
+        // Work around bug 735560
+        if (gl->Vendor() == gl::GLVendor::Intel) {
+            options |= SH_EMULATE_BUILT_IN_FUNCTIONS;
+        }
+
+        // Work around bug 636926
+        if (gl->Vendor() == gl::GLVendor::NVIDIA) {
+            options |= SH_UNROLL_FOR_LOOP_WITH_SAMPLER_ARRAY_INDEX;
+        }
+    }
+#endif
+
+    return options;
+}
+
+} // namespace webgl
+
+////////////////////////////////////////
+
+webgl::ShaderValidator*
+WebGLContext::CreateShaderValidator(GLenum shaderType) const
+{
+    if (mBypassShaderValidation)
+        return nullptr;
+
+    ShShaderSpec spec = SH_WEBGL_SPEC;
+    ShShaderOutput outputLanguage = gl->IsGLES() ? SH_ESSL_OUTPUT
+                                                 : SH_GLSL_OUTPUT;
+
+    ShBuiltInResources resources;
+    memset(&resources, 0, sizeof(resources));
+    ShInitBuiltInResources(&resources);
+
+    resources.HashFunction = webgl::IdentifierHashFunc;
+
+    resources.MaxVertexAttribs = mGLMaxVertexAttribs;
+    resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
+    resources.MaxVaryingVectors = mGLMaxVaryingVectors;
+    resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits;
+    resources.MaxCombinedTextureImageUnits = mGLMaxTextureUnits;
+    resources.MaxTextureImageUnits = mGLMaxTextureImageUnits;
+    resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors;
+    resources.MaxDrawBuffers = mGLMaxDrawBuffers;
+
+    if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth))
+        resources.EXT_frag_depth = 1;
+
+    if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives))
+        resources.OES_standard_derivatives = 1;
+
+    if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
+        resources.EXT_draw_buffers = 1;
+
+    if (IsExtensionEnabled(WebGLExtensionID::EXT_shader_texture_lod))
+        resources.EXT_shader_texture_lod = 1;
+
+    // Tell ANGLE to allow highp in frag shaders. (unless disabled)
+    // If underlying GLES doesn't have highp in frag shaders, it should complain anyways.
+    resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1;
+
+    if (gl->WorkAroundDriverBugs()) {
+#ifdef XP_MACOSX
+        if (gl->Vendor() == gl::GLVendor::NVIDIA) {
+            // Work around bug 890432
+            resources.MaxExpressionComplexity = 1000;
+        }
+#endif
+    }
+
+    int compileOptions = webgl::ChooseValidatorCompileOptions(resources, gl);
+
+    return webgl::ShaderValidator::Create(shaderType, spec, outputLanguage, resources,
+                                          compileOptions);
+}
+
+////////////////////////////////////////
+
+namespace webgl {
+
+/*static*/ ShaderValidator*
+ShaderValidator::Create(GLenum shaderType, ShShaderSpec spec,
+                        ShShaderOutput outputLanguage,
+                        const ShBuiltInResources& resources, int compileOptions)
+{
+    ShHandle handle = ShConstructCompiler(shaderType, spec, outputLanguage, &resources);
+    if (!handle)
+        return nullptr;
+
+    return new ShaderValidator(handle, compileOptions);
+}
+
+ShaderValidator::~ShaderValidator()
+{
+    ShDestruct(mHandle);
+}
+
+bool
+ShaderValidator::ValidateAndTranslate(const char* source)
+{
+    MOZ_ASSERT(!mHasRun);
+    mHasRun = true;
+
+    const char* const parts[] = {
+        source
+    };
+    return ShCompile(mHandle, parts, ArrayLength(parts), mCompileOptions);
+}
+
+void
+ShaderValidator::GetInfo(ShShaderInfo pname, size_t* params) const
+{
+    MOZ_ASSERT(mHasRun);
+
+    ShGetInfo(mHandle, pname, params);
+}
+
+void
+ShaderValidator::GetInfoLog(nsACString* out) const
+{
+    MOZ_ASSERT(mHasRun);
+
+    size_t lenWithNull = 0;
+    GetInfo(SH_INFO_LOG_LENGTH, &lenWithNull);
+    MOZ_ASSERT(lenWithNull >= 1);
+
+    // SetLength allocates len+1, for the null-term.
+    out->SetLength(lenWithNull - 1);
+
+    ShGetInfoLog(mHandle, out->BeginWriting());
+}
+
+void
+ShaderValidator::GetOutput(nsACString* out) const
+{
+    MOZ_ASSERT(mHasRun);
+
+    size_t lenWithNull = 0;
+    GetInfo(SH_OBJECT_CODE_LENGTH, &lenWithNull);
+    MOZ_ASSERT(lenWithNull >= 1);
+
+    // SetLength allocates len+1, for the null-term.
+    out->SetLength(lenWithNull - 1);
+
+    ShGetObjectCode(mHandle, out->BeginWriting());
+}
+
+template<size_t N>
+static bool
+StartsWith(const std::string& haystack, const char (&needle)[N])
+{
+    return haystack.compare(0, N - 1, needle) == 0;
+}
+
+bool
+ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const
+{
+    {
+        const std::vector<sh::Uniform>& vertList = *ShGetUniforms(prev->mHandle);
+        const std::vector<sh::Uniform>& fragList = *ShGetUniforms(mHandle);
+
+        for (auto itrFrag = fragList.begin(); itrFrag != fragList.end(); ++itrFrag) {
+            for (auto itrVert = vertList.begin(); itrVert != vertList.end(); ++itrVert) {
+                if (itrVert->name != itrFrag->name)
+                    continue;
+
+                if (!itrVert->isSameUniformAtLinkTime(*itrFrag)) {
+                    nsPrintfCString error("Uniform `%s`is not linkable between"
+                                          " attached shaders.",
+                                          itrFrag->name.c_str());
+                    *out_log = error;
+                    return false;
+                }
+
+                break;
+            }
+        }
+    }
+    {
+        const std::vector<sh::Varying>& vertList = *ShGetVaryings(prev->mHandle);
+        const std::vector<sh::Varying>& fragList = *ShGetVaryings(mHandle);
+
+        for (auto itrFrag = fragList.begin(); itrFrag != fragList.end(); ++itrFrag) {
+            static const char prefix[] = "gl_";
+            if (StartsWith(itrFrag->name, prefix))
+                continue;
+
+            bool definedInVertShader = false;
+
+            for (auto itrVert = vertList.begin(); itrVert != vertList.end(); ++itrVert) {
+                if (itrVert->name != itrFrag->name)
+                    continue;
+
+                if (!itrVert->isSameVaryingAtLinkTime(*itrFrag)) {
+                    nsPrintfCString error("Varying `%s`is not linkable between"
+                                          " attached shaders.",
+                                          itrFrag->name.c_str());
+                    *out_log = error;
+                    return false;
+                }
+
+                definedInVertShader = true;
+                break;
+            }
+
+            if (!definedInVertShader && itrFrag->staticUse) {
+                nsPrintfCString error("Varying `%s` has static-use in the frag"
+                                      " shader, but is undeclared in the vert"
+                                      " shader.", itrFrag->name.c_str());
+                *out_log = error;
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+size_t
+ShaderValidator::CalcNumSamplerUniforms() const
+{
+    size_t accum = 0;
+
+    const std::vector<sh::Uniform>& uniforms = *ShGetUniforms(mHandle);
+
+    for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) {
+        GLenum type = itr->type;
+        if (type == LOCAL_GL_SAMPLER_2D ||
+            type == LOCAL_GL_SAMPLER_CUBE)
+        {
+            accum += itr->arraySize;
+        }
+    }
+
+    return accum;
+}
+
+// Attribs cannot be structs or arrays, and neither can vertex inputs in ES3.
+// Therefore, attrib names are always simple.
+bool
+ShaderValidator::FindAttribUserNameByMappedName(const std::string& mappedName,
+                                                const std::string** const out_userName) const
+{
+    const std::vector<sh::Attribute>& attribs = *ShGetAttributes(mHandle);
+    for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
+        if (itr->mappedName == mappedName) {
+            *out_userName = &(itr->name);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+ShaderValidator::FindAttribMappedNameByUserName(const std::string& userName,
+                                                const std::string** const out_mappedName) const
+{
+    const std::vector<sh::Attribute>& attribs = *ShGetAttributes(mHandle);
+    for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
+        if (itr->name == userName) {
+            *out_mappedName = &(itr->mappedName);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+// This must handle names like "foo.bar[0]".
+bool
+ShaderValidator::FindUniformByMappedName(const std::string& mappedName,
+                                         std::string* const out_userName,
+                                         bool* const out_isArray) const
+{
+    const std::vector<sh::Uniform>& uniforms = *ShGetUniforms(mHandle);
+    for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) {
+        const sh::ShaderVariable* found;
+        if (!itr->findInfoByMappedName(mappedName, &found, out_userName))
+            continue;
+
+        *out_isArray = found->isArray();
+        return true;
+    }
+
+    return false;
+}
+
+} // namespace webgl
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGLShaderValidator.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 WEBGL_SHADER_VALIDATOR_H_
+#define WEBGL_SHADER_VALIDATOR_H_
+
+#include "angle/ShaderLang.h"
+#include "GLDefs.h"
+#include "nsString.h"
+#include <string>
+
+namespace mozilla {
+namespace webgl {
+
+class ShaderValidator MOZ_FINAL
+{
+    const ShHandle mHandle;
+    const int mCompileOptions;
+    bool mHasRun;
+
+public:
+    static ShaderValidator* Create(GLenum shaderType, ShShaderSpec spec,
+                                   ShShaderOutput outputLanguage,
+                                   const ShBuiltInResources& resources,
+                                   int compileOptions);
+
+private:
+    ShaderValidator(ShHandle handle, int compileOptions)
+        : mHandle(handle)
+        , mCompileOptions(compileOptions)
+        , mHasRun(false)
+    { }
+
+public:
+    ~ShaderValidator();
+
+    bool ValidateAndTranslate(const char* source);
+    void GetInfo(ShShaderInfo pname, size_t* params) const;
+    void GetInfoLog(nsACString* out) const;
+    void GetOutput(nsACString* out) const;
+    bool CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const;
+    size_t CalcNumSamplerUniforms() const;
+
+    bool FindAttribUserNameByMappedName(const std::string& mappedName,
+                                        const std::string** const out_userName) const;
+
+    bool FindAttribMappedNameByUserName(const std::string& userName,
+                                        const std::string** const out_mappedName) const;
+
+    bool FindUniformByMappedName(const std::string& mappedName,
+                                 std::string* const out_userName,
+                                 bool* const out_isArray) const;
+};
+
+} // namespace webgl
+} // namespace mozilla
+
+#endif // WEBGL_SHADER_VALIDATOR_H_
deleted file mode 100644
--- a/dom/canvas/WebGLUniformInfo.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* 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 WEBGLUNIFORMINFO_H_
-#define WEBGLUNIFORMINFO_H_
-
-#include "GLDefs.h"
-#include "angle/ShaderLang.h"
-
-namespace mozilla {
-
-struct WebGLUniformInfo {
-    uint32_t arraySize;
-    bool isArray;
-    sh::GLenum type;
-
-    explicit WebGLUniformInfo(uint32_t s = 0, bool a = false, sh::GLenum t = LOCAL_GL_NONE)
-        : arraySize(s), isArray(a), type(t) {}
-
-    int ElementSize() const {
-        switch (type) {
-            case LOCAL_GL_FLOAT:
-            case LOCAL_GL_INT:
-            case LOCAL_GL_UNSIGNED_INT:
-            case LOCAL_GL_BOOL:
-            case LOCAL_GL_SAMPLER_2D:
-            case LOCAL_GL_SAMPLER_3D:
-            case LOCAL_GL_SAMPLER_CUBE:
-            case LOCAL_GL_SAMPLER_2D_SHADOW:
-            case LOCAL_GL_SAMPLER_2D_ARRAY:
-            case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
-            case LOCAL_GL_SAMPLER_CUBE_SHADOW:
-            case LOCAL_GL_INT_SAMPLER_2D:
-            case LOCAL_GL_INT_SAMPLER_3D:
-            case LOCAL_GL_INT_SAMPLER_CUBE:
-            case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
-            case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
-            case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
-            case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
-            case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
-                return 1;
-            case LOCAL_GL_FLOAT_VEC2:
-            case LOCAL_GL_INT_VEC2:
-            case LOCAL_GL_UNSIGNED_INT_VEC2:
-            case LOCAL_GL_BOOL_VEC2:
-                return 2;
-            case LOCAL_GL_FLOAT_VEC3:
-            case LOCAL_GL_INT_VEC3:
-            case LOCAL_GL_UNSIGNED_INT_VEC3:
-            case LOCAL_GL_BOOL_VEC3:
-                return 3;
-            case LOCAL_GL_FLOAT_VEC4:
-            case LOCAL_GL_INT_VEC4:
-            case LOCAL_GL_UNSIGNED_INT_VEC4:
-            case LOCAL_GL_BOOL_VEC4:
-            case LOCAL_GL_FLOAT_MAT2:
-                return 4;
-            case LOCAL_GL_FLOAT_MAT2x3:
-            case LOCAL_GL_FLOAT_MAT3x2:
-                return 6;
-            case LOCAL_GL_FLOAT_MAT2x4:
-            case LOCAL_GL_FLOAT_MAT4x2:
-                return 8;
-            case LOCAL_GL_FLOAT_MAT3:
-                return 9;
-            case LOCAL_GL_FLOAT_MAT3x4:
-            case LOCAL_GL_FLOAT_MAT4x3:
-                return 12;
-            case LOCAL_GL_FLOAT_MAT4:
-                return 16;
-            default:
-                MOZ_ASSERT(false); // should never get here
-                return 0;
-        }
-    }
-};
-
-} // namespace mozilla
-
-#endif
--- a/dom/canvas/WebGLUniformLocation.cpp
+++ b/dom/canvas/WebGLUniformLocation.cpp
@@ -1,40 +1,278 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "WebGLUniformLocation.h"
 
+#include "GLContext.h"
+#include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
+#include "WebGLActiveInfo.h"
 #include "WebGLContext.h"
 #include "WebGLProgram.h"
-#include "WebGLShader.h"
 
 namespace mozilla {
 
+WebGLUniformLocation::WebGLUniformLocation(WebGLContext* webgl,
+                                           const webgl::LinkedProgramInfo* linkInfo,
+                                           GLuint loc, const WebGLActiveInfo* activeInfo)
+    : WebGLContextBoundObject(webgl)
+    , mLinkInfo(linkInfo)
+    , mLoc(loc)
+    , mActiveInfo(activeInfo)
+{ }
+
+WebGLUniformLocation::~WebGLUniformLocation()
+{ }
+
 bool
-WebGLUniformLocation::WrapObject(JSContext* aCx,
-                                 JS::MutableHandle<JSObject*> aReflector)
+WebGLUniformLocation::ValidateForProgram(WebGLProgram* prog, WebGLContext* webgl,
+                                         const char* funcName) const
+{
+    // Check the weak-pointer.
+    if (!mLinkInfo) {
+        webgl->ErrorInvalidOperation("%s: This uniform location is obsolete because its"
+                                     " program has been successfully relinked.",
+                                     funcName);
+        return false;
+    }
+
+    if (mLinkInfo->prog != prog) {
+        webgl->ErrorInvalidOperation("%s: This uniform location corresponds to a"
+                                     " different program.", funcName);
+        return false;
+    }
+
+    return true;
+}
+
+static bool
+IsUniformSetterTypeValid(GLenum setterType, GLenum uniformType)
 {
-    return dom::WebGLUniformLocationBinding::Wrap(aCx, this, aReflector);
+    switch (uniformType) {
+    case LOCAL_GL_BOOL:
+    case LOCAL_GL_BOOL_VEC2:
+    case LOCAL_GL_BOOL_VEC3:
+    case LOCAL_GL_BOOL_VEC4:
+        return setterType == LOCAL_GL_INT ||
+               setterType == LOCAL_GL_FLOAT; // GLfloat(0.0) sets a bool to false.
+
+    case LOCAL_GL_INT:
+    case LOCAL_GL_INT_SAMPLER_2D:
+    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_INT_SAMPLER_3D:
+    case LOCAL_GL_INT_SAMPLER_CUBE:
+    case LOCAL_GL_INT_VEC2:
+    case LOCAL_GL_INT_VEC3:
+    case LOCAL_GL_INT_VEC4:
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+        return setterType == LOCAL_GL_INT;
+
+    case LOCAL_GL_FLOAT:
+    case LOCAL_GL_FLOAT_MAT2:
+    case LOCAL_GL_FLOAT_MAT2x3:
+    case LOCAL_GL_FLOAT_MAT2x4:
+    case LOCAL_GL_FLOAT_MAT3:
+    case LOCAL_GL_FLOAT_MAT3x2:
+    case LOCAL_GL_FLOAT_MAT3x4:
+    case LOCAL_GL_FLOAT_MAT4:
+    case LOCAL_GL_FLOAT_MAT4x2:
+    case LOCAL_GL_FLOAT_MAT4x3:
+    case LOCAL_GL_FLOAT_VEC2:
+    case LOCAL_GL_FLOAT_VEC3:
+    case LOCAL_GL_FLOAT_VEC4:
+        return setterType == LOCAL_GL_FLOAT;
+
+    default:
+        MOZ_CRASH("Bad `uniformType`.");
+    }
+}
+
+bool
+WebGLUniformLocation::ValidateSizeAndType(uint8_t setterElemSize, GLenum setterType,
+                                          WebGLContext* webgl, const char* funcName) const
+{
+    MOZ_ASSERT(mLinkInfo);
+
+    if (setterElemSize != mActiveInfo->mElemSize) {
+        webgl->ErrorInvalidOperation("%s: Bad uniform size: %i", funcName,
+                                     mActiveInfo->mElemSize);
+        return false;
+    }
+
+    if (!IsUniformSetterTypeValid(setterType, mActiveInfo->mElemType)) {
+        webgl->ErrorInvalidOperation("%s: Bad uniform type: %i", funcName,
+                                     mActiveInfo->mElemSize);
+        return false;
+    }
+
+    return true;
 }
 
-WebGLUniformLocation::WebGLUniformLocation(WebGLContext* context,
-                                           WebGLProgram* program,
-                                           GLint location,
-                                           const WebGLUniformInfo& info)
-    : WebGLContextBoundObject(context)
-    , mProgram(program)
-    , mProgramGeneration(program->Generation())
-    , mLocation(location)
-    , mInfo(info)
+bool
+WebGLUniformLocation::ValidateArrayLength(uint8_t setterElemSize, size_t setterArraySize,
+                                          WebGLContext* webgl, const char* funcName) const
 {
-    mElementSize = info.ElementSize();
+    MOZ_ASSERT(mLinkInfo);
+
+    if (setterArraySize == 0 ||
+        setterArraySize % setterElemSize)
+    {
+        webgl->ErrorInvalidValue("%s: expected an array of length a multiple of"
+                                 " %d, got an array of length %d.",
+                                 funcName, setterElemSize, setterArraySize);
+        return false;
+    }
+
+    /* GLES 2.0.25, Section 2.10, p38
+     *   When loading `N` elements starting at an arbitrary position `k` in a uniform
+     *   declared as an array, elements `k` through `k + N - 1` in the array will be
+     *   replaced with the new values. Values for any array element that exceeds the
+     *   highest array element index used, as reported by `GetActiveUniform`, will be
+     *   ignored by GL.
+     */
+    if (!mActiveInfo->mIsArray &&
+        setterArraySize != setterElemSize)
+    {
+        webgl->ErrorInvalidOperation("%s: expected an array of length exactly %d"
+                                     " (since this uniform is not an array"
+                                     " uniform), got an array of length %d.",
+                                     funcName, setterElemSize, setterArraySize);
+        return false;
+    }
+
+    return true;
+}
+
+bool
+WebGLUniformLocation::ValidateSamplerSetter(GLint value, WebGLContext* webgl,
+                                            const char* funcName) const
+{
+    MOZ_ASSERT(mLinkInfo);
+
+    if (mActiveInfo->mElemType != LOCAL_GL_SAMPLER_2D &&
+        mActiveInfo->mElemType != LOCAL_GL_SAMPLER_CUBE)
+    {
+        return true;
+    }
+
+    if (value >= 0 && value < (GLint)webgl->GLMaxTextureUnits())
+        return true;
+
+    webgl->ErrorInvalidValue("%s: This uniform location is a sampler, but %d is not a"
+                             " valid texture unit.",
+                             funcName, value);
+    return false;
 }
 
-NS_IMPL_CYCLE_COLLECTION(WebGLUniformLocation, mProgram)
+JS::Value
+WebGLUniformLocation::GetUniform(JSContext* js, WebGLContext* webgl) const
+{
+    MOZ_ASSERT(mLinkInfo);
+
+    uint8_t elemSize = mActiveInfo->mElemSize;
+    static const uint8_t kMaxElemSize = 16;
+    MOZ_ASSERT(elemSize <= kMaxElemSize);
+
+    GLuint prog = mLinkInfo->prog->mGLName;
+
+    gl::GLContext* gl = webgl->GL();
+    gl->MakeCurrent();
+
+    switch (mActiveInfo->mElemType) {
+    case LOCAL_GL_INT:
+    case LOCAL_GL_INT_VEC2:
+    case LOCAL_GL_INT_VEC3:
+    case LOCAL_GL_INT_VEC4:
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_CUBE:
+        {
+            GLint buffer[kMaxElemSize] = {0};
+            gl->fGetUniformiv(prog, mLoc, buffer);
+
+            if (elemSize == 1)
+                return JS::Int32Value(buffer[0]);
+
+            JSObject* obj = dom::Int32Array::Create(js, webgl, elemSize, buffer);
+            if (!obj) {
+                webgl->ErrorOutOfMemory("getUniform: out of memory");
+                return JS::NullValue();
+            }
+            return JS::ObjectOrNullValue(obj);
+        }
+
+    case LOCAL_GL_BOOL:
+    case LOCAL_GL_BOOL_VEC2:
+    case LOCAL_GL_BOOL_VEC3:
+    case LOCAL_GL_BOOL_VEC4:
+        {
+            GLint buffer[kMaxElemSize] = {0};
+            gl->fGetUniformiv(prog, mLoc, buffer);
+
+            if (elemSize == 1)
+                return JS::BooleanValue(buffer[0]);
+
+            bool boolBuffer[kMaxElemSize];
+            for (uint8_t i = 0; i < kMaxElemSize; i++)
+                boolBuffer[i] = buffer[i];
+
+            JS::RootedValue val(js);
+            // Be careful: we don't want to convert all of |uv|!
+            if (!dom::ToJSValue(js, boolBuffer, elemSize, &val)) {
+                webgl->ErrorOutOfMemory("getUniform: out of memory");
+                return JS::NullValue();
+            }
+            return val;
+        }
+
+    case LOCAL_GL_FLOAT:
+    case LOCAL_GL_FLOAT_VEC2:
+    case LOCAL_GL_FLOAT_VEC3:
+    case LOCAL_GL_FLOAT_VEC4:
+    case LOCAL_GL_FLOAT_MAT2:
+    case LOCAL_GL_FLOAT_MAT3:
+    case LOCAL_GL_FLOAT_MAT4:
+        {
+            GLfloat buffer[16] = {0.0f};
+            gl->fGetUniformfv(prog, mLoc, buffer);
+
+            if (elemSize == 1)
+                return JS::DoubleValue(buffer[0]);
+
+            JSObject* obj = dom::Float32Array::Create(js, webgl, elemSize, buffer);
+            if (!obj) {
+                webgl->ErrorOutOfMemory("getUniform: out of memory");
+                return JS::NullValue();
+            }
+            return JS::ObjectOrNullValue(obj);
+        }
+
+    default:
+        MOZ_CRASH("Invalid elemType.");
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+JSObject*
+WebGLUniformLocation::WrapObject(JSContext* js)
+{
+    return dom::WebGLUniformLocationBinding::Wrap(js, this);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLUniformLocation)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLUniformLocation, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLUniformLocation, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLUniformLocation.h
+++ b/dom/canvas/WebGLUniformLocation.h
@@ -1,55 +1,70 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 WEBGL_UNIFORM_LOCATION_H_
 #define WEBGL_UNIFORM_LOCATION_H_
 
+#include "GLDefs.h"
+#include "mozilla/WeakPtr.h"
+#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS
+#include "nsISupportsImpl.h" // NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING
+#include "nsWrapperCache.h"
 #include "WebGLObjectModel.h"
-#include "WebGLUniformInfo.h"
+
+struct JSContext;
 
 namespace mozilla {
+class WebGLActiveInfo;
+class WebGLContext;
+class WebGLProgram;
 
-class WebGLProgram;
+namespace webgl {
+struct LinkedProgramInfo;
+}
 
 class WebGLUniformLocation MOZ_FINAL
-    : public WebGLContextBoundObject
+    : public nsWrapperCache
+    , public WebGLContextBoundObject
 {
 public:
-    WebGLUniformLocation(WebGLContext* context, WebGLProgram* program,
-                         GLint location, const WebGLUniformInfo& info);
+    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLUniformLocation)
+    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLUniformLocation)
+
+    virtual JSObject* WrapObject(JSContext* js) MOZ_OVERRIDE;
+
+    WebGLContext* GetParentObject() const {
+        return mContext;
+    }
+
+
+    const WeakPtr<const webgl::LinkedProgramInfo> mLinkInfo;
+    const GLuint mLoc;
+    const WebGLActiveInfo* const mActiveInfo;
 
-    // needed for certain helper functions like ValidateObject.
-    // WebGLUniformLocation's can't be 'Deleted' in the WebGL sense.
+    WebGLUniformLocation(WebGLContext* webgl, const webgl::LinkedProgramInfo* linkInfo,
+                         GLuint loc, const WebGLActiveInfo* activeInfo);
+
+    bool ValidateForProgram(WebGLProgram* prog, WebGLContext* webgl,
+                            const char* funcName) const;
+    bool ValidateSamplerSetter(GLint value, WebGLContext* webgl,
+                               const char* funcName) const;
+    bool ValidateSizeAndType(uint8_t setterElemSize, GLenum setterType,
+                             WebGLContext* webgl, const char* funcName) const;
+    bool ValidateArrayLength(uint8_t setterElemSize, size_t setterArraySize,
+                             WebGLContext* webgl, const char* funcName) const;
+
+    JS::Value GetUniform(JSContext* js, WebGLContext* webgl) const;
+
+    // Needed for certain helper functions like ValidateObject.
+    // `WebGLUniformLocation`s can't be 'Deleted' in the WebGL sense.
     bool IsDeleted() const { return false; }
 
-    const WebGLUniformInfo& Info() const { return mInfo; }
-
-    WebGLProgram* Program() const { return mProgram; }
-    GLint Location() const { return mLocation; }
-    uint32_t ProgramGeneration() const { return mProgramGeneration; }
-    int ElementSize() const { return mElementSize; }
-
-    bool WrapObject(JSContext* aCx, JS::MutableHandle<JSObject*> aReflector);
-
-    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLUniformLocation)
-    NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(WebGLUniformLocation)
-
 protected:
-    ~WebGLUniformLocation() { }
-
-    // nsRefPtr, not WebGLRefPtr, so that we don't prevent the program from being explicitly deleted.
-    // we just want to avoid having a dangling pointer.
-    nsRefPtr<WebGLProgram> mProgram;
-
-    uint32_t mProgramGeneration;
-    GLint mLocation;
-    WebGLUniformInfo mInfo;
-    int mElementSize;
-    friend class WebGLProgram;
+    ~WebGLUniformLocation();
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_UNIFORM_LOCATION_H_
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGLValidateStrings.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "WebGLValidateStrings.h"
+
+#include "nsString.h"
+#include "WebGLContext.h"
+
+namespace mozilla {
+// The following code was taken from the WebKit WebGL implementation,
+// which can be found here:
+// http://trac.webkit.org/browser/trunk/Source/WebCore/html/canvas/WebGLRenderingContext.cpp?rev=93625#L121
+// Note that some modifications were done to adapt it to Mozilla.
+/****** BEGIN CODE TAKEN FROM WEBKIT ******/
+bool IsValidGLSLCharacter(char16_t c)
+{
+    // Printing characters are valid except " $ ` @ \ ' DEL.
+    if (c >= 32 && c <= 126 &&
+        c != '"' && c != '$' && c != '`' && c != '@' && c != '\\' && c != '\'')
+    {
+         return true;
+    }
+
+    // Horizontal tab, line feed, vertical tab, form feed, carriage return
+    // are also valid.
+    if (c >= 9 && c <= 13) {
+         return true;
+    }
+
+    return false;
+}
+
+void StripComments::process(char16_t c)
+{
+    if (isNewline(c)) {
+        // No matter what state we are in, pass through newlines
+        // so we preserve line numbers.
+        emit(c);
+
+        if (m_parseState != InMultiLineComment)
+            m_parseState = BeginningOfLine;
+
+        return;
+    }
+
+    char16_t temp = 0;
+    switch (m_parseState) {
+    case BeginningOfLine:
+        // If it's an ASCII space.
+        if (c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9))) {
+            emit(c);
+            break;
+        }
+
+        if (c == '#') {
+            m_parseState = InPreprocessorDirective;
+            emit(c);
+            break;
+        }
+
+        // Transition to normal state and re-handle character.
+        m_parseState = MiddleOfLine;
+        process(c);
+        break;
+
+    case MiddleOfLine:
+        if (c == '/' && peek(temp)) {
+            if (temp == '/') {
+                m_parseState = InSingleLineComment;
+                emit(' ');
+                advance();
+                break;
+            }
+
+            if (temp == '*') {
+                m_parseState = InMultiLineComment;
+                // Emit the comment start in case the user has
+                // an unclosed comment and we want to later
+                // signal an error.
+                emit('/');
+                emit('*');
+                advance();
+                break;
+            }
+        }
+
+        emit(c);
+        break;
+
+    case InPreprocessorDirective:
+        // No matter what the character is, just pass it
+        // through. Do not parse comments in this state. This
+        // might not be the right thing to do long term, but it
+        // should handle the #error preprocessor directive.
+        emit(c);
+        break;
+
+    case InSingleLineComment:
+        // The newline code at the top of this function takes care
+        // of resetting our state when we get out of the
+        // single-line comment. Swallow all other characters.
+        break;
+
+    case InMultiLineComment:
+        if (c == '*' && peek(temp) && temp == '/') {
+            emit('*');
+            emit('/');
+            m_parseState = MiddleOfLine;
+            advance();
+            break;
+        }
+
+        // Swallow all other characters. Unclear whether we may
+        // want or need to just emit a space per character to try
+        // to preserve column numbers for debugging purposes.
+        break;
+    }
+}
+
+/****** END CODE TAKEN FROM WEBKIT ******/
+
+bool
+ValidateGLSLString(const nsAString& string, WebGLContext* webgl, const char* funcName)
+{
+    for (size_t i = 0; i < string.Length(); ++i) {
+        if (!IsValidGLSLCharacter(string.CharAt(i))) {
+           webgl->ErrorInvalidValue("%s: String contains the illegal character '%d'",
+                                    funcName, string.CharAt(i));
+           return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+ValidateGLSLVariableName(const nsAString& name, WebGLContext* webgl, const char* funcName)
+{
+    if (name.IsEmpty())
+        return false;
+
+    const uint32_t maxSize = 256;
+    if (name.Length() > maxSize) {
+        webgl->ErrorInvalidValue("%s: Identifier is %d characters long, exceeds the"
+                                 " maximum allowed length of %d characters.",
+                                 funcName, name.Length(), maxSize);
+        return false;
+    }
+
+    if (!ValidateGLSLString(name, webgl, funcName))
+        return false;
+
+    nsString prefix1 = NS_LITERAL_STRING("webgl_");
+    nsString prefix2 = NS_LITERAL_STRING("_webgl_");
+
+    if (Substring(name, 0, prefix1.Length()).Equals(prefix1) ||
+        Substring(name, 0, prefix2.Length()).Equals(prefix2))
+    {
+        webgl->ErrorInvalidOperation("%s: String contains a reserved GLSL prefix.",
+                                     funcName);
+        return false;
+    }
+
+    return true;
+}
+
+} // namespace mozilla
--- a/dom/canvas/WebGLValidateStrings.h
+++ b/dom/canvas/WebGLValidateStrings.h
@@ -22,231 +22,135 @@
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef WEBGL_VALIDATE_STRINGS_H_
 #define WEBGL_VALIDATE_STRINGS_H_
 
-#include "WebGLContext.h"
+#include "nsString.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 
+class WebGLContext;
+
 // The following code was taken from the WebKit WebGL implementation,
 // which can be found here:
 // http://trac.webkit.org/browser/trunk/Source/WebCore/html/canvas/WebGLRenderingContext.cpp?rev=93625#L121
 // Note that some modifications were done to adapt it to Mozilla.
 /****** BEGIN CODE TAKEN FROM WEBKIT ******/
-    bool WebGLContext::ValidateGLSLCharacter(char16_t c)
+// Strips comments from shader text. This allows non-ASCII characters
+// to be used in comments without potentially breaking OpenGL
+// implementations not expecting characters outside the GLSL ES set.
+class StripComments {
+public:
+    explicit StripComments(const nsAString& str)
+        : m_parseState(BeginningOfLine)
+        , m_end(str.EndReading())
+        , m_current(str.BeginReading())
+        , m_position(0)
     {
-        // Printing characters are valid except " $ ` @ \ ' DEL.
-        if (c >= 32 && c <= 126 &&
-            c != '"' && c != '$' && c != '`' && c != '@' && c != '\\' && c != '\'')
-        {
-             return true;
-        }
-
-        // Horizontal tab, line feed, vertical tab, form feed, carriage return
-        // are also valid.
-        if (c >= 9 && c <= 13) {
-             return true;
-        }
-
-        return false;
+        m_result.SetLength(str.Length());
+        parse();
     }
 
-    // Strips comments from shader text. This allows non-ASCII characters
-    // to be used in comments without potentially breaking OpenGL
-    // implementations not expecting characters outside the GLSL ES set.
-    class StripComments {
-    public:
-        explicit StripComments(const nsAString& str)
-            : m_parseState(BeginningOfLine)
-            , m_end(str.EndReading())
-            , m_current(str.BeginReading())
-            , m_position(0)
-        {
-            m_result.SetLength(str.Length());
-            parse();
-        }
-
-        const nsTArray<char16_t>& result()
-        {
-            return m_result;
-        }
-
-        size_t length()
-        {
-            return m_position;
-        }
-
-    private:
-        bool hasMoreCharacters()
-        {
-            return (m_current < m_end);
-        }
-
-        void parse()
-        {
-            while (hasMoreCharacters()) {
-                process(current());
-                // process() might advance the position.
-                if (hasMoreCharacters())
-                    advance();
-            }
-        }
-
-        void process(char16_t);
+    const nsTArray<char16_t>& result()
+    {
+        return m_result;
+    }
 
-        bool peek(char16_t& character)
-        {
-            if (m_current + 1 >= m_end)
-                return false;
-            character = *(m_current + 1);
-            return true;
-        }
-
-        char16_t current()
-        {
-            //ASSERT(m_position < m_length);
-            return *m_current;
-        }
-
-        void advance()
-        {
-            ++m_current;
-        }
-
-        bool isNewline(char16_t character)
-        {
-            // Don't attempt to canonicalize newline related characters.
-            return (character == '\n' || character == '\r');
-        }
-
-        void emit(char16_t character)
-        {
-            m_result[m_position++] = character;
-        }
-
-        enum ParseState {
-            // Have not seen an ASCII non-whitespace character yet on
-            // this line. Possible that we might see a preprocessor
-            // directive.
-            BeginningOfLine,
-
-            // Have seen at least one ASCII non-whitespace character
-            // on this line.
-            MiddleOfLine,
-
-            // Handling a preprocessor directive. Passes through all
-            // characters up to the end of the line. Disables comment
-            // processing.
-            InPreprocessorDirective,
-
-            // Handling a single-line comment. The comment text is
-            // replaced with a single space.
-            InSingleLineComment,
+    size_t length()
+    {
+        return m_position;
+    }
 
-            // Handling a multi-line comment. Newlines are passed
-            // through to preserve line numbers.
-            InMultiLineComment
-        };
-
-        ParseState m_parseState;
-        const char16_t* m_end;
-        const char16_t* m_current;
-        size_t m_position;
-        nsTArray<char16_t> m_result;
-    };
-
-    void StripComments::process(char16_t c)
+private:
+    bool hasMoreCharacters()
     {
-        if (isNewline(c)) {
-            // No matter what state we are in, pass through newlines
-            // so we preserve line numbers.
-            emit(c);
-
-            if (m_parseState != InMultiLineComment)
-                m_parseState = BeginningOfLine;
-
-            return;
-        }
-
-        char16_t temp = 0;
-        switch (m_parseState) {
-        case BeginningOfLine:
-            // If it's an ASCII space.
-            if (c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9))) {
-                emit(c);
-                break;
-            }
-
-            if (c == '#') {
-                m_parseState = InPreprocessorDirective;
-                emit(c);
-                break;
-            }
-
-            // Transition to normal state and re-handle character.
-            m_parseState = MiddleOfLine;
-            process(c);
-            break;
+        return (m_current < m_end);
+    }
 
-        case MiddleOfLine:
-            if (c == '/' && peek(temp)) {
-                if (temp == '/') {
-                    m_parseState = InSingleLineComment;
-                    emit(' ');
-                    advance();
-                    break;
-                }
-
-                if (temp == '*') {
-                    m_parseState = InMultiLineComment;
-                    // Emit the comment start in case the user has
-                    // an unclosed comment and we want to later
-                    // signal an error.
-                    emit('/');
-                    emit('*');
-                    advance();
-                    break;
-                }
-            }
-
-            emit(c);
-            break;
-
-        case InPreprocessorDirective:
-            // No matter what the character is, just pass it
-            // through. Do not parse comments in this state. This
-            // might not be the right thing to do long term, but it
-            // should handle the #error preprocessor directive.
-            emit(c);
-            break;
-
-        case InSingleLineComment:
-            // The newline code at the top of this function takes care
-            // of resetting our state when we get out of the
-            // single-line comment. Swallow all other characters.
-            break;
-
-        case InMultiLineComment:
-            if (c == '*' && peek(temp) && temp == '/') {
-                emit('*');
-                emit('/');
-                m_parseState = MiddleOfLine;
+    void parse()
+    {
+        while (hasMoreCharacters()) {
+            process(current());
+            // process() might advance the position.
+            if (hasMoreCharacters())
                 advance();
-                break;
-            }
-
-            // Swallow all other characters. Unclear whether we may
-            // want or need to just emit a space per character to try
-            // to preserve column numbers for debugging purposes.
-            break;
         }
     }
 
+    void process(char16_t);
+
+    bool peek(char16_t& character)
+    {
+        if (m_current + 1 >= m_end)
+            return false;
+        character = *(m_current + 1);
+        return true;
+    }
+
+    char16_t current()
+    {
+        //ASSERT(m_position < m_length);
+        return *m_current;
+    }
+
+    void advance()
+    {
+        ++m_current;
+    }
+
+    bool isNewline(char16_t character)
+    {
+        // Don't attempt to canonicalize newline related characters.
+        return (character == '\n' || character == '\r');
+    }
+
+    void emit(char16_t character)
+    {
+        m_result[m_position++] = character;
+    }
+
+    enum ParseState {
+        // Have not seen an ASCII non-whitespace character yet on
+        // this line. Possible that we might see a preprocessor
+        // directive.
+        BeginningOfLine,
+
+        // Have seen at least one ASCII non-whitespace character
+        // on this line.
+        MiddleOfLine,
+
+        // Handling a preprocessor directive. Passes through all
+        // characters up to the end of the line. Disables comment
+        // processing.
+        InPreprocessorDirective,
+
+        // Handling a single-line comment. The comment text is
+        // replaced with a single space.
+        InSingleLineComment,
+
+        // Handling a multi-line comment. Newlines are passed
+        // through to preserve line numbers.
+        InMultiLineComment
+    };
+
+    ParseState m_parseState;
+    const char16_t* m_end;
+    const char16_t* m_current;
+    size_t m_position;
+    nsTArray<char16_t> m_result;
+};
+
 /****** END CODE TAKEN FROM WEBKIT ******/
 
+bool ValidateGLSLString(const nsAString& string, WebGLContext* webgl,
+                        const char* funcName);
+
+bool ValidateGLSLVariableName(const nsAString& name, WebGLContext* webgl,
+                              const char* funcName);
+
 } // namespace mozilla
 
 #endif // WEBGL_VALIDATE_STRINGS_H_
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -103,21 +103,23 @@ UNIFIED_SOURCES += [
     'WebGLFramebufferAttachable.cpp',
     'WebGLObjectModel.cpp',
     'WebGLProgram.cpp',
     'WebGLQuery.cpp',
     'WebGLRenderbuffer.cpp',
     'WebGLSampler.cpp',
     'WebGLShader.cpp',
     'WebGLShaderPrecisionFormat.cpp',
+    'WebGLShaderValidator.cpp',
     'WebGLSync.cpp',
     'WebGLTexelConversions.cpp',
     'WebGLTexture.cpp',
     'WebGLTransformFeedback.cpp',
     'WebGLUniformLocation.cpp',
+    'WebGLValidateStrings.cpp',
     'WebGLVertexArray.cpp',
     'WebGLVertexArrayFake.cpp',
     'WebGLVertexArrayGL.cpp',
 ]
 LOCAL_INCLUDES += [
     '/js/xpconnect/wrappers',
 ]
 
--- a/dom/canvas/test/webgl-conformance/conformance/misc/bad-arguments-test.html
+++ b/dom/canvas/test/webgl-conformance/conformance/misc/bad-arguments-test.html
@@ -63,25 +63,23 @@ var arguments = [
 
 var argument;
 
 function shouldBeEmptyString(command) {
   shouldBe(command, "''");
 }
 
 for (var i = 0; i < arguments.length; ++i) {
-  var func, func2, func3;
+  var func, func2;
   if (arguments[i].throws) {
     func = shouldThrow;
     func2 = shouldThrow;
-    func3 = shouldThrow;
   } else {
     func = shouldBeUndefined;
     func2 = shouldBeNull;
-    func3 = shouldBeEmptyString;
   }
   argument = arguments[i].value;
   func("context.compileShader(argument)");
   func("context.linkProgram(argument)");
   func("context.attachShader(program, argument)");
   func("context.attachShader(argument, shader)");
   func("context.detachShader(program, argument)");
   func("context.detachShader(argument, shader)");
@@ -93,25 +91,24 @@ for (var i = 0; i < arguments.length; ++
   func("context.bindRenderbuffer(context.RENDERBUFFER, argument)");
   func("context.bindTexture(context.TEXTURE_2D, argument)");
   func("context.framebufferRenderbuffer(context.FRAMEBUFFER, context.DEPTH_ATTACHMENT, context.RENDERBUFFER, argument)");
   func("context.framebufferTexture2D(context.FRAMEBUFFER, context.COLOR_ATTACHMENT0, context.TEXTURE_2D, argument, 0)");
   func("context.uniform2fv(argument, new Float32Array([0.0, 0.0]))");
   func("context.uniform2iv(argument, new Int32Array([0, 0]))");
   func("context.uniformMatrix2fv(argument, false, new Float32Array([0.0, 0.0, 0.0, 0.0]))");
 
+  func2("context.getProgramInfoLog(argument)");
   func2("context.getProgramParameter(argument, 0)");
+  func2("context.getShaderInfoLog(argument)");
   func2("context.getShaderParameter(argument, 0)");
+  func2("context.getShaderSource(argument)");
   func2("context.getUniform(argument, loc)");
   func2("context.getUniform(program, argument)");
   func2("context.getUniformLocation(argument, 'u_modelViewProjMatrix')");
-
-  func3("context.getProgramInfoLog(argument)");
-  func3("context.getShaderInfoLog(argument)");
-  func3("context.getShaderSource(argument)");
 }
 
 successfullyParsed = true;
 </script>
 
 <script>finishTest();</script>
 </body>
 </html>
--- a/dom/canvas/test/webgl-conformance/mochi-single.html
+++ b/dom/canvas/test/webgl-conformance/mochi-single.html
@@ -124,16 +124,17 @@ function GetExpectedTestFailSet() {
         failSet['conformance/textures/texture-mips.html'] = true;
         failSet['conformance/textures/texture-npot.html'] = true;
         failSet['conformance/textures/texture-size-cube-maps.html'] = true;
       } else {
         // Android 2.3 slaves.
         failSet['conformance/extensions/oes-texture-float.html'] = true;
         failSet['conformance/glsl/functions/glsl-function-sin.html'] = true;
         failSet['conformance/misc/object-deletion-behaviour.html'] = true;
+        failSet['conformance/programs/program-test.html'] = true;
         failSet['conformance/textures/tex-image-and-sub-image-2d-with-video.html'] = true;
         failSet['conformance/textures/texture-mips.html'] = true;
         failSet['conformance/textures/texture-npot.html'] = true;
       }
       break;
 
     case DriverInfo.OS.B2G:
       failSet['conformance/context/context-attributes-alpha-depth-stencil-antialias.html'] = true;
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -52,26 +52,27 @@ var gTestWindows = [
 var testWindow = null;
 var gTestIndex = 0;
 
 // TODO: if ever we remove these checks for XP and Lion, we should do the same
 // in dom/tests/mochitest/pointerlock/test_pointerlock-api.html, which uses the same pattern.
 const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
 const isOSXLion = navigator.userAgent.indexOf("Mac OS X 10.7") != -1;
 const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
+const isOSXYosemite = navigator.userAgent.indexOf("Mac OS X 10.10") != -1;
 
 function nextTest() {
   if (isWinXP) {
     todo(false, "Can't reliably run full-screen tests on Windows XP due to bug 704010");
     SimpleTest.finish();
     return;
   }
   if (testWindow) {
     testWindow.close();
-    if (isOSXLion || isOSXMtnLion) {
+    if (isOSXLion || isOSXMtnLion || isOSXYosemite) {
       // On OS X Lion, tests cause problems. Timeouts are a bad way to get around
       // the problem and may lead to future [orange], but they are the only option
       // at this point.
       SimpleTest.waitForFocus(function() { setTimeout(runNextTest, 3000); });
       return;
     }
   }
   runNextTest();
@@ -93,17 +94,17 @@ function runNextTest() {
     gTestIndex++;
   } else {
     SpecialPowers.clearUserPref("full-screen-api.enabled");
     SpecialPowers.clearUserPref("full-screen-api.allow-trusted-requests-only");
     SimpleTest.finish();
   }
 }
 
-if (isOSXLion || isOSXMtnLion) {
+if (isOSXLion || isOSXMtnLion || isOSXYosemite) {
   todo(false, "Can't reliably run full-screen tests on OS X (bug 900453 comment 18 & bug 802504)");
 } else {
   try {
     window.fullScreen = true;
   } catch (e) {
   }
   is(window.fullScreen, false, "Shouldn't be able to set window fullscreen from content");
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -577,16 +577,18 @@ bool MediaDecoderStateMachine::HaveEnoug
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeVideo()
 {
   AssertCurrentThreadInMonitor();
   return IsVideoDecoding() &&
          ((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) ||
+          (mState == DECODER_STATE_DECODING_FIRSTFRAME &&
+           IsVideoDecoding() && VideoQueue().GetSize() == 0) ||
           (!mMinimizePreroll && !HaveEnoughDecodedVideo()));
 }
 
 bool
 MediaDecoderStateMachine::NeedToSkipToNextKeyframe()
 {
   AssertCurrentThreadInMonitor();
   if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
@@ -676,16 +678,18 @@ MediaDecoderStateMachine::NeedToDecodeAu
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("NeedToDecodeAudio() isDec=%d decToTar=%d minPrl=%d seek=%d enufAud=%d",
              IsAudioDecoding(), mDecodeToSeekTarget, mMinimizePreroll,
              mState == DECODER_STATE_SEEKING,
              HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate));
 
   return IsAudioDecoding() &&
          ((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) ||
+          (mState == DECODER_STATE_DECODING_FIRSTFRAME &&
+           IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
           (!mMinimizePreroll &&
           !HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate) &&
           (mState != DECODER_STATE_SEEKING || mDecodeToSeekTarget)));
 }
 
 void
 MediaDecoderStateMachine::DecodeAudio()
 {
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -128,17 +128,17 @@ class TimestampedMediaByteRange;
 // Used to denote ranges of data which are cached.
 class MediaByteRange {
 public:
   MediaByteRange() : mStart(0), mEnd(0) {}
 
   MediaByteRange(int64_t aStart, int64_t aEnd)
     : mStart(aStart), mEnd(aEnd)
   {
-    NS_ASSERTION(mStart < mEnd, "Range should end after start!");
+    NS_ASSERTION(mStart <= mEnd, "Range should end after start!");
   }
 
   explicit MediaByteRange(TimestampedMediaByteRange& aByteRange);
 
   bool IsNull() const {
     return mStart == 0 && mEnd == 0;
   }
 
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -263,9 +263,15 @@ MediaSourceDecoder::SetCDMProxy(CDMProxy
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mReader->SetCDMProxy(aProxy);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 #endif
 
+bool
+MediaSourceDecoder::IsActiveReader(MediaDecoderReader* aReader)
+{
+  return mReader->IsActiveReader(aReader);
+}
+
 } // namespace mozilla
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -69,16 +69,20 @@ public:
   void PrepareReaderInitialization();
 
 #ifdef MOZ_EME
   virtual nsresult SetCDMProxy(CDMProxy* aProxy) MOZ_OVERRIDE;
 #endif
 
   MediaSourceReader* GetReader() { return mReader; }
 
+  // Returns true if aReader is a currently active audio or video
+  // reader in this decoders MediaSourceReader.
+  bool IsActiveReader(MediaDecoderReader* aReader);
+
 private:
   // The owning MediaSource holds a strong reference to this decoder, and
   // calls Attach/DetachMediaSource on this decoder to set and clear
   // mMediaSource.
   dom::MediaSource* mMediaSource;
   nsRefPtr<MediaSourceReader> mReader;
 
   // Protected by GetReentrantMonitor()
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -903,9 +903,16 @@ MediaSourceReader::SetCDMProxy(CDMProxy*
     nsresult rv = mTrackBuffers[i]->SetCDMProxy(aProxy);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 #endif
 
+bool
+MediaSourceReader::IsActiveReader(MediaDecoderReader* aReader)
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  return aReader == mVideoReader.get() || aReader == mAudioReader.get();
+}
+
 } // namespace mozilla
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -133,16 +133,19 @@ public:
   nsresult SetCDMProxy(CDMProxy* aProxy);
 #endif
 
   virtual bool IsAsync() const MOZ_OVERRIDE {
     return (!mAudioReader || mAudioReader->IsAsync()) &&
            (!mVideoReader || mVideoReader->IsAsync());
   }
 
+  // Returns true if aReader is a currently active audio or video
+  bool IsActiveReader(MediaDecoderReader* aReader);
+
 private:
   // Switch the current audio/video reader to the reader that
   // contains aTarget (or up to aError after target). Both
   // aTarget and aError are in microseconds.
   bool SwitchAudioReader(int64_t aTarget, int64_t aError = 0);
   bool SwitchVideoReader(int64_t aTarget, int64_t aError = 0);
   void RequestAudioDataComplete(int64_t aTime);
   void RequestAudioDataFailed(nsresult aResult);
--- a/dom/media/mediasource/ResourceQueue.h
+++ b/dom/media/mediasource/ResourceQueue.h
@@ -100,29 +100,46 @@ public:
   void AppendItem(const uint8_t* aData, uint32_t aLength) {
     mLogicalLength += aLength;
     Push(new ResourceItem(aData, aLength));
   }
 
   // Tries to evict at least aSizeToEvict from the queue up until
   // aOffset. Returns amount evicted.
   uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict) {
+    SBR_DEBUG("ResourceQueue(%p)::Evict(aOffset=%llu, aSizeToEvict=%u)",
+              this, aOffset, aSizeToEvict);
+    return EvictBefore(std::min(aOffset, (uint64_t)aSizeToEvict));
+  }
+
+  uint32_t EvictBefore(uint64_t aOffset) {
+    SBR_DEBUG("ResourceQueue(%p)::EvictBefore(%llu)", this, aOffset);
     uint32_t evicted = 0;
     while (ResourceItem* item = ResourceAt(0)) {
-      if (item->mData.Length() + mOffset > aOffset) {
+      SBR_DEBUG("ResourceQueue(%p)::EvictBefore item=%p length=%d offset=%llu",
+                this, item, item->mData.Length(), mOffset);
+      if (item->mData.Length() + mOffset >= aOffset) {
         break;
       }
       mOffset += item->mData.Length();
       evicted += item->mData.Length();
-      SBR_DEBUGV("ResourceQueue(%p)::Evict(%llu, %u) removed chunk length=%u",
-                 this, aOffset, aSizeToEvict, item->mData.Length());
       delete PopFront();
-      if (aSizeToEvict && evicted >= aSizeToEvict) {
-        break;
-      }
+    }
+    return evicted;
+  }
+
+  uint32_t EvictAll() {
+    SBR_DEBUG("ResourceQueue(%p)::EvictAll()", this);
+    uint32_t evicted = 0;
+    while (ResourceItem* item = ResourceAt(0)) {
+      SBR_DEBUG("ResourceQueue(%p)::EvictAll item=%p length=%d offset=%llu",
+                this, item, item->mData.Length(), mOffset);
+      mOffset += item->mData.Length();
+      evicted += item->mData.Length();
+      delete PopFront();
     }
     return evicted;
   }
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
     // Calculate the size of the internal deque.
     size_t size = nsDeque::SizeOfExcludingThis(aMallocSizeOf);
 
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -382,24 +382,27 @@ SourceBuffer::PrepareAppend(ErrorResult&
   // Eviction uses a byte threshold. If the buffer is greater than the
   // number of bytes then data is evicted. The time range for this
   // eviction is reported back to the media source. It will then
   // evict data before that range across all SourceBuffers it knows
   // about.
   // TODO: Make the eviction threshold smaller for audio-only streams.
   // TODO: Drive evictions off memory pressure notifications.
   // TODO: Consider a global eviction threshold  rather than per TrackBuffer.
-  bool evicted = mTrackBuffer->EvictData(mEvictionThreshold);
+  double newBufferStartTime = 0.0;
+  bool evicted =
+    mTrackBuffer->EvictData(mMediaSource->GetDecoder()->GetCurrentTime(),
+                            mEvictionThreshold, &newBufferStartTime);
   if (evicted) {
     MSE_DEBUG("SourceBuffer(%p)::AppendData Evict; current buffered start=%f",
               this, GetBufferedStart());
 
     // We notify that we've evicted from the time range 0 through to
     // the current start point.
-    mMediaSource->NotifyEvicted(0.0, GetBufferedStart());
+    mMediaSource->NotifyEvicted(0.0, newBufferStartTime);
   }
 
   // TODO: Test buffer full flag.
   return true;
 }
 
 double
 SourceBuffer::GetBufferedStart()
--- a/dom/media/mediasource/SourceBufferResource.cpp
+++ b/dom/media/mediasource/SourceBufferResource.cpp
@@ -167,34 +167,43 @@ SourceBufferResource::ReadFromCache(char
   mOffset = oldOffset; // ReadFromCache isn't supposed to affect the seek position.
   NS_ENSURE_SUCCESS(rv, rv);
 
   // ReadFromCache return failure if not all the data is cached.
   return bytesRead == aCount ? NS_OK : NS_ERROR_FAILURE;
 }
 
 uint32_t
-SourceBufferResource::EvictData(uint32_t aThreshold)
+SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold)
 {
-  SBR_DEBUG("SourceBufferResource(%p)::EvictData(aThreshold=%u)", this, aThreshold);
+  SBR_DEBUG("SourceBufferResource(%p)::EvictData(aPlaybackOffset=%llu,"
+            "aThreshold=%u)", this, aPlaybackOffset, aThreshold);
   ReentrantMonitorAutoEnter mon(mMonitor);
-  return mInputBuffer.Evict(mOffset, aThreshold);
+  return mInputBuffer.Evict(aPlaybackOffset, aThreshold);
 }
 
 void
 SourceBufferResource::EvictBefore(uint64_t aOffset)
 {
   SBR_DEBUG("SourceBufferResource(%p)::EvictBefore(aOffset=%llu)", this, aOffset);
   ReentrantMonitorAutoEnter mon(mMonitor);
   // If aOffset is past the current playback offset we don't evict.
   if (aOffset < mOffset) {
-    mInputBuffer.Evict(aOffset, 0);
+    mInputBuffer.EvictBefore(aOffset);
   }
 }
 
+uint32_t
+SourceBufferResource::EvictAll()
+{
+  SBR_DEBUG("SourceBufferResource(%p)::EvictAll()", this);
+  ReentrantMonitorAutoEnter mon(mMonitor);
+  return mInputBuffer.EvictAll();
+}
+
 void
 SourceBufferResource::AppendData(const uint8_t* aData, uint32_t aLength)
 {
   SBR_DEBUG("SourceBufferResource(%p)::AppendData(aData=%p, aLength=%u)", this, aData, aLength);
   ReentrantMonitorAutoEnter mon(mMonitor);
   mInputBuffer.AppendItem(aData, aLength);
   mon.NotifyAll();
 }
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -110,21 +110,24 @@ public:
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   // Used by SourceBuffer.
   void AppendData(const uint8_t* aData, uint32_t aLength);
   void Ended();
   // Remove data from resource if it holds more than the threshold
   // number of bytes. Returns amount evicted.
-  uint32_t EvictData(uint32_t aThreshold);
+  uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold);
 
   // Remove data from resource before the given offset.
   void EvictBefore(uint64_t aOffset);
 
+  // Remove all data from the resource
+  uint32_t EvictAll();
+
   // Returns the amount of data currently retained by this resource.
   int64_t GetSize() {
     ReentrantMonitorAutoEnter mon(mMonitor);
     return mInputBuffer.GetLength() - mInputBuffer.GetOffset();
   }
 
 #if defined(DEBUG)
   void Dump(const char* aPath) {
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -28,16 +28,21 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__))
 #define MSE_API(...) PR_LOG(GetMediaSourceAPILog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #define MSE_DEBUG(...)
 #define MSE_DEBUGV(...)
 #define MSE_API(...)
 #endif
 
+// Time in seconds to substract from the current time when deciding the
+// time point to evict data before in a decoder. This is used to help
+// precent evicting the current playback point.
+#define MSE_EVICT_THRESHOLD_TIME 2.0
+
 namespace mozilla {
 
 TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mParentDecoder(aParentDecoder)
   , mType(aType)
   , mLastStartTimestamp(0)
   , mLastTimestampOffset(0)
   , mShutdown(false)
@@ -235,87 +240,128 @@ public:
     nsRefPtr<dom::TimeRanges> second = new dom::TimeRanges();
     aSecond->GetBuffered(second);
 
     return first->GetStartTime() == second->GetStartTime();
   }
 };
 
 bool
-TrackBuffer::EvictData(uint32_t aThreshold)
+TrackBuffer::EvictData(double aPlaybackTime,
+                       uint32_t aThreshold,
+                       double* aBufferStartTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
 
+  if (!mCurrentDecoder) {
+    return false;
+  }
+
   int64_t totalSize = 0;
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     totalSize += mDecoders[i]->GetResource()->GetSize();
   }
 
   int64_t toEvict = totalSize - aThreshold;
   if (toEvict <= 0 || mInitializedDecoders.IsEmpty()) {
     return false;
   }
 
-  // Get a list of initialized decoders, sorted by their start times.
+  // Get a list of initialized decoders
   nsTArray<SourceBufferDecoder*> decoders;
   decoders.AppendElements(mInitializedDecoders);
-  decoders.Sort(DecoderSorter());
 
   // First try to evict data before the current play position, starting
   // with the earliest time.
   uint32_t i = 0;
-  for (; i < decoders.Length(); ++i) {
-    MSE_DEBUG("TrackBuffer(%p)::EvictData decoder=%u threshold=%u toEvict=%lld",
-              this, i, aThreshold, toEvict);
-    toEvict -= decoders[i]->GetResource()->EvictData(toEvict);
-    if (!decoders[i]->GetResource()->GetSize() &&
-        decoders[i] != mCurrentDecoder) {
-      RemoveDecoder(decoders[i]);
+  bool pastCurrentDecoder = true;
+  for (; i < decoders.Length() && toEvict > 0; ++i) {
+    nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+    decoders[i]->GetBuffered(buffered);
+    bool onCurrent = decoders[i] == mCurrentDecoder;
+    if (onCurrent) {
+      pastCurrentDecoder = false;
     }
-    if (toEvict <= 0 || decoders[i] == mCurrentDecoder) {
-      break;
+
+    MSE_DEBUG("TrackBuffer(%p)::EvictData decoder=%u/%u threshold=%u "
+              "toEvict=%lld current=%s pastCurrent=%s",
+              this, i, decoders.Length(), aThreshold, toEvict,
+              onCurrent ? "true" : "false",
+              pastCurrentDecoder ? "true" : "false");
+
+    if (pastCurrentDecoder
+        && !mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+      // Remove data from older decoders than the current one.
+      // Don't remove data if it is currently active.
+      MSE_DEBUG("TrackBuffer(%p)::EvictData evicting all before start "
+                "bufferedStart=%f bufferedEnd=%f aPlaybackTime=%f size=%lld",
+                this, buffered->GetStartTime(), buffered->GetEndTime(),
+                aPlaybackTime, decoders[i]->GetResource()->GetSize());
+      toEvict -= decoders[i]->GetResource()->EvictAll();
+    } else {
+      // To ensure we don't evict data past the current playback position
+      // we apply a threshold of a few seconds back and evict data up to
+      // that point.
+      if (aPlaybackTime > MSE_EVICT_THRESHOLD_TIME) {
+        double time = aPlaybackTime - MSE_EVICT_THRESHOLD_TIME;
+        int64_t playbackOffset = decoders[i]->ConvertToByteOffset(time);
+        MSE_DEBUG("TrackBuffer(%p)::EvictData evicting some bufferedEnd=%f"
+                  "aPlaybackTime=%f time=%f, playbackOffset=%lld size=%lld",
+                  this, buffered->GetEndTime(), aPlaybackTime, time,
+                  playbackOffset, decoders[i]->GetResource()->GetSize());
+        if (playbackOffset > 0) {
+          toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset,
+                                                           toEvict);
+        }
+      }
     }
   }
 
-  // If we still need to evict more, then try to evict entire decoders,
-  // starting from the end.
-  if (toEvict > 0) {
-    uint32_t end = i;
-    MOZ_ASSERT(decoders[end] == mCurrentDecoder);
+  // Remove decoders that have no data in them
+  for (i = 0; i < decoders.Length(); ++i) {
+    nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
+    decoders[i]->GetBuffered(buffered);
+    MSE_DEBUG("TrackBuffer(%p):EvictData maybe remove empty decoders=%d "
+              "size=%lld start=%f end=%f",
+              this, i, decoders[i]->GetResource()->GetSize(),
+              buffered->GetStartTime(), buffered->GetEndTime());
+    if (decoders[i] == mCurrentDecoder
+        || mParentDecoder->IsActiveReader(decoders[i]->GetReader())) {
+      continue;
+    }
 
-    for (i = decoders.Length() - 1; i > end; --i) {
-      MSE_DEBUG("TrackBuffer(%p)::EvictData removing entire decoder=%u from end toEvict=%lld",
-                this, i, toEvict);
-      // TODO: We could implement forward-eviction within a decoder and
-      // be able to evict within the current decoder.
-      toEvict -= decoders[i]->GetResource()->GetSize();
+    if (decoders[i]->GetResource()->GetSize() == 0 ||
+        buffered->GetStartTime() < 0.0 ||
+        buffered->GetEndTime() < 0.0) {
+      MSE_DEBUG("TrackBuffer(%p):EvictData remove empty decoders=%d", this, i);
       RemoveDecoder(decoders[i]);
-      if (toEvict <= 0) {
-        break;
-      }
     }
   }
-  return toEvict < (totalSize - aThreshold);
+
+  bool evicted = toEvict < (totalSize - aThreshold);
+  if (evicted) {
+    nsRefPtr<TimeRanges> ranges = new TimeRanges();
+    mCurrentDecoder->GetBuffered(ranges);
+    *aBufferStartTime = std::max(0.0, ranges->GetStartTime());
+  }
+
+  return evicted;
 }
 
 void
 TrackBuffer::EvictBefore(double aTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
     int64_t endOffset = mInitializedDecoders[i]->ConvertToByteOffset(aTime);
     if (endOffset > 0) {
       MSE_DEBUG("TrackBuffer(%p)::EvictBefore decoder=%u offset=%lld", this, i, endOffset);
       mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset);
-      if (!mInitializedDecoders[i]->GetResource()->GetSize() &&
-          mInitializedDecoders[i] != mCurrentDecoder) {
-        RemoveDecoder(mInitializedDecoders[i]);
-      }
     }
   }
 }
 
 double
 TrackBuffer::Buffered(dom::TimeRanges* aRanges)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
@@ -641,16 +687,19 @@ private:
 
 void
 TrackBuffer::RemoveDecoder(SourceBufferDecoder* aDecoder)
 {
   RefPtr<nsIRunnable> task = new DelayedDispatchToMainThread(aDecoder);
 
   {
     ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+    // There should be no other references to the decoder. Assert that
+    // we aren't using it in the MediaSourceReader.
+    MOZ_ASSERT(!mParentDecoder->IsActiveReader(aDecoder->GetReader()));
     mInitializedDecoders.RemoveElement(aDecoder);
     mDecoders.RemoveElement(aDecoder);
 
     if (mCurrentDecoder == aDecoder) {
       DiscardDecoder();
     }
   }
   aDecoder->GetReader()->GetTaskQueue()->Dispatch(task);
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -35,17 +35,27 @@ public:
   TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType);
 
   nsRefPtr<ShutdownPromise> Shutdown();
 
   // Append data to the current decoder.  Also responsible for calling
   // NotifyDataArrived on the decoder to keep buffered range computation up
   // to date.  Returns false if the append failed.
   bool AppendData(const uint8_t* aData, uint32_t aLength, int64_t aTimestampOffset /* microseconds */);
-  bool EvictData(uint32_t aThreshold);
+
+  // Evicts data held in the current decoders SourceBufferResource from the
+  // start of the buffer through to aPlaybackTime. aThreshold is used to
+  // bound the data being evicted. It will not evict more than aThreshold
+  // bytes. aBufferStartTime contains the new start time of the current
+  // decoders buffered data after the eviction. Returns true if data was
+  // evicted.
+  bool EvictData(double aPlaybackTime, uint32_t aThreshold, double* aBufferStartTime);
+
+  // Evicts data held in all the decoders SourceBufferResource from the start
+  // of the buffer through to aTime.
   void EvictBefore(double aTime);
 
   // Returns the highest end time of all of the buffered ranges in the
   // decoders managed by this TrackBuffer, and returns the union of the
   // decoders buffered ranges in aRanges. This may be called on any thread.
   double Buffered(dom::TimeRanges* aRanges);
 
   // Mark the current decoder's resource as ended, clear mCurrentDecoder and
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -1483,17 +1483,18 @@ function PeerConnectionWrapper(label, co
 
   this.constraints = [ ];
   this.offerOptions = {};
   this.streams = [ ];
   this.mediaCheckers = [ ];
 
   this.dataChannels = [ ];
 
-  this.onAddStreamFired = false;
+  this.onAddStreamAudioCounter = 0;
+  this.onAddStreamVideoCounter = 0;
   this.addStreamCallbacks = {};
 
   this._local_ice_candidates = [];
   this._remote_ice_candidates = [];
   this._ice_candidates_to_add = [];
   this.holdIceCandidates = true;
   this.endOfTrickleIce = false;
   this.localRequiresTrickleIce = false;
@@ -1529,25 +1530,25 @@ function PeerConnectionWrapper(label, co
   /**
    * Callback for native peer connection 'onaddstream' events.
    *
    * @param {Object} event
    *        Event data which includes the stream to be added
    */
   this._pc.onaddstream = function (event) {
     info(self + ": 'onaddstream' event fired for " + JSON.stringify(event.stream));
-    // TODO: remove this once Bugs 998552 and 998546 are closed
-    self.onAddStreamFired = true;
 
     var type = '';
     if (event.stream.getAudioTracks().length > 0) {
       type = 'audio';
+      self.onAddStreamAudioCounter += event.stream.getAudioTracks().length;
     }
     if (event.stream.getVideoTracks().length > 0) {
       type += 'video';
+      self.onAddStreamVideoCounter += event.stream.getVideoTracks().length;
     }
     self.attachMedia(event.stream, type, 'remote');
 
     Object.keys(self.addStreamCallbacks).forEach(function(name) {
       info(self + " calling addStreamCallback " + name);
       self.addStreamCallbacks[name]();
     });
    };
@@ -2246,22 +2247,18 @@ PeerConnectionWrapper.prototype = {
   /**
    * Checks that we are getting the media tracks we expect.
    *
    * @param {object} constraintsRemote
    *        The media constraints of the local and remote peer connection object
    */
   checkMediaTracks : function PCW_checkMediaTracks(constraintsRemote, onSuccess) {
     var self = this;
-    var addStreamTimeout = null;
 
     function _checkMediaTracks(constraintsRemote, onSuccess) {
-      if (addStreamTimeout !== null) {
-        clearTimeout(addStreamTimeout);
-      }
 
       var localConstraintAudioTracks =
         self.countAudioTracksInMediaConstraint(self.constraints);
       var localStreams = self._pc.getLocalStreams();
       var localAudioTracks = self.countAudioTracksInStreams(localStreams, false);
       is(localAudioTracks, localConstraintAudioTracks, self + ' has ' +
         localAudioTracks + ' local audio tracks');
 
@@ -2288,33 +2285,45 @@ PeerConnectionWrapper.prototype = {
     }
 
     // we have to do this check as the onaddstream never fires if the remote
     // stream has no track at all!
     var expectedRemoteTracks =
       self.countAudioTracksInMediaConstraint(constraintsRemote) +
       self.countVideoTracksInMediaConstraint(constraintsRemote);
 
-    // TODO: remove this once Bugs 998552 and 998546 are closed
-    if ((self.onAddStreamFired) || (expectedRemoteTracks == 0)) {
-      _checkMediaTracks(constraintsRemote, onSuccess);
-    } else {
-      info(self + " checkMediaTracks() got called before onAddStream fired");
-      // we rely on the outer mochitest timeout to catch the case where
+    // TODO: this whole counting of streams should be replaced with comparing
+    //       media stream objects IDs and what we got in the SDP (bug 1089798)
+    function _compareReceivedAndExpectedTracks(constraintsRemote, onSuccess) {
+      var receivedRemoteTracks =
+        self.onAddStreamAudioCounter + self.onAddStreamVideoCounter;
+
+      if (receivedRemoteTracks === expectedRemoteTracks) {
+        _checkMediaTracks(constraintsRemote, onSuccess);
+      } else if (receivedRemoteTracks > expectedRemoteTracks) {
+        ok(false, "Received more streams " + receivedRemoteTracks +
+            " then expected " + expectedRemoteTracks);
+        _checkMediaTracks(constraintsRemote, onSuccess);
+      } else {
+        info("Still waiting for more remote streams to arrive (" +
+            receivedRemoteTracks + " vs " + expectedRemoteTracks + ")");
+      }
+    }
+
+    if (expectedRemoteTracks > (self.onAddStreamAudioCounter +
+        self.onAddStreamVideoCounter)) {
+      // This installs a callback handler for every time onaddstrem fires.
+      // We rely on the outer mochitest timeout to catch the case where
       // onaddstream never fires
       self.addStreamCallbacks.checkMediaTracks = function() {
-        _checkMediaTracks(constraintsRemote, onSuccess);
+        _compareReceivedAndExpectedTracks(constraintsRemote, onSuccess);
       };
-      addStreamTimeout = setTimeout(function () {
-        ok(self.onAddStreamFired, self + " checkMediaTracks() timed out waiting for onaddstream event to fire");
-        if (!self.onAddStreamFired) {
-          onSuccess();
-        }
-      }, 60000);
     }
+    _compareReceivedAndExpectedTracks(constraintsRemote, onSuccess);
+
   },
 
   verifySdp : function PCW_verifySdp(desc, expectedType, offerConstraintsList,
       offerOptions, trickleIceCallback) {
     info("Examining this SessionDescription: " + JSON.stringify(desc));
     info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
     info("offerOptions: " + JSON.stringify(offerOptions));
     ok(desc, "SessionDescription is not null");
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -182,37 +182,16 @@ NfcContentHelper.prototype = {
     cpmm.sendAsyncMessage("NFC:Transceive", {
       requestId: requestId,
       sessionToken: sessionToken,
       technology: technology,
       command: command
     });
   },
 
-  connect: function connect(techType, sessionToken, callback) {
-    let requestId = callback.getCallbackId();
-    this._requestMap[requestId] = callback;
-
-    cpmm.sendAsyncMessage("NFC:Connect", {
-      requestId: requestId,
-      sessionToken: sessionToken,
-      techType: techType
-    });
-  },
-
-  close: function close(sessionToken, callback) {
-    let requestId = callback.getCallbackId();
-    this._requestMap[requestId] = callback;
-
-    cpmm.sendAsyncMessage("NFC:Close", {
-      requestId: requestId,
-      sessionToken: sessionToken
-    });
-  },
-
   sendFile: function sendFile(data, sessionToken, callback) {
     let requestId = callback.getCallbackId();
     this._requestMap[requestId] = callback;
 
     cpmm.sendAsyncMessage("NFC:SendFile", {
       requestId: requestId,
       sessionToken: sessionToken,
       blob: data.blob
@@ -279,31 +258,29 @@ NfcContentHelper.prototype = {
         this.handle(subject.key, subject.value);
       }
     }
   },
 
   // nsIMessageListener
   receiveMessage: function receiveMessage(message) {
     DEBUG && debug("Message received: " + JSON.stringify(message));
-    let result = message.json;
+    let result = message.data;
 
     switch (message.name) {
       case "NFC:ReadNDEFResponse":
         this.handleReadNDEFResponse(result);
         break;
       case "NFC:CheckP2PRegistrationResponse":
         this.handleCheckP2PRegistrationResponse(result);
         break;
       case "NFC:TransceiveResponse":
         this.handleTransceiveResponse(result);
         break;
-      case "NFC:ConnectResponse": // Fall through.
-      case "NFC:CloseResponse":
-      case "NFC:WriteNDEFResponse":
+      case "NFC:WriteNDEFResponse": // Fall through.
       case "NFC:MakeReadOnlyResponse":
       case "NFC:FormatResponse":
       case "NFC:NotifySendFileStatusResponse":
       case "NFC:ChangeRFStateResponse":
         this.handleGeneralResponse(result);
         break;
       case "NFC:DOMEvent":
         switch (result.event) {
@@ -350,17 +327,17 @@ NfcContentHelper.prototype = {
     switch (name) {
       case NFC.SETTING_NFC_DEBUG:
         DEBUG = result;
         updateDebug();
         break;
     }
   },
 
-  handleGeneralResponse: function handleReadNDEFResponse(result) {
+  handleGeneralResponse: function handleGeneralResponse(result) {
     let requestId = result.requestId;
     let callback = this._requestMap[requestId];
     if (!callback) {
       debug("not firing message " + result.type + " for id: " + requestId);
       return;
     }
     delete this._requestMap[requestId];
 
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -56,18 +56,16 @@ const NFC_CID =
 
 const NFC_IPC_MSG_ENTRIES = [
   { permission: null,
     messages: ["NFC:AddEventListener",
                "NFC:QueryInfo"] },
 
   { permission: "nfc",
     messages: ["NFC:ReadNDEF",
-               "NFC:Connect",
-               "NFC:Close",
                "NFC:WriteNDEF",
                "NFC:MakeReadOnly",
                "NFC:Format",
                "NFC:Transceive"] },
 
   { permission: "nfc-share",
     messages: ["NFC:SendFile",
                "NFC:RegisterPeerReadyTarget",
@@ -509,19 +507,17 @@ Nfc.prototype = {
      case "ChangeRFStateResponse":
         this.sendNfcResponse(message);
 
         if (!message.errorMsg) {
           this.rfState = message.rfState;
           gMessageManager.onRFStateChange(this.rfState);
         }
         break;
-      case "ConnectResponse": // Fall through.
-      case "CloseResponse":
-      case "ReadNDEFResponse":
+      case "ReadNDEFResponse": // Fall through.
       case "MakeReadOnlyResponse":
       case "FormatResponse":
       case "TransceiveResponse":
       case "WriteNDEFResponse":
         this.sendNfcResponse(message);
         break;
       default:
         throw new Error("Don't know about this message type: " + message.type);
@@ -574,22 +570,16 @@ Nfc.prototype = {
         this.sendToNfcService("makeReadOnly", message.data);
         break;
       case "NFC:Format":
         this.sendToNfcService("format", message.data);
         break;
       case "NFC:Transceive":
         this.sendToNfcService("transceive", message.data);
         break;
-      case "NFC:Connect":
-        this.sendToNfcService("connect", message.data);
-        break;
-      case "NFC:Close":
-        this.sendToNfcService("close", message.data);
-        break;
       case "NFC:SendFile":
         // Chrome process is the arbitrator / mediator between
         // system app (content process) that issued nfc 'sendFile' operation
         // and system app that handles the system message :
         // 'nfc-manager-send-file'. System app subsequently handover's
         // the data to alternate carrier's (BT / WiFi) 'sendFile' interface.
 
         // Notify system app to initiate BT send file operation
--- a/dom/nfc/gonk/NfcGonkMessage.h
+++ b/dom/nfc/gonk/NfcGonkMessage.h
@@ -3,22 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NfcGonkMessage_h
 #define NfcGonkMessage_h
 
 namespace mozilla {
 
 #define NFCD_MAJOR_VERSION 1
-#define NFCD_MINOR_VERSION 19
+#define NFCD_MINOR_VERSION 20
 
 enum NfcRequest {
   ChangeRFStateReq = 0,
-  ConnectReq,
-  CloseReq,
   ReadNDEFReq,
   WriteNDEFReq,
   MakeReadOnlyReq,
   FormatReq,
   TransceiveReq,
 };
 
 enum NfcResponse {
--- a/dom/nfc/gonk/NfcMessageHandler.cpp
+++ b/dom/nfc/gonk/NfcMessageHandler.cpp
@@ -18,27 +18,23 @@ using namespace mozilla;
 using namespace mozilla::dom;
 
 static const char* kChangeRFStateRequest = "changeRFState";
 static const char* kReadNDEFRequest = "readNDEF";
 static const char* kWriteNDEFRequest = "writeNDEF";
 static const char* kMakeReadOnlyRequest = "makeReadOnly";
 static const char* kFormatRequest = "format";
 static const char* kTransceiveRequest = "transceive";
-static const char* kConnectRequest = "connect";
-static const char* kCloseRequest = "close";
 
 static const char* kChangeRFStateResponse = "ChangeRFStateResponse";
 static const char* kReadNDEFResponse = "ReadNDEFResponse";
 static const char* kWriteNDEFResponse = "WriteNDEFResponse";
 static const char* kMakeReadOnlyResponse = "MakeReadOnlyResponse";
 static const char* kFormatResponse = "FormatResponse";
 static const char* kTransceiveResponse = "TransceiveResponse";
-static const char* kConnectResponse = "ConnectResponse";
-static const char* kCloseResponse = "CloseResponse";
 
 static const char* kInitializedNotification = "InitializedNotification";
 static const char* kTechDiscoveredNotification = "TechDiscoveredNotification";
 static const char* kTechLostNotification = "TechLostNotification";
 static const char* kHCIEventTransactionNotification =
                      "HCIEventTransactionNotification";
 
 bool
@@ -58,22 +54,16 @@ NfcMessageHandler::Marshall(Parcel& aPar
     result = MakeReadOnlyRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::MakeReadOnlyReq);
   } else if (!strcmp(type, kFormatRequest)) {
     result = FormatRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::FormatReq);
   } else if (!strcmp(type, kTransceiveRequest)) {
     result = TransceiveRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::TransceiveReq);
-  } else if (!strcmp(type, kConnectRequest)) {
-    result = ConnectRequest(aParcel, aOptions);
-    mPendingReqQueue.AppendElement(NfcRequest::ConnectReq);
-  } else if (!strcmp(type, kCloseRequest)) {
-    result = CloseRequest(aParcel, aOptions);
-    mPendingReqQueue.AppendElement(NfcRequest::CloseReq);
   } else {
     result = false;
   }
 
   return result;
 }
 
 bool
@@ -129,22 +119,16 @@ NfcMessageHandler::GeneralResponse(const
       type = kWriteNDEFResponse;
       break;
     case NfcRequest::MakeReadOnlyReq:
       type = kMakeReadOnlyResponse;
       break;
     case NfcRequest::FormatReq:
       type = kFormatResponse;
       break;
-    case NfcRequest::ConnectReq:
-      type = kConnectResponse;
-      break;
-    case NfcRequest::CloseReq:
-      type = kCloseResponse;
-      break;
     default:
       NMH_LOG("Nfcd, unknown general response %d", pendingReq);
       return false;
   }
 
   aOptions.mType = NS_ConvertUTF8toUTF16(type);
   aOptions.mErrorCode = aParcel.readInt32();
   aOptions.mSessionId = aParcel.readInt32();
@@ -264,35 +248,16 @@ NfcMessageHandler::TransceiveRequest(Par
   void* data = aParcel.writeInplace(length);
   memcpy(data, aOptions.mCommand.Elements(), length);
 
   mRequestIdQueue.AppendElement(aOptions.mRequestId);
   return true;
 }
 
 bool
-NfcMessageHandler::ConnectRequest(Parcel& aParcel, const CommandOptions& aOptions)
-{
-  aParcel.writeInt32(NfcRequest::ConnectReq);
-  aParcel.writeInt32(aOptions.mSessionId);
-  aParcel.writeInt32(aOptions.mTechType);
-  mRequestIdQueue.AppendElement(aOptions.mRequestId);
-  return true;
-}
-
-bool
-NfcMessageHandler::CloseRequest(Parcel& aParcel, const CommandOptions& aOptions)
-{
-  aParcel.writeInt32(NfcRequest::CloseReq);
-  aParcel.writeInt32(aOptions.mSessionId);
-  mRequestIdQueue.AppendElement(aOptions.mRequestId);
-  return true;
-}
-
-bool
 NfcMessageHandler::InitializeNotification(const Parcel& aParcel, EventOptions& aOptions)
 {
   aOptions.mType = NS_ConvertUTF8toUTF16(kInitializedNotification);
   aOptions.mStatus = aParcel.readInt32();
   aOptions.mMajorVersion = aParcel.readInt32();
   aOptions.mMinorVersion = aParcel.readInt32();
 
   if (aOptions.mMajorVersion != NFCD_MAJOR_VERSION ||
--- a/dom/nfc/gonk/NfcMessageHandler.h
+++ b/dom/nfc/gonk/NfcMessageHandler.h
@@ -29,18 +29,16 @@ private:
   bool ChangeRFStateResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool ReadNDEFRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool ReadNDEFResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool WriteNDEFRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool MakeReadOnlyRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool FormatRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool TransceiveRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool TransceiveResponse(const android::Parcel& aParcel, EventOptions& aOptions);
-  bool ConnectRequest(android::Parcel& aParcel, const CommandOptions& options);
-  bool CloseRequest(android::Parcel& aParcel, const CommandOptions& options);
 
   bool InitializeNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool TechDiscoveredNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool TechLostNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool HCIEventTransactionNotification(const android::Parcel& aParcel, EventOptions& aOptions);
 
   bool ReadNDEFMessage(const android::Parcel& aParcel, EventOptions& aOptions);
   bool WriteNDEFMessage(android::Parcel& aParcel, const CommandOptions& aOptions);
--- a/dom/nfc/nsINfcContentHelper.idl
+++ b/dom/nfc/nsINfcContentHelper.idl
@@ -97,17 +97,17 @@ interface nsINfcRequestCallback : nsISup
 
   void notifySuccessWithNDEFRecords(in nsIVariant records);
 
   void notifySuccessWithByteArray(in nsIVariant array);
 
   void notifyError(in DOMString errorMsg);
 };
 
-[scriptable, uuid(0f8aae32-9920-491e-a197-8995941d54df)]
+[scriptable, uuid(b5194ae8-d5d5-482f-a73f-dd0d755a1972)]
 interface nsINfcContentHelper : nsISupports
 {
   void init(in nsIDOMWindow window);
 
   /**
    * Read current NDEF data on the tag.
    *
    * @param sessionToken
@@ -176,44 +176,16 @@ interface nsINfcContentHelper : nsISuppo
    *
    */
   void transceive(in DOMString sessionToken,
                   in DOMString technology,
                   in nsIVariant command,
                   in nsINfcRequestCallback callback);
 
   /**
-   * Enable I/O operations to the tag
-   *
-   * @param techType
-   *        Interface to a technology in a Tag
-   *
-   * @param sessionToken
-   *        Current token
-   *
-   * @param callback
-   *        Called when request is finished
-   */
-  void connect(in unsigned long techType,
-               in DOMString sessionToken,
-               in nsINfcRequestCallback callback);
-
-  /**
-   * Disable I/O operations to the tag
-   *
-   * @param sessionToken
-   *        Current token
-   *
-   * @param callback
-   *        Called when request is finished
-   */
-  void close(in DOMString sessionToken,
-             in nsINfcRequestCallback callback);
-
-  /**
    * Get current RF state.
    */
   DOMString queryRFState();
 
   /**
    * Initiate send file operation.
    *
    * @param blob
--- a/dom/nfc/nsNfc.js
+++ b/dom/nfc/nsNfc.js
@@ -20,22 +20,24 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "appsService",
                                    "@mozilla.org/AppsService;1",
                                    "nsIAppsService");
 
 function NfcCallback(aWindow) {
+  this._window = aWindow;
   this.initDOMRequestHelper(aWindow, null);
   this._createPromise();
 }
 NfcCallback.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
+  _window: null,
   promise: null,
   _requestId: null,
 
   _createPromise: function _createPromise() {
     this.promise = this.createPromise((aResolve, aReject) => {
       this._requestId = btoa(this.getPromiseResolverId({
         resolve: aResolve,
         reject: aReject
@@ -75,27 +77,27 @@ NfcCallback.prototype = {
   },
 
   notifySuccessWithByteArray: function notifySuccessWithByteArray(aArray) {
     let resolver = this.takePromiseResolver(atob(this._requestId));
     if (!resolver) {
       debug("can not find promise resolver for id: " + this._requestId);
       return;
     }
-    resolver.resolve(aArray);
+    resolver.resolve(Cu.cloneInto(aArray, this._window));
   },
 
   notifyError: function notifyError(aErrorMsg) {
     let resolver = this.takePromiseResolver(atob(this._requestId));
     if (!resolver) {
       debug("can not find promise resolver for id: " + this._requestId +
            ", errormsg: " + aErrorMsg);
       return;
     }
-    resolver.reject(aErrorMsg);
+    resolver.reject(new this._window.Error(aErrorMsg));
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
                                          Ci.nsIObserver,
                                          Ci.nsINfcRequestCallback]),
 };
 
 // Should be mapped to the NFCTagType defined in MozNFCTag.webidl.
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -200,22 +200,38 @@ protected:
 
     LinkThenableCallables(cx, resolveFunc, rejectFunc);
 
     ErrorResult rv;
 
     JS::Rooted<JSObject*> rootedThenable(cx, mThenable);
 
     mThen->Call(rootedThenable, resolveFunc, rejectFunc, rv,
-                CallbackObject::eRethrowExceptions);
+                CallbackObject::eRethrowExceptions,
+                mPromise->Compartment());
 
     rv.WouldReportJSException();
-    if (rv.IsJSException()) {
+    if (rv.Failed()) {
       JS::Rooted<JS::Value> exn(cx);
-      rv.StealJSException(cx, &exn);
+      if (rv.IsJSException()) {
+        // Enter the compartment of mPromise before stealing the JS exception,
+        // since the StealJSException call will use the current compartment for
+        // a security check that determines how much of the stack we're allowed
+        // to see and we'll be exposing that stack to consumers of mPromise.
+        JSAutoCompartment ac(cx, mPromise->GlobalJSObject());
+        rv.StealJSException(cx, &exn);
+      } else {
+        // Convert the ErrorResult to a JS exception object that we can reject
+        // ourselves with.  This will be exactly the exception that would get
+        // thrown from a binding method whose ErrorResult ended up with
+        // whatever is on "rv" right now.
+        JSAutoCompartment ac(cx, mPromise->GlobalJSObject());
+        DebugOnly<bool> conversionResult = ToJSValue(cx, rv, &exn);
+        MOZ_ASSERT(conversionResult);
+      }
 
       bool couldMarkAsCalled = MarkAsCalledIfNotCalledBefore(cx, resolveFunc);
 
       // If we could mark as called, neither of the callbacks had been called
       // when the exception was thrown. So we can reject the Promise.
       if (couldMarkAsCalled) {
         bool ok = JS_WrapValue(cx, &exn);
         MOZ_ASSERT(ok);
@@ -557,32 +573,46 @@ Promise::CallInitFunction(const GlobalOb
   JS::Rooted<JSObject*> rejectFunc(cx,
                                    CreateFunction(cx, aGlobal.Get(), this,
                                                   PromiseCallback::Reject));
   if (!rejectFunc) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
-  aInit.Call(resolveFunc, rejectFunc, aRv, CallbackObject::eRethrowExceptions);
+  aInit.Call(resolveFunc, rejectFunc, aRv, CallbackObject::eRethrowExceptions,
+             Compartment());
   aRv.WouldReportJSException();
 
   if (aRv.IsJSException()) {
     JS::Rooted<JS::Value> value(cx);
-    aRv.StealJSException(cx, &value);
+    { // scope for ac
+      // Enter the compartment of our global before stealing the JS exception,
+      // since the StealJSException call will use the current compartment for
+      // a security check that determines how much of the stack we're allowed
+      // to see, and we'll be exposing that stack to consumers of this promise.
+      JSAutoCompartment ac(cx, GlobalJSObject());
+      aRv.StealJSException(cx, &value);
+    }
 
     // we want the same behavior as this JS implementation:
     // function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }}
     if (!JS_WrapValue(cx, &value)) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
       return;
     }
 
     MaybeRejectInternal(cx, value);
   }
+
+  // Else aRv is an error.  We _could_ reject ourselves with that error, but
+  // we're just going to propagate aRv out to the binding code, which will then
+  // throw us away and create a new promise rejected with the error on aRv.  So
+  // there's no need to worry about rejecting ourselves here; the bindings
+  // will do the right thing.
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Resolve(const GlobalObject& aGlobal,
                  JS::Handle<JS::Value> aValue, ErrorResult& aRv)
 {
   // If a Promise was passed, just return it.
   if (aValue.isObject()) {
@@ -931,16 +961,28 @@ Promise::AppendNativeHandler(PromiseNati
     new NativePromiseCallback(aRunnable, Resolved);
 
   nsRefPtr<PromiseCallback> rejectCb =
     new NativePromiseCallback(aRunnable, Rejected);
 
   AppendCallbacks(resolveCb, rejectCb);
 }
 
+JSObject*
+Promise::GlobalJSObject() const
+{
+  return mGlobal->GetGlobalJSObject();
+}
+
+JSCompartment*
+Promise::Compartment() const
+{
+  return js::GetObjectCompartment(GlobalJSObject());
+}
+
 void
 Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
                          PromiseCallback* aRejectCallback)
 {
   if (aResolveCallback) {
     mResolveCallbacks.AppendElement(aResolveCallback);
   }
 
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -14,16 +14,17 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/WeakPtr.h"
 #include "nsWrapperCache.h"
 #include "nsAutoPtr.h"
 #include "js/TypeDecls.h"
+#include "jspubtd.h"
 
 #include "mozilla/dom/workers/bindings/WorkerFeature.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
@@ -171,16 +172,20 @@ public:
       const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   static already_AddRefed<Promise>
   Race(const GlobalObject& aGlobal,
        const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   void AppendNativeHandler(PromiseNativeHandler* aRunnable);
 
+  JSObject* GlobalJSObject() const;
+
+  JSCompartment* Compartment() const;
+
 protected:
   // Do NOT call this unless you're Promise::Create.  I wish we could enforce
   // that from inside this class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
 
   virtual ~Promise();
 
   // Queue an async microtask to current main or worker thread.
--- a/dom/promise/PromiseCallback.cpp
+++ b/dom/promise/PromiseCallback.cpp
@@ -200,28 +200,47 @@ WrapperPromiseCallback::Call(JSContext* 
     NS_WARNING("Failed to wrap value into the right compartment.");
     return;
   }
 
   ErrorResult rv;
 
   // PromiseReactionTask step 6
   JS::Rooted<JS::Value> retValue(aCx);
-  mCallback->Call(value, &retValue, rv, CallbackObject::eRethrowExceptions);
+  mCallback->Call(value, &retValue, rv, CallbackObject::eRethrowExceptions,
+                  mNextPromise->Compartment());
 
   rv.WouldReportJSException();
 
   // PromiseReactionTask step 7
-  if (rv.Failed() && rv.IsJSException()) {
+  if (rv.Failed()) {
     JS::Rooted<JS::Value> value(aCx);
-    rv.StealJSException(aCx, &value);
+    if (rv.IsJSException()) {
+      { // scope for ac
+        // Enter the compartment of mNextPromise before stealing the JS
+        // exception, since the StealJSException call will use the current
+        // compartment for a security check that determines how much of the
+        // stack we're allowed to see and we'll be exposing that stack to
+        // consumers of mPromise.
+        JSAutoCompartment ac(aCx, mNextPromise->GlobalJSObject());
+        rv.StealJSException(aCx, &value);
+      }
 
-    if (!JS_WrapValue(aCx, &value)) {
-      NS_WARNING("Failed to wrap value into the right compartment.");
-      return;
+      if (!JS_WrapValue(aCx, &value)) {
+        NS_WARNING("Failed to wrap value into the right compartment.");
+        return;
+      }
+    } else {
+      // Convert the ErrorResult to a JS exception object that we can reject
+      // ourselves with.  This will be exactly the exception that would get
+      // thrown from a binding method whose ErrorResult ended up with whatever
+      // is on "rv" right now.
+      JSAutoCompartment ac(aCx, mNextPromise->GlobalJSObject());
+      DebugOnly<bool> conversionResult = ToJSValue(aCx, rv, &value);
+      MOZ_ASSERT(conversionResult);
     }
 
     mNextPromise->RejectInternal(aCx, value);
     return;
   }
 
   // If the return value is the same as the promise itself, throw TypeError.
   if (retValue.isObject()) {
--- a/dom/tests/mochitest/pointerlock/test_pointerlock-api.html
+++ b/dom/tests/mochitest/pointerlock/test_pointerlock-api.html
@@ -65,16 +65,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         var gTestWindow = null;
         var gTestIndex = 0;
 
         // TODO: if ever we remove these checks for XP and Lion, we should do the same
         // in dom/html/test/test_fullscreen-api.html, which uses the same pattern.
         const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
         const isOSXLion = navigator.userAgent.indexOf("Mac OS X 10.7") != -1;
         const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
+        const isOSXYosemite = navigator.userAgent.indexOf("Mac OS X 10.10") != -1;
         const isWin8 = navigator.userAgent.indexOf("Windows NT 6.2") != -1;
 
         function finish() {
           SpecialPowers.clearUserPref("full-screen-api.enabled");
           SpecialPowers.clearUserPref("full-screen-api.allow-trusted-requests-only");
           SpecialPowers.removeFullscreenAllowed(document)
           SimpleTest.finish();
         }
@@ -85,18 +86,18 @@ https://bugzilla.mozilla.org/show_bug.cg
             finish();
             return;
           }
           if (isWin8) {
             todo(false, "Can't reliably run full-screen + pointer lock tests on Windows 8");
             finish();
             return;
           }
-          if (isOSXLion || isOSXMtnLion) {
-            todo(false, "Can't reliably run full-screen tests on OS X Lion or Mountain Lion, see bug 744125");
+          if (isOSXLion || isOSXMtnLion || isOSXYosemite) {
+            todo(false, "Can't reliably run full-screen tests on OS X Lion or Mountain Lion or Yosemite, see bug 744125");
             finish();
             return;
           }
           if (gTestWindow) {
             gTestWindow.close();
           }
           SimpleTest.waitForFocus(runNextTest);
         }
--- a/dom/webidl/TestInterfaceJS.webidl
+++ b/dom/webidl/TestInterfaceJS.webidl
@@ -50,9 +50,20 @@ interface TestInterfaceJS {
   void testSequenceUnion((sequence<DOMString> or DOMString) arg);
 
   // Tests for exception-throwing behavior
   [Throws]
   void testThrowDOMError();
 
   [Throws]
   void testThrowDOMException();
+
+  // Tests for promise-rejection behavior
+  Promise<void> testPromiseWithThrowingChromePromiseInit();
+  Promise<void> testPromiseWithThrowingContentPromiseInit(PromiseInit func);
+  Promise<void> testPromiseWithDOMExceptionThrowingPromiseInit();
+  Promise<void> testPromiseWithThrowingChromeThenFunction();
+  Promise<void> testPromiseWithThrowingContentThenFunction(AnyCallback func);
+  Promise<void> testPromiseWithDOMExceptionThrowingThenFunction();
+  Promise<void> testPromiseWithThrowingChromeThenable();
+  Promise<void> testPromiseWithThrowingContentThenable(object thenable);
+  Promise<void> testPromiseWithDOMExceptionThrowingThenable();
 };
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -755,18 +755,16 @@ XULDocument::AddBroadcastListenerFor(Ele
     rv = nsContentUtils::CheckSameOrigin(this, &aListener);
 
     if (NS_FAILED(rv)) {
         aRv.Throw(rv);
         return;
     }
 
     static const PLDHashTableOps gOps = {
-        PL_DHashAllocTable,
-        PL_DHashFreeTable,
         PL_DHashVoidPtrKeyStub,
         PL_DHashMatchEntryStub,
         PL_DHashMoveEntryStub,
         ClearBroadcasterMapEntry,
         nullptr
     };
 
     if (! mBroadcasterMap) {
--- a/embedding/components/commandhandler/nsCommandParams.cpp
+++ b/embedding/components/commandhandler/nsCommandParams.cpp
@@ -11,18 +11,16 @@
 
 #include "nsCommandParams.h"
 #include "mozilla/HashFunctions.h"
 
 using namespace mozilla;
 
 const PLDHashTableOps nsCommandParams::sHashOps =
 {
-  PL_DHashAllocTable,
-  PL_DHashFreeTable,
   HashKey,
   HashMatchEntry,
   HashMoveEntry,
   HashClearEntry
 };
 
 NS_IMPL_ISUPPORTS(nsCommandParams, nsICommandParams)
 
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -897,17 +897,21 @@ mozInlineSpellChecker::SpellCheckAfterEd
 // mozInlineSpellChecker::SpellCheckRange
 //
 //    Spellchecks all the words in the given range.
 //    Supply a nullptr range and this will check the entire editor.
 
 nsresult
 mozInlineSpellChecker::SpellCheckRange(nsIDOMRange* aRange)
 {
-  NS_ENSURE_TRUE(mSpellCheck, NS_ERROR_NOT_INITIALIZED);
+  if (!mSpellCheck) {
+    NS_WARN_IF_FALSE(mPendingSpellCheck,
+                     "Trying to spellcheck, but checking seems to be disabled");
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   mozInlineSpellStatus status(this);
   nsRange* range = static_cast<nsRange*>(aRange);
   nsresult rv = status.InitForRange(range);
   NS_ENSURE_SUCCESS(rv, rv);
   return ScheduleSpellCheck(status);
 }
 
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -2010,17 +2010,17 @@ public:
     }
 
     void fGetShaderSource(GLint obj, GLsizei maxLength, GLsizei* length, GLchar* source) {
         BEFORE_GL_CALL;
         mSymbols.fGetShaderSource(obj, maxLength, length, source);
         AFTER_GL_CALL;
     }
 
-    void fShaderSource(GLuint shader, GLsizei count, const GLchar** strings, const GLint* lengths) {
+    void fShaderSource(GLuint shader, GLsizei count, const GLchar* const* strings, const GLint* lengths) {
         BEFORE_GL_CALL;
         mSymbols.fShaderSource(shader, count, strings, lengths);
         AFTER_GL_CALL;
     }
 
 private:
     void raw_fBindFramebuffer(GLenum target, GLuint framebuffer) {
         BEFORE_GL_CALL;
--- a/gfx/gl/GLContextSymbols.h
+++ b/gfx/gl/GLContextSymbols.h
@@ -303,17 +303,17 @@ struct GLContextSymbols
     typedef void (GLAPIENTRY * PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint* param);
     PFNGLGETSHADERIVPROC fGetShaderiv;
     typedef void (GLAPIENTRY * PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
     PFNGLGETSHADERINFOLOGPROC fGetShaderInfoLog;
     typedef void (GLAPIENTRY * PFNGETSHADERPRECISIONFORMAT) (GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision);
     PFNGETSHADERPRECISIONFORMAT fGetShaderPrecisionFormat;
     typedef void (GLAPIENTRY * PFNGLGETSHADERSOURCEPROC) (GLint obj, GLsizei maxLength, GLsizei* length, GLchar* source);
     PFNGLGETSHADERSOURCEPROC fGetShaderSource;
-    typedef void (GLAPIENTRY * PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar** strings, const GLint* lengths);
+    typedef void (GLAPIENTRY * PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* const* strings, const GLint* lengths);
     PFNGLSHADERSOURCEPROC fShaderSource;
 
     typedef void (GLAPIENTRY * PFNGLBINDFRAMEBUFFER) (GLenum target, GLuint framebuffer);
     PFNGLBINDFRAMEBUFFER fBindFramebuffer;
     typedef void (GLAPIENTRY * PFNGLBINDRENDERBUFFER) (GLenum target, GLuint renderbuffer);
     PFNGLBINDRENDERBUFFER fBindRenderbuffer;
     typedef GLenum (GLAPIENTRY * PFNGLCHECKFRAMEBUFFERSTATUS) (GLenum target);
     PFNGLCHECKFRAMEBUFFERSTATUS fCheckFramebufferStatus;
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -617,18 +617,16 @@ FT2FontFamily::AddFacesToFontList(Infall
 #define CACHE_KEY "font.cached-list"
 
 class FontNameCache {
 public:
     FontNameCache()
         : mWriteNeeded(false)
     {
         mOps = (PLDHashTableOps) {
-            PL_DHashAllocTable,
-            PL_DHashFreeTable,
             StringHash,
             HashMatchEntry,
             MoveEntry,
             PL_DHashClearEntryStub,
             nullptr
         };
 
         PL_DHashTableInit(&mMap, &mOps, sizeof(FNCMapEntry), 0);
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -232,26 +232,25 @@ private:
 
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
 
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000);
   DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520);
-  DECL_GFX_PREF(Live, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
+  DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.decodeondraw",                ImageMemDecodeOnDraw, bool, false);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
-  DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield",         ImageMemMaxMSBeforeYield, uint32_t, 400);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mozsamplesize.enabled",           ImageMozSampleSizeEnabled, bool, false);
-  DECL_GFX_PREF(Live, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
+  DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-video.enabled",            AsyncVideoEnabled, bool, true);
   DECL_GFX_PREF(Once, "layers.async-video-oop.enabled",        AsyncVideoOOPEnabled, bool, true);
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -33,17 +33,17 @@ GetBMPLog()
   return sBMPLog;
 }
 #endif
 
 // Convert from row (1..height) to absolute line (0..height-1)
 #define LINE(row) ((mBIH.height < 0) ? (-mBIH.height - (row)) : ((row) - 1))
 #define PIXEL_OFFSET(row, col) (LINE(row) * mBIH.width + col)
 
-nsBMPDecoder::nsBMPDecoder(RasterImage& aImage)
+nsBMPDecoder::nsBMPDecoder(RasterImage* aImage)
   : Decoder(aImage)
   , mPos(0)
   , mLOH(WIN_V3_HEADER_LENGTH)
   , mNumColors(0)
   , mColors(nullptr)
   , mRow(nullptr)
   , mRowBytes(0)
   , mCurLine(1)  // Otherwise decoder will never start.
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -18,17 +18,17 @@ namespace image {
 class RasterImage;
 
 /// Decoder for BMP-Files, as used by Windows and OS/2
 
 class nsBMPDecoder : public Decoder
 {
 public:
 
-    explicit nsBMPDecoder(RasterImage& aImage);
+    explicit nsBMPDecoder(RasterImage* aImage);
     ~nsBMPDecoder();
 
     // Specifies whether or not the BMP file will contain alpha data
     // If set to true and the BMP is 32BPP, the alpha data will be
     // retrieved from the 4th byte of image data per pixel
     void SetUseAlphaData(bool useAlphaData);
 
     // Obtains the bits per pixel from the internal BIH header
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -63,17 +63,17 @@ namespace image {
     mGIFStruct.state = (s);            \
   PR_END_MACRO
 
 // Get a 16-bit value stored in little-endian format
 #define GETINT16(p)   ((p)[1]<<8|(p)[0])
 //////////////////////////////////////////////////////////////////////
 // GIF Decoder Implementation
 
-nsGIFDecoder2::nsGIFDecoder2(RasterImage& aImage)
+nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage)
   : Decoder(aImage)
   , mCurrentRow(-1)
   , mLastFlushedRow(-1)
   , mOldColor(0)
   , mCurrentFrameIndex(-1)
   , mCurrentPass(0)
   , mLastFlushedPass(0)
   , mGIFOpen(false)
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -18,17 +18,17 @@ class RasterImage;
 
 //////////////////////////////////////////////////////////////////////
 // nsGIFDecoder2 Definition
 
 class nsGIFDecoder2 : public Decoder
 {
 public:
 
-  explicit nsGIFDecoder2(RasterImage& aImage);
+  explicit nsGIFDecoder2(RasterImage* aImage);
   ~nsGIFDecoder2();
 
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
 
 private:
   // These functions will be called when the decoder has a decoded row,
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -53,17 +53,17 @@ nsICODecoder::GetNumColors()
     default:
       numColors = (uint16_t)-1;
     }
   }
   return numColors;
 }
 
 
-nsICODecoder::nsICODecoder(RasterImage& aImage)
+nsICODecoder::nsICODecoder(RasterImage* aImage)
  : Decoder(aImage)
 {
   mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
   mIsPNG = false;
   mRow = nullptr;
   mOldLine = mCurLine = 1; // Otherwise decoder will never start
 }
 
@@ -244,17 +244,17 @@ nsICODecoder::WriteInternal(const char* 
     aCount -= 2;
   }
 
   if (mNumIcons == 0) {
     return; // Nothing to do.
   }
 
   uint16_t colorDepth = 0;
-  nsIntSize prefSize = mImage.GetRequestedResolution();
+  nsIntSize prefSize = mImage->GetRequestedResolution();
   if (prefSize.width == 0 && prefSize.height == 0) {
     prefSize.SizeTo(PREFICONSIZE, PREFICONSIZE);
   }
 
   // A measure of the difference in size between the entry we've found
   // and the requested size. We will choose the smallest image that is
   // >= requested size (i.e. we assume it's better to downscale a larger
   // icon than to upscale a smaller one).
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -18,17 +18,17 @@ namespace mozilla {
 namespace image {
 
 class RasterImage;
 
 class nsICODecoder : public Decoder
 {
 public:
 
-  explicit nsICODecoder(RasterImage& aImage);
+  explicit nsICODecoder(RasterImage* aImage);
   virtual ~nsICODecoder();
 
   // Obtains the width of the icon directory entry
   uint32_t GetRealWidth() const
   {
     return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth;
   }
 
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -10,17 +10,17 @@
 #include "nsRect.h"
 #include "nsError.h"
 #include "RasterImage.h"
 #include <algorithm>
 
 namespace mozilla {
 namespace image {
 
-nsIconDecoder::nsIconDecoder(RasterImage& aImage)
+nsIconDecoder::nsIconDecoder(RasterImage* aImage)
  : Decoder(aImage),
    mWidth(-1),
    mHeight(-1),
    mPixBytesRead(0),
    mState(iconStateStart)
 {
   // Nothing to do
 }
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -33,17 +33,17 @@ class RasterImage;
 //
 //
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsIconDecoder : public Decoder
 {
 public:
 
-  explicit nsIconDecoder(RasterImage& aImage);
+  explicit nsIconDecoder(RasterImage* aImage);
   virtual ~nsIconDecoder();
 
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
 
   uint8_t mWidth;
   uint8_t mHeight;
   uint32_t mPixBytesRead;
   uint32_t mState;
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -79,17 +79,17 @@ METHODDEF(boolean) fill_input_buffer (j_
 METHODDEF(void) skip_input_data (j_decompress_ptr jd, long num_bytes);
 METHODDEF(void) term_source (j_decompress_ptr jd);
 METHODDEF(void) my_error_exit (j_common_ptr cinfo);
 
 // Normal JFIF markers can't have more bytes than this.
 #define MAX_JPEG_MARKER_LENGTH  (((uint32_t)1 << 16) - 1)
 
 
-nsJPEGDecoder::nsJPEGDecoder(RasterImage& aImage,
+nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage,
                              Decoder::DecodeStyle aDecodeStyle)
  : Decoder(aImage)
  , mDecodeStyle(aDecodeStyle)
 {
   mState = JPEG_HEADER;
   mReading = true;
   mImageData = nullptr;
 
@@ -232,17 +232,17 @@ nsJPEGDecoder::WriteInternal(const char*
 
       // Step 3: read file parameters with jpeg_read_header()
       if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) {
         PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
                ("} (JPEG_SUSPENDED)"));
         return; // I/O suspension
       }
 
-      int sampleSize = mImage.GetRequestedSampleSize();
+      int sampleSize = mImage->GetRequestedSampleSize();
       if (sampleSize > 0) {
         mInfo.scale_num = 1;
         mInfo.scale_denom = sampleSize;
       }
 
       // Used to set up image size so arrays can be allocated
       jpeg_calc_output_dimensions(&mInfo);
 
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -47,17 +47,17 @@ typedef enum {
 } jstate;
 
 class RasterImage;
 struct Orientation;
 
 class nsJPEGDecoder : public Decoder
 {
 public:
-  nsJPEGDecoder(RasterImage& aImage, Decoder::DecodeStyle aDecodeStyle);
+  nsJPEGDecoder(RasterImage* aImage, Decoder::DecodeStyle aDecodeStyle);
   virtual ~nsJPEGDecoder();
 
   virtual void InitInternal() MOZ_OVERRIDE;
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
 
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
   void NotifyDone();
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -102,17 +102,17 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn
   }
 }
 #endif
 
 // First 8 bytes of a PNG file
 const uint8_t
 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
-nsPNGDecoder::nsPNGDecoder(RasterImage& aImage)
+nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
  : Decoder(aImage),
    mPNG(nullptr), mInfo(nullptr),
    mCMSLine(nullptr), interlacebuf(nullptr),
    mInProfile(nullptr), mTransform(nullptr),
    mHeaderBytesRead(0), mCMSMode(0),
    mChannels(0), mFrameIsHidden(false),
    mDisablePremultipliedAlpha(false),
    mNumFrames(0)
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -19,17 +19,17 @@
 
 namespace mozilla {
 namespace image {
 class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
 public:
-  explicit nsPNGDecoder(RasterImage& aImage);
+  explicit nsPNGDecoder(RasterImage* aImage);
   virtual ~nsPNGDecoder();
 
   virtual void InitInternal() MOZ_OVERRIDE;
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
 
   void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
                    int32_t width, int32_t height,
--- a/image/src/DecodePool.cpp
+++ b/image/src/DecodePool.cpp
@@ -7,17 +7,16 @@
 
 #include <algorithm>
 
 #include "mozilla/ClearOnShutdown.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
-#include "nsProxyRelease.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prsystem.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 #include "gfxPrefs.h"
@@ -37,112 +36,98 @@ namespace image {
 
 class NotifyProgressWorker : public nsRunnable
 {
 public:
   /**
    * Called by the DecodePool when it's done some significant portion of
    * decoding, so that progress can be recorded and notifications can be sent.
    */
-  static void Dispatch(RasterImage* aImage)
+  static void Dispatch(RasterImage* aImage,
+                       Progress aProgress,
+                       const nsIntRect& aInvalidRect,
+                       uint32_t aFlags)
   {
-    nsCOMPtr<nsIRunnable> worker = new NotifyProgressWorker(aImage);
+    MOZ_ASSERT(aImage);
+
+    nsCOMPtr<nsIRunnable> worker =
+      new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags);
     NS_DispatchToMainThread(worker);
   }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
-    ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
-
-    mImage->FinishedSomeDecoding(ShutdownReason::DONE);
-
+    mImage->NotifyProgress(mProgress, mInvalidRect, mFlags);
     return NS_OK;
   }
 
 private:
-  explicit NotifyProgressWorker(RasterImage* aImage)
+  NotifyProgressWorker(RasterImage* aImage, Progress aProgress,
+                       const nsIntRect& aInvalidRect, uint32_t aFlags)
     : mImage(aImage)
+    , mProgress(aProgress)
+    , mInvalidRect(aInvalidRect)
+    , mFlags(aFlags)
   { }
 
   nsRefPtr<RasterImage> mImage;
+  const Progress mProgress;
+  const nsIntRect mInvalidRect;
+  const uint32_t mFlags;
+};
+
+class NotifyDecodeCompleteWorker : public nsRunnable
+{
+public:
+  /**
+   * Called by the DecodePool when decoding is complete, so that final cleanup
+   * can be performed.
+   */
+  static void Dispatch(Decoder* aDecoder)
+  {
+    MOZ_ASSERT(aDecoder);
+
+    nsCOMPtr<nsIRunnable> worker = new NotifyDecodeCompleteWorker(aDecoder);
+    NS_DispatchToMainThread(worker);
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    DecodePool::Singleton()->NotifyDecodeComplete(mDecoder);
+    return NS_OK;
+  }
+
+private:
+  explicit NotifyDecodeCompleteWorker(Decoder* aDecoder)
+    : mDecoder(aDecoder)
+  { }
+
+  nsRefPtr<Decoder> mDecoder;
 };
 
 class DecodeWorker : public nsRunnable
 {
 public:
-  explicit DecodeWorker(RasterImage* aImage)
-    : mImage(aImage)
-  { }
+  explicit DecodeWorker(Decoder* aDecoder)
+    : mDecoder(aDecoder)
+  {
+    MOZ_ASSERT(mDecoder);
+  }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(!NS_IsMainThread());
-
-    ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
-
-    // If we were interrupted, we shouldn't do any work.
-    if (mImage->mDecodeStatus == DecodeStatus::STOPPED) {
-      NotifyProgressWorker::Dispatch(mImage);
-      return NS_OK;
-    }
-
-    // If someone came along and synchronously decoded us, there's nothing for us to do.
-    if (!mImage->mDecoder || mImage->IsDecodeFinished()) {
-      NotifyProgressWorker::Dispatch(mImage);
-      return NS_OK;
-    }
-
-    mImage->mDecodeStatus = DecodeStatus::ACTIVE;
-
-    size_t oldByteCount = mImage->mDecoder->BytesDecoded();
-
-    size_t maxBytes = mImage->mSourceData.Length() -
-                      mImage->mDecoder->BytesDecoded();
-    DecodePool::Singleton()->DecodeSomeOfImage(mImage, DecodeUntil::DONE_BYTES,
-                                               maxBytes);
-
-    size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount;
-
-    mImage->mDecodeStatus = DecodeStatus::WORK_DONE;
-
-    if (mImage->mDecoder &&
-        !mImage->mError &&
-        !mImage->mPendingError &&
-        !mImage->IsDecodeFinished() &&
-        bytesDecoded < maxBytes &&
-        bytesDecoded > 0) {
-      // We aren't finished decoding, and we have more data, so add this request
-      // to the back of the list.
-      DecodePool::Singleton()->RequestDecode(mImage);
-    } else {
-      // Nothing more for us to do - let everyone know what happened.
-      NotifyProgressWorker::Dispatch(mImage);
-    }
-
+    DecodePool::Singleton()->Decode(mDecoder);
     return NS_OK;
   }
 
-protected:
-  virtual ~DecodeWorker()
-  {
-    // Dispatch mImage to main thread to prevent mImage from being destructed by decode thread.
-    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
-    NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
-    if (mainThread) {
-      // Handle ambiguous nsISupports inheritance
-      RasterImage* rawImg = nullptr;
-      mImage.swap(rawImg);
-      DebugOnly<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
-      MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
-    }
-  }
-
 private:
-  nsRefPtr<RasterImage> mImage;
+  nsRefPtr<Decoder> mDecoder;
 };
 
 #ifdef MOZ_NUWA_PROCESS
 
 class RIDThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
@@ -189,23 +174,16 @@ DecodePool::Singleton()
     MOZ_ASSERT(NS_IsMainThread());
     sSingleton = new DecodePool();
     ClearOnShutdown(&sSingleton);
   }
 
   return sSingleton;
 }
 
-already_AddRefed<nsIEventTarget>
-DecodePool::GetEventTarget()
-{
-  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
-  return target.forget();
-}
-
 DecodePool::DecodePool()
   : mThreadPoolMutex("Thread Pool")
 {
   mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
   MOZ_RELEASE_ASSERT(mThreadPool,
                      "Should succeed in creating image decoding thread pool");
 
   mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
@@ -253,166 +231,111 @@ DecodePool::Observe(nsISupports*, const 
   if (threadPool) {
     threadPool->Shutdown();
   }
 
   return NS_OK;
 }
 
 void
-DecodePool::RequestDecode(RasterImage* aImage)
+DecodePool::AsyncDecode(Decoder* aDecoder)
 {
-  MOZ_ASSERT(aImage->mDecoder);
-  aImage->mDecodingMonitor.AssertCurrentThreadIn();
+  MOZ_ASSERT(aDecoder);
 
-  if (aImage->mDecodeStatus == DecodeStatus::PENDING ||
-      aImage->mDecodeStatus == DecodeStatus::ACTIVE) {
-    // The image is already in our list of images to decode, or currently being
-    // decoded, so we don't have to do anything else.
-    return;
-  }
-
-  aImage->mDecodeStatus = DecodeStatus::PENDING;
-  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aImage);
+  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
 
   // Dispatch to the thread pool if it exists. If it doesn't, we're currently
   // shutting down, so it's OK to just drop the job on the floor.
   MutexAutoLock threadPoolLock(mThreadPoolMutex);
   if (mThreadPool) {
     mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
   }
 }
 
 void
-DecodePool::DecodeABitOf(RasterImage* aImage)
+DecodePool::SyncDecodeIfSmall(Decoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  aImage->mDecodingMonitor.AssertCurrentThreadIn();
+  MOZ_ASSERT(aDecoder);
 
-  // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
-    aImage->FinishedSomeDecoding();
+  if (aDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime())) {
+    Decode(aDecoder);
+    return;
   }
 
-  DecodeSomeOfImage(aImage);
+  AsyncDecode(aDecoder);
+}
 
-  aImage->FinishedSomeDecoding();
+void
+DecodePool::SyncDecodeIfPossible(Decoder* aDecoder)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  Decode(aDecoder);
+}
+
+already_AddRefed<nsIEventTarget>
+DecodePool::GetEventTarget()
+{
+  MutexAutoLock threadPoolLock(mThreadPoolMutex);
+  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
+  return target.forget();
+}
 
-  // If we aren't yet finished decoding and we have more data in hand, add
-  // this request to the back of the priority list.
-  if (aImage->mDecoder &&
-      !aImage->mError &&
-      !aImage->IsDecodeFinished() &&
-      aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded()) {
-    RequestDecode(aImage);
+already_AddRefed<nsIRunnable>
+DecodePool::CreateDecodeWorker(Decoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder);
+  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
+  return worker.forget();
+}
+
+void
+DecodePool::Decode(Decoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder);
+
+  nsresult rv = aDecoder->Decode();
+
+  if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
+    if (aDecoder->HasProgress()) {
+      NotifyProgress(aDecoder);
+    }
+    // The decoder will ensure that a new worker gets enqueued to continue
+    // decoding when more data is available.
+  } else {
+    NotifyDecodeComplete(aDecoder);
   }
 }
 
-/* static */ void
-DecodePool::StopDecoding(RasterImage* aImage)
+void
+DecodePool::NotifyProgress(Decoder* aDecoder)
 {
-  aImage->mDecodingMonitor.AssertCurrentThreadIn();
-
-  // If we haven't got a decode request, we're not currently decoding. (Having
-  // a decode request doesn't imply we *are* decoding, though.)
-  aImage->mDecodeStatus = DecodeStatus::STOPPED;
-}
-
-nsresult
-DecodePool::DecodeUntilSizeAvailable(RasterImage* aImage)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  ReentrantMonitorAutoEnter lock(aImage->mDecodingMonitor);
-
-  // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
-    nsresult rv = aImage->FinishedSomeDecoding();
-    if (NS_FAILED(rv)) {
-      aImage->DoError();
-      return rv;
-    }
-  }
+  MOZ_ASSERT(aDecoder);
 
-  nsresult rv = DecodeSomeOfImage(aImage, DecodeUntil::SIZE);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  return aImage->FinishedSomeDecoding();
-}
-
-nsresult
-DecodePool::DecodeSomeOfImage(RasterImage* aImage,
-                              DecodeUntil aDecodeUntil /* = DecodeUntil::TIME */,
-                              uint32_t bytesToDecode /* = 0 */)
-{
-  MOZ_ASSERT(aImage->mInitialized, "Worker active for uninitialized container");
-  aImage->mDecodingMonitor.AssertCurrentThreadIn();
-
-  // If an error is flagged, it probably happened while we were waiting
-  // in the event queue.
-  if (aImage->mError) {
-    return NS_OK;
-  }
-
-  // If there is an error worker pending (say because the main thread has enqueued
-  // another decode request for us before processing the error worker) then bail out.
-  if (aImage->mPendingError) {
-    return NS_OK;
+  if (!NS_IsMainThread()) {
+    NotifyProgressWorker::Dispatch(aDecoder->GetImage(),
+                                   aDecoder->TakeProgress(),
+                                   aDecoder->TakeInvalidRect(),
+                                   aDecoder->GetDecodeFlags());
+    return;
   }
 
-  // If mDecoded or we don't have a decoder, we must have finished already (for
-  // example, a synchronous decode request came while the worker was pending).
-  if (!aImage->mDecoder || aImage->mDecoded) {
-    return NS_OK;
-  }
-
-  nsRefPtr<Decoder> decoderKungFuDeathGrip = aImage->mDecoder;
+  aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
+                                       aDecoder->TakeInvalidRect(),
+                                       aDecoder->GetDecodeFlags());
+}
 
-  uint32_t maxBytes;
-  if (aImage->mDecoder->IsSizeDecode()) {
-    // Decode all available data if we're a size decode; they're cheap, and we
-    // want them to be more or less synchronous.
-    maxBytes = aImage->mSourceData.Length();
-  } else {
-    // We're only guaranteed to decode this many bytes, so in particular,
-    // gfxPrefs::ImageMemDecodeBytesAtATime should be set high enough for us
-    // to read the size from most images.
-    maxBytes = gfxPrefs::ImageMemDecodeBytesAtATime();
-  }
+void
+DecodePool::NotifyDecodeComplete(Decoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder);
 
-  if (bytesToDecode == 0) {
-    bytesToDecode = aImage->mSourceData.Length() - aImage->mDecoder->BytesDecoded();
+  if (!NS_IsMainThread()) {
+    NotifyDecodeCompleteWorker::Dispatch(aDecoder);
+    return;
   }
 
-  TimeStamp deadline = TimeStamp::Now() +
-                       TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield());
-
-  // We keep decoding chunks until:
-  //  * we don't have any data left to decode,
-  //  * the decode completes,
-  //  * we're an DecodeUntil::SIZE decode and we get the size, or
-  //  * we run out of time.
-  while (aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded() &&
-         bytesToDecode > 0 &&
-         !aImage->IsDecodeFinished() &&
-         !(aDecodeUntil == DecodeUntil::SIZE && aImage->mHasSize)) {
-    uint32_t chunkSize = min(bytesToDecode, maxBytes);
-    nsresult rv = aImage->DecodeSomeData(chunkSize);
-    if (NS_FAILED(rv)) {
-      aImage->DoError();
-      return rv;
-    }
-
-    bytesToDecode -= chunkSize;
-
-    // Yield if we've been decoding for too long. We check this _after_ decoding
-    // a chunk to ensure that we don't yield without doing any decoding.
-    if (aDecodeUntil == DecodeUntil::TIME && TimeStamp::Now() >= deadline) {
-      break;
-    }
-  }
-
-  return NS_OK;
+  aDecoder->Finish();
+  aDecoder->GetImage()->FinalizeDecoder(aDecoder);
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/DecodePool.h
+++ b/image/src/DecodePool.h
@@ -20,110 +20,80 @@
 class nsIThreadPool;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
 class RasterImage;
 
-MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t)
-  INACTIVE,
-  PENDING,
-  ACTIVE,
-  WORK_DONE,
-  STOPPED
-MOZ_END_ENUM_CLASS(DecodeStatus)
-
-MOZ_BEGIN_ENUM_CLASS(DecodeUntil, uint8_t)
-  TIME,
-  SIZE,
-  DONE_BYTES
-MOZ_END_ENUM_CLASS(DecodeUntil)
-
-MOZ_BEGIN_ENUM_CLASS(ShutdownReason, uint8_t)
-  DONE,
-  NOT_NEEDED,
-  FATAL_ERROR
-MOZ_END_ENUM_CLASS(ShutdownReason)
-
-
 /**
- * DecodePool is a singleton class we use when decoding large images.
+ * DecodePool is a singleton class that manages decoding of raster images. It
+ * owns a pool of image decoding threads that are used for asynchronous
+ * decoding.
  *
- * When we wish to decode an image larger than
- * image.mem.max_bytes_for_sync_decode, we call DecodePool::RequestDecode()
- * for the image.  This adds the image to a queue of pending requests and posts
- * the DecodePool singleton to the event queue, if it's not already pending
- * there.
- *
- * When the DecodePool is run from the event queue, it decodes the image (and
- * all others it's managing) in chunks, periodically yielding control back to
- * the event loop.
+ * DecodePool allows callers to run a decoder, handling management of the
+ * decoder's lifecycle and whether it executes on the main thread,
+ * off-main-thread in the image decoding thread pool, or on some combination of
+ * the two.
  */
 class DecodePool : public nsIObserver
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static DecodePool* Singleton();
 
-  /**
-   * Ask the DecodePool to asynchronously decode this image.
-   */
-  void RequestDecode(RasterImage* aImage);
+  /// Ask the DecodePool to run @aDecoder asynchronously and return immediately.
+  void AsyncDecode(Decoder* aDecoder);
 
   /**
-   * Decode aImage for a short amount of time, and post the remainder to the
-   * queue.
+   * Run @aDecoder synchronously if the image it's decoding is small. If the
+   * image is too large, or if the source data isn't complete yet, run @aDecoder
+   * asynchronously instead.
    */
-  void DecodeABitOf(RasterImage* aImage);
+  void SyncDecodeIfSmall(Decoder* aDecoder);
 
   /**
-   * Ask the DecodePool to stop decoding this image.  Internally, we also
-   * call this function when we finish decoding an image.
-   *
-   * Since the DecodePool keeps raw pointers to RasterImages, make sure you
-   * call this before a RasterImage is destroyed!
+   * Run aDecoder synchronously if at all possible. If it can't complete
+   * synchronously because the source data isn't complete, asynchronously decode
+   * the rest.
    */
-  static void StopDecoding(RasterImage* aImage);
+  void SyncDecodeIfPossible(Decoder* aDecoder);
 
   /**
-   * Synchronously decode the beginning of the image until we run out of
-   * bytes or we get the image's size.  Note that this done on a best-effort
-   * basis; if the size is burried too deep in the image, we'll give up.
+   * Returns an event target interface to the DecodePool's underlying thread
+   * pool. Callers can use this event target to submit work to the image
+   * decoding thread pool.
    *
-   * @return NS_ERROR if an error is encountered, and NS_OK otherwise.  (Note
-   *         that we return NS_OK even when the size was not found.)
-   */
-  nsresult DecodeUntilSizeAvailable(RasterImage* aImage);
-
-  /**
-   * Returns an event target interface to the thread pool; primarily for
-   * OnDataAvailable delivery off main thread.
-   *
-   * @return An nsIEventTarget interface to mThreadPool.
+   * @return An nsIEventTarget interface to the thread pool.
    */
   already_AddRefed<nsIEventTarget> GetEventTarget();
 
   /**
-   * Decode some chunks of the given image.  If aDecodeUntil is SIZE,
-   * decode until we have the image's size, then stop. If bytesToDecode is
-   * non-0, at most bytesToDecode bytes will be decoded. if aDecodeUntil is
-   * DONE_BYTES, decode until all bytesToDecode bytes are decoded.
+   * Creates a worker which can be used to attempt further decoding using the
+   * provided decoder.
+   *
+   * @return The new worker, which should be posted to the event target returned
+   *         by GetEventTarget.
    */
-  nsresult DecodeSomeOfImage(RasterImage* aImage,
-                             DecodeUntil aDecodeUntil = DecodeUntil::TIME,
-                             uint32_t bytesToDecode = 0);
+  already_AddRefed<nsIRunnable> CreateDecodeWorker(Decoder* aDecoder);
 
 private:
+  friend class DecodeWorker;
+  friend class NotifyDecodeCompleteWorker;
+
   DecodePool();
   virtual ~DecodePool();
 
+  void Decode(Decoder* aDecoder);
+  void NotifyDecodeComplete(Decoder* aDecoder);
+  void NotifyProgress(Decoder* aDecoder);
+
   static StaticRefPtr<DecodePool> sSingleton;
 
   // mThreadPoolMutex protects mThreadPool. For all RasterImages R,
   // R::mDecodingMonitor must be acquired before mThreadPoolMutex
   // if both are acquired; the other order may cause deadlock.
   Mutex                     mThreadPoolMutex;
   nsCOMPtr<nsIThreadPool>   mThreadPool;
 };
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -2,40 +2,45 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Decoder.h"
 
 #include "mozilla/gfx/2D.h"
+#include "DecodePool.h"
+#include "GeckoProfiler.h"
 #include "imgIContainer.h"
 #include "nsIConsoleService.h"
 #include "nsIScriptError.h"
-#include "GeckoProfiler.h"
+#include "nsProxyRelease.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 
 using mozilla::gfx::IntSize;
 using mozilla::gfx::SurfaceFormat;
 
 namespace mozilla {
 namespace image {
 
-Decoder::Decoder(RasterImage &aImage)
+Decoder::Decoder(RasterImage* aImage)
   : mImage(aImage)
   , mProgress(NoProgress)
   , mImageData(nullptr)
   , mColormap(nullptr)
   , mChunkCount(0)
   , mDecodeFlags(0)
   , mBytesDecoded(0)
   , mSendPartialInvalidations(false)
+  , mDataDone(false)
   , mDecodeDone(false)