Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 07 Jun 2014 14:16:54 -0400
changeset 187469 eccdf8bfd1391c1b1ea1fc39fc0ab5aa54753a2e
parent 187468 2d7a9930ec047661e854c1cf649def1d95a1ed4b (current diff)
parent 187405 a2f0e0619332ba4ae1f37aab37bff6e71ffa19ab (diff)
child 187470 30e1107561f5c28a1292d6d7f07cc29c66d1e278
push id44608
push userryanvm@gmail.com
push dateMon, 09 Jun 2014 02:12:46 +0000
treeherdermozilla-inbound@9305a8ec77fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound. a=merge
b2g/app/b2g.js
b2g/installer/package-manifest.in
dom/base/Navigator.cpp
--- a/.hgignore
+++ b/.hgignore
@@ -24,16 +24,17 @@
 ^security/manager/\.nss\.checkout$
 
 # Build directories
 ^obj
 
 # Build directories for js shell
 _DBG\.OBJ/
 _OPT\.OBJ/
+^js/src/.*-obj/
 
 # SpiderMonkey configury
 ^js/src/configure$
 ^js/src/autom4te.cache$
 # SpiderMonkey test result logs
 ^js/src/tests/results-.*\.(html|txt)$
 
 # Java HTML5 parser classes
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,10 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 904723 (Array.from) needs a clobber once again because of changes to js.msg
-and self-hosted code (see bug 1019955).
+Bug 973238 part 9 needs clobber due to self-hosted code (bug 1019955).
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -950,19 +950,24 @@ pref("apz.y_skate_size_multiplier", "1.5
 pref("apz.x_stationary_size_multiplier", "1.5");
 pref("apz.y_stationary_size_multiplier", "1.8");
 pref("apz.enlarge_displayport_when_clipped", true);
 // Use "sticky" axis locking
 pref("apz.axis_lock_mode", 2);
 pref("apz.subframe.enabled", true);
 
 // Overscroll-related settings
-pref("apz.overscroll.enabled", false);
-pref("apz.overscroll.snap_back_accel", "0.003");
-pref("apz.overscroll.snap_back_init_vel", "1");
+pref("apz.overscroll.enabled", true);
+pref("apz.overscroll.fling_friction", "0.02");
+pref("apz.overscroll.fling_stopped_threshold", "0.4");
+pref("apz.overscroll.clamping", "0.5");
+pref("apz.overscroll.z_effect", "0.2");
+pref("apz.overscroll.snap_back.spring_stiffness", "0.6");
+pref("apz.overscroll.snap_back.spring_friction", "0.1");
+pref("apz.overscroll.snap_back.mass", "1000");
 
 // This preference allows FirefoxOS apps (and content, I think) to force
 // the use of software (instead of hardware accelerated) 2D canvases by
 // creating a context like this:
 //
 //   canvas.getContext('2d', { willReadFrequently: true })
 //
 // Using a software canvas can save memory when JS calls getImageData()
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -645,17 +645,17 @@ let settingsToObserve = {
   'app.update.interval': 86400,
   'app.update.url': {
     resetToPref: true
   },
   'apz.force-enable': {
     prefName: 'dom.browser_frames.useAsyncPanZoom',
     defaultValue: false
   },
-  'apz.overscroll.enabled': false,
+  'apz.overscroll.enabled': true,
   'debug.fps.enabled': {
     prefName: 'layers.acceleration.draw-fps',
     defaultValue: false
   },
   'debug.log-animations.enabled': {
     prefName: 'layers.offmainthreadcomposition.log-animations',
     defaultValue: false
   },
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -1,16 +1,14 @@
 [DEFAULT]
+run-if = toolkit == "gonk"
 support-files =
   permission_handler_chrome.js
   SandboxPromptTest.html
   filepicker_path_handler_chrome.js
   systemapp_helper.js
 
 [test_sandbox_permission.html]
-run-if = toolkit == "gonk"
+skip-if = true # bug 984274 - frequent timeouts
 [test_filepicker_path.html]
-run-if = toolkit == "gonk"
 [test_permission_deny.html]
-run-if = toolkit == "gonk"
 [test_permission_gum_remember.html]
-run-if = toolkit == "gonk"
 [test_systemapp.html]
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -481,16 +481,18 @@
 @BINPATH@/components/nsHandlerService.js
 @BINPATH@/components/nsWebHandlerApp.manifest
 @BINPATH@/components/nsWebHandlerApp.js
 @BINPATH@/components/satchel.manifest
 @BINPATH@/components/nsFormAutoComplete.js
 @BINPATH@/components/nsFormHistory.js
 @BINPATH@/components/FormHistoryStartup.js
 @BINPATH@/components/nsInputListAutoComplete.js
+@BINPATH@/components/formautofill.manifest
+@BINPATH@/components/AutofillController.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 @BINPATH@/components/messageWakeupService.js
 @BINPATH@/components/messageWakeupService.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1400782040000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1401837170000">
   <emItems>
       <emItem  blockID="i454" id="sqlmoz@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                                 <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
@@ -867,16 +867,22 @@
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i43" id="supportaccessplugin@gmail.com">
                           <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i588" id="quick_start@gmail.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i340" id="chiang@programmer.net">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i54" id="applebeegifts@mozilla.doslash.org">
                         <versionRange  minVersion="0" maxVersion="*">
@@ -1273,28 +1279,34 @@
               </prefs>
     </emItem>
       <emItem  blockID="i86" id="{45147e67-4020-47e2-8f7a-55464fb535aa}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
-      <emItem  blockID="i19" id="{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}">
-                        <versionRange  minVersion="1.1b1" maxVersion="1.1b1">
+      <emItem  blockID="i586" id="jid1-0xtMKhXFEs4jIg@jetpack">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i318" id="ffxtlbr@incredibar.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i19" id="{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}">
+                        <versionRange  minVersion="1.1b1" maxVersion="1.1b1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i531" id="/^(4cb61367-efbf-4aa1-8e3a-7f776c9d5763@cdece6e9-b2ef-40a9-b178-291da9870c59\.com|0efc9c38-1ec7-49ed-8915-53a48b6b7600@e7f17679-2a42-4659-83c5-7ba961fdf75a\.com|6be3335b-ef79-4b0b-a0ba-b87afbc6f4ad@6bbb4d2e-e33e-4fa5-9b37-934f4fb50182\.com)$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i490" id="now.msn.com@services.mozilla.org">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
@@ -1405,16 +1417,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i220" id="pricepeep@getpricepeep.com">
                         <versionRange  minVersion="0" maxVersion="2.1.0.19.99" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i590" id="{94cd2cc3-083f-49ba-a218-4cda4b4829fd}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i544" id="/^(93abedcf-8e3a-4d02-b761-d1441e437c09@243f129d-aee2-42c2-bcd1-48858e1c22fd\.com|9acfc440-ac2d-417a-a64c-f6f14653b712@09f9a966-9258-4b12-af32-da29bdcc28c5\.com|58ad0086-1cfb-48bb-8ad2-33a8905572bc@5715d2be-69b9-4930-8f7e-64bdeb961cfd\.com)$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i224" id="{336D0C35-8A85-403a-B9D2-65C292C39087}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1232,18 +1232,22 @@ pref("devtools.toolbar.enabled", true);
 pref("devtools.toolbar.visible", false);
 pref("devtools.commands.dir", "");
 
 // Enable the app manager
 pref("devtools.appmanager.enabled", true);
 pref("devtools.appmanager.lastTab", "help");
 pref("devtools.appmanager.manifestEditor.enabled", true);
 
-// Disable devtools webide until bug 1007059
+// Enable devtools webide
+#ifdef MOZ_DEVTOOLS_WEBIDE
+pref("devtools.webide.enabled", true);
+#else
 pref("devtools.webide.enabled", false);
+#endif
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.selectedTool", "webconsole");
 pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage"]');
 pref("devtools.toolbox.sideEnabled", true);
@@ -1556,16 +1560,18 @@ pref("identity.fxaccounts.remote.signin.
 // "identity.fxaccounts.remote.signup.uri" pref.
 pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
 
 // On GTK, we now default to showing the menubar only when alt is pressed:
 #ifdef MOZ_WIDGET_GTK
 pref("ui.key.menuAccessKeyFocuses", true);
 #endif
 
+// Encrypted media extensions.
+pref("media.eme.enabled", false);
 
 // Delete HTTP cache v2 data of users that didn't opt-in manually
 pref("browser.cache.auto_delete_cache_version", 1);
 // Play with different values of the decay time and get telemetry,
 // 0 means to randomize (and persist) the experiment value in users' profiles,
 // -1 means no experiment is run and we use the preferred value for frecency (6h)
 pref("browser.cache.frecency_experiment", 0);
 
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -440,17 +440,17 @@ nsContextMenu.prototype = {
     this.showItem("context-media-play",  onMedia && (this.target.paused || this.target.ended));
     this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
     this.showItem("context-media-mute",   onMedia && !this.target.muted);
     this.showItem("context-media-unmute", onMedia && this.target.muted);
     this.showItem("context-media-playbackrate", onMedia);
     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
     this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
     this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
-    var statsShowing = this.onVideo && XPCNativeWrapper.unwrap(this.target).mozMediaStatisticsShowing;
+    var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing;
     this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
     this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
 
     // Disable them when there isn't a valid media source loaded.
     if (onMedia) {
       this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
       this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
       this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -23,17 +23,19 @@ DIRS += [
     'shadereditor',
     'shared',
     'sourceeditor',
     'styleeditor',
     'styleinspector',
     'tilt',
     'webaudioeditor',
     'webconsole',
-    'webide',
 ]
 
+if CONFIG['MOZ_DEVTOOLS_WEBIDE']:
+    DIRS += ['webide']
+
 EXTRA_COMPONENTS += [
     'devtools-clhandler.js',
     'devtools-clhandler.manifest',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -152,24 +152,22 @@ let UI = {
     this.hidePanels();
     document.querySelector("window").classList.add("busy")
     this.updateCommands();
   },
 
   unbusy: function() {
     document.querySelector("window").classList.remove("busy")
     this.updateCommands();
-    this._busyPromise = null;
   },
 
   busyUntil: function(promise, operationDescription) {
     // Freeze the UI until the promise is resolved. A 30s timeout
     // will unfreeze the UI, just in case the promise never gets
     // resolved.
-    this._busyPromise = promise;
     let timeout = setTimeout(() => {
       this.unbusy();
       UI.reportError("error_operationTimeout", operationDescription);
     }, 30000);
     this.busy();
     promise.then(() => {
       clearTimeout(timeout);
       this.unbusy();
--- a/browser/devtools/webide/test/head.js
+++ b/browser/devtools/webide/test/head.js
@@ -57,25 +57,27 @@ function closeWebIDE(win) {
   });
 
   win.close();
 
   return deferred.promise;
 }
 
 function removeAllProjects() {
-  return Task.spawn(function* () {
-    yield AppProjects.load();
+  let deferred = promise.defer();
+  AppProjects.load().then(() => {
     let projects = AppProjects.store.object.projects;
     for (let i = 0; i < projects.length; i++) {
-      yield AppProjects.remove(projects[i].location);
+      AppProjects.remove(projects[i].location);
     }
+    deferred.resolve();
   });
+
+  return deferred.promise;
 }
-
 function nextTick() {
   let deferred = promise.defer();
   SimpleTest.executeSoon(() => {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
--- a/browser/devtools/webide/test/test_cli.html
+++ b/browser/devtools/webide/test/test_cli.html
@@ -14,22 +14,25 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
+          let clClass = Components.classes["@mozilla.org/toolkit/command-line;1"].createInstance();
+
           Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
           DebuggerServer.init(function () { return true; });
           DebuggerServer.addBrowserActors();
 
           let win = yield openWebIDE();
 
+
           let packagedAppLocation = getTestFilePath("app");
 
           let cli = "actions=addPackagedApp&location=" + packagedAppLocation;
           yield win.handleCommandline(cli);
 
           let project = win.AppManager.selectedProject;
           is(project.location, packagedAppLocation, "Project imported");
 
--- a/browser/devtools/webide/test/test_import.html
+++ b/browser/devtools/webide/test/test_import.html
@@ -17,19 +17,16 @@
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
             let win = yield openWebIDE();
             let packagedAppLocation = getTestFilePath("app");
 
-            yield win.AppProjects.load();
-            is(win.AppProjects.store.object.projects.length, 0, "IDB is empty");
-
             yield win.Cmds.importPackagedApp(packagedAppLocation);
 
             let project = win.AppManager.selectedProject;
             is(project.location, packagedAppLocation, "Location is valid");
             is(project.name, "A name (in app directory)", "name field has been updated");
             is(project.manifest.launch_path, "/index.html", "manifest found. launch_path valid.");
             is(project.manifest.description, "desc", "manifest found. description valid");
 
--- a/browser/devtools/webide/test/test_runtime.html
+++ b/browser/devtools/webide/test/test_runtime.html
@@ -44,61 +44,71 @@
 
             getName: function() {
               return "fakeRuntime";
             }
           });
 
           win.AppManager.update("runtimelist");
 
-          let packagedAppLocation = getTestFilePath("app");
+          let hostedAppManifest = TEST_BASE + "hosted_app.manifest";
+          yield win.Cmds.importHostedApp(hostedAppManifest);
 
-          yield win.Cmds.importPackagedApp(packagedAppLocation);
+          yield win.Cmds.showRuntimePanel();
 
           let panelNode = win.document.querySelector("#runtime-panel");
           let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
           is(items.length, 1, "Found one runtime button");
 
           let deferred = promise.defer();
           win.AppManager.connection.once(
               win.Connection.Events.CONNECTED,
               () => deferred.resolve());
 
           items[0].click();
 
-          ok(win.document.querySelector("window").className, "busy", "UI is busy");
-          yield win.UI._busyPromise;
+          yield deferred.promise;
 
           is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
 
-          ok(isPlayActive(), "play button is enabled 1");
-          ok(!isStopActive(), "stop button is disabled 1");
-          let oldProject = win.AppManager.selectedProject;
-          win.AppManager.selectedProject = null;
-
           yield nextTick();
 
           ok(!isPlayActive(), "play button is disabled 2");
           ok(!isStopActive(), "stop button is disabled 2");
-          win.AppManager._selectedProject = oldProject;
+
+          win.AppManager.selectedProject.errorsCount = 0;
           win.UI.updateCommands();
 
           yield nextTick();
 
           ok(isPlayActive(), "play button is enabled 3");
           ok(!isStopActive(), "stop button is disabled 3");
+          let oldProject = win.AppManager.selectedProject;
+          win.AppManager.selectedProject = null;
+
+          yield nextTick();
+
+          ok(!isPlayActive(), "play button is disabled 4");
+          ok(!isStopActive(), "stop button is disabled 4");
+          win.AppManager._selectedProject = oldProject;
+          win.UI.updateCommands();
+
+          yield nextTick();
+
+          ok(isPlayActive(), "play button is enabled 5");
+          ok(!isStopActive(), "stop button is disabled 5");
 
 
           yield win.Cmds.disconnectRuntime();
 
           is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
 
           ok(win.AppManager.selectedProject, "A project is still selected");
-          ok(!isPlayActive(), "play button is disabled 4");
-          ok(!isStopActive(), "stop button is disabled 4");
+          ok(!isPlayActive(), "play button is disabled 6");
+          ok(!isStopActive(), "stop button is disabled 6");
 
           deferred = promise.defer();
           win.AppManager.connection.once(
               win.Connection.Events.CONNECTED,
               () => deferred.resolve());
 
           win.document.querySelectorAll(".runtime-panel-item-custom")[1].click();
 
@@ -107,18 +117,16 @@
           is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
 
           yield win.Cmds.disconnectRuntime();
 
           yield closeWebIDE(win);
 
           DebuggerServer.destroy();
 
-          yield removeAllProjects();
-
           SimpleTest.finish();
 
         });
       }
 
 
     </script>
   </body>
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -449,16 +449,18 @@
 @BINPATH@/components/nsHandlerService.js
 @BINPATH@/components/nsWebHandlerApp.manifest
 @BINPATH@/components/nsWebHandlerApp.js
 @BINPATH@/components/satchel.manifest
 @BINPATH@/components/nsFormAutoComplete.js
 @BINPATH@/components/nsFormHistory.js
 @BINPATH@/components/FormHistoryStartup.js
 @BINPATH@/components/nsInputListAutoComplete.js
+@BINPATH@/components/formautofill.manifest
+@BINPATH@/components/AutofillController.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 @BINPATH@/browser/components/BrowserProfileMigrators.manifest
 @BINPATH@/browser/components/ProfileMigrator.js
 @BINPATH@/browser/components/ChromeProfileMigrator.js
 @BINPATH@/browser/components/FirefoxProfileMigrator.js
@@ -631,19 +633,21 @@
 @BINPATH@/chrome/recording/*
 #ifdef MOZ_GTK
 @BINPATH@/browser/chrome/icons/default/default16.png
 @BINPATH@/browser/chrome/icons/default/default32.png
 @BINPATH@/browser/chrome/icons/default/default48.png
 #endif
 
 ; [Webide Files]
+#ifdef MOZ_DEVTOOLS_WEBIDE
 @BINPATH@/browser/chrome/webide@JAREXT@
 @BINPATH@/browser/chrome/webide.manifest
 @BINPATH@/browser/@PREF_DIR@/webide-prefs.js
+#endif
 
 ; shell icons
 #ifdef XP_UNIX
 #ifndef XP_MACOSX
 ; shell icons
 @BINPATH@/browser/icons/*.png
 #ifdef MOZ_UPDATER
 ; updater icon
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -505,16 +505,18 @@
   width: 2px;
   background-image: linear-gradient(hsla(204,45%,98%,0), hsla(204,45%,98%,.1), hsla(204,45%,98%,0)),
                     linear-gradient(hsla(206,37%,4%,0), hsla(206,37%,4%,.6), hsla(206,37%,4%,0)),
                     linear-gradient(hsla(204,45%,98%,0), hsla(204,45%,98%,.1), hsla(204,45%,98%,0));
   background-size: 1px 100%;
   background-repeat: no-repeat;
   background-position: 0, 1px, 2px;
 }
+
+#toolbox-buttons:empty + #toolbox-controls-separator,
 #toolbox-controls-separator[invisible] {
   visibility: hidden;
 }
 
 /* Command buttons */
 
 .command-button {
   -moz-appearance: none;
--- a/configure.in
+++ b/configure.in
@@ -7675,16 +7675,29 @@ if test "$MOZ_CHROME_FILE_FORMAT" = "sym
 fi
 
 if test "$MOZ_CHROME_FILE_FORMAT" != "jar" &&
     test "$MOZ_CHROME_FILE_FORMAT" != "flat" &&
     test "$MOZ_CHROME_FILE_FORMAT" != "omni"; then
     AC_MSG_ERROR([--enable-chrome-format must be set to either jar, flat, or omni])
 fi
 
+dnl ========================================================
+dnl = Enable Support for devtools webide
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(devtools-webide,
+[  --enable-devtools-webide Set compile flags necessary for compiling devtools webide ],
+MOZ_DEVTOOLS_WEBIDE=1,
+MOZ_DEVTOOLS_WEBIDE= )
+
+if test -n "$MOZ_DEVTOOLS_WEBIDE"; then
+    AC_DEFINE(MOZ_DEVTOOLS_WEBIDE)
+fi
+AC_SUBST(MOZ_DEVTOOLS_WEBIDE)
+
 dnl =========================================================
 dnl Omnijar packaging (bug 552121)
 dnl =========================================================
 dnl Omnijar packaging is compatible with flat packaging.
 dnl In unpackaged builds, omnijar looks for files as if
 dnl things were flat packaged. After packaging, all files
 dnl are loaded from a single jar. MOZ_CHROME_FILE_FORMAT
 dnl is set to flat since putting files into jars is only
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -878,20 +878,20 @@ public:
    * there is a scroll frame that contains the frame being scrolled). This
    * frame is always the first continuation.
    *
    * In the case of absolutely positioned elements and floated elements, this
    * frame is the out of flow frame, not the placeholder.
    */
   nsIFrame* GetPrimaryFrame() const
   {
-    return IsInDoc() ? mPrimaryFrame : nullptr;
+    return (IsInDoc() || HasFlag(NODE_IS_IN_SHADOW_TREE)) ? mPrimaryFrame : nullptr;
   }
   void SetPrimaryFrame(nsIFrame* aFrame) {
-    NS_ASSERTION(IsInDoc(), "This will end badly!");
+    MOZ_ASSERT(IsInDoc() || HasFlag(NODE_IS_IN_SHADOW_TREE), "This will end badly!");
     NS_PRECONDITION(!aFrame || !mPrimaryFrame || aFrame == mPrimaryFrame,
                     "Losing track of existing primary frame");
     mPrimaryFrame = aFrame;
   }
 
   nsresult LookupNamespaceURIInternal(const nsAString& aNamespacePrefix,
                                       nsAString& aNamespaceURI) const;
 
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -521,16 +521,29 @@ public:
    * @return the current document
    */
   nsIDocument *GetCurrentDoc() const
   {
     return IsInDoc() ? OwnerDoc() : nullptr;
   }
 
   /**
+   * This method gets the current doc of the node hosting this content
+   * or the current doc of this content if it is not being hosted. This
+   * method walks through ShadowRoot boundaries until it reach the host
+   * that is located in the root of the "tree of trees" (see Shadow DOM
+   * spec) and returns the current doc for that host.
+   */
+  nsIDocument* GetCrossShadowCurrentDoc() const
+  {
+    return HasFlag(NODE_IS_IN_SHADOW_TREE) ?
+      GetCrossShadowCurrentDocInternal() : GetCurrentDoc();
+  }
+
+  /**
    * The values returned by this function are the ones defined for
    * nsIDOMNode.nodeType
    */
   uint16_t NodeType() const
   {
     return mNodeInfo->NodeType();
   }
   const nsString& NodeName() const
@@ -803,39 +816,17 @@ public:
     return mParent && mParent->IsElement() ? mParent->AsElement() : nullptr;
   }
 
   /**
    * Get the root of the subtree this node belongs to.  This never returns
    * null.  It may return 'this' (e.g. for document nodes, and nodes that
    * are the roots of disconnected subtrees).
    */
-  nsINode* SubtreeRoot() const
-  {
-    // There are three cases of interest here.  nsINodes that are really:
-    // 1. nsIDocument nodes - Are always in the document.
-    // 2. nsIContent nodes - Are either in the document, or mSubtreeRoot
-    //    is updated in BindToTree/UnbindFromTree.
-    // 3. nsIAttribute nodes - Are never in the document, and mSubtreeRoot
-    //    is always 'this' (as set in nsINode's ctor).
-    nsINode* node = IsInDoc() ? OwnerDocAsNode() : mSubtreeRoot;
-    NS_ASSERTION(node, "Should always have a node here!");
-#ifdef DEBUG
-    {
-      const nsINode* slowNode = this;
-      const nsINode* iter = slowNode;
-      while ((iter = iter->GetParentNode())) {
-        slowNode = iter;
-      }
-
-      NS_ASSERTION(slowNode == node, "These should always be in sync!");
-    }
-#endif
-    return node;
-  }
+  nsINode* SubtreeRoot() const;
 
   /**
    * See nsIDOMEventTarget
    */
   NS_DECL_NSIDOMEVENTTARGET
 
   virtual mozilla::EventListenerManager*
     GetExistingListenerManager() const MOZ_OVERRIDE;
@@ -1200,16 +1191,18 @@ public:
    */
   bool Contains(const nsINode* aOther) const;
   nsresult Contains(nsIDOMNode* aOther, bool* aReturn);
 
   bool UnoptimizableCCNode() const;
 
 private:
 
+  nsIDocument* GetCrossShadowCurrentDocInternal() const;
+
   nsIContent* GetNextNodeImpl(const nsINode* aRoot,
                               const bool aSkipChildren) const
   {
     // Can't use nsContentUtils::ContentIsDescendantOf here, since we
     // can't include it here.
 #ifdef DEBUG
     if (aRoot) {
       const nsINode* cur = this;
@@ -1515,17 +1508,18 @@ protected:
   { return GetBoolFlag(ElementHasWeirdParserInsertionMode); }
   bool HandlingClick() const { return GetBoolFlag(NodeHandlingClick); }
   void SetHandlingClick() { SetBoolFlag(NodeHandlingClick); }
   void ClearHandlingClick() { ClearBoolFlag(NodeHandlingClick); }
 
   void SetSubtreeRootPointer(nsINode* aSubtreeRoot)
   {
     NS_ASSERTION(aSubtreeRoot, "aSubtreeRoot can never be null!");
-    NS_ASSERTION(!(IsNodeOfType(eCONTENT) && IsInDoc()), "Shouldn't be here!");
+    NS_ASSERTION(!(IsNodeOfType(eCONTENT) && IsInDoc()) &&
+                 !HasFlag(NODE_IS_IN_SHADOW_TREE), "Shouldn't be here!");
     mSubtreeRoot = aSubtreeRoot;
   }
 
   void ClearSubtreeRootPointer()
   {
     mSubtreeRoot = nullptr;
   }
 
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -185,17 +185,17 @@ Element::IntrinsicState() const
 {
   return IsEditable() ? NS_EVENT_STATE_MOZ_READWRITE :
                         NS_EVENT_STATE_MOZ_READONLY;
 }
 
 void
 Element::NotifyStateChange(EventStates aStates)
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetCrossShadowCurrentDoc();
   if (doc) {
     nsAutoScriptBlocker scriptBlocker;
     doc->ContentStateChanged(this, aStates);
   }
 }
 
 void
 Element::UpdateLinkState(EventStates aState)
@@ -826,17 +826,17 @@ Element::CreateShadowRoot(ErrorResult& a
   nsRefPtr<nsXBLBinding> xblBinding = new nsXBLBinding(shadowRoot, protoBinding);
   shadowRoot->SetAssociatedBinding(xblBinding);
   xblBinding->SetBoundElement(this);
 
   SetXBLBinding(xblBinding);
 
   // Recreate the frame for the bound content because binding a ShadowRoot
   // changes how things are rendered.
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetCrossShadowCurrentDoc();
   if (doc) {
     nsIPresShell *shell = doc->GetShell();
     if (shell) {
       shell->RecreateFramesFor(this);
     }
   }
 
   return shadowRoot.forget();
@@ -1226,16 +1226,17 @@ Element::BindToTree(nsIDocument* aDocume
   if (aParent) {
     if (aParent->IsInNativeAnonymousSubtree()) {
       SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
     }
     if (aParent->HasFlag(NODE_CHROME_ONLY_ACCESS)) {
       SetFlags(NODE_CHROME_ONLY_ACCESS);
     }
     if (aParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
+      ClearSubtreeRootPointer();
       SetFlags(NODE_IS_IN_SHADOW_TREE);
     }
     ShadowRoot* parentContainingShadow = aParent->GetContainingShadow();
     if (parentContainingShadow) {
       DOMSlots()->mContainingShadow = parentContainingShadow;
     }
   }
 
@@ -1286,18 +1287,19 @@ Element::BindToTree(nsIDocument* aDocume
     UnsetFlags(NODE_FORCE_XBL_BINDINGS |
                // And clear the lazy frame construction bits.
                NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES |
                // And the restyle bits
                ELEMENT_ALL_RESTYLE_FLAGS);
 
     // Propagate scoped style sheet tracking bit.
     SetIsElementInStyleScope(mParent->IsElementInStyleScope());
-  } else {
-    // If we're not in the doc, update our subtree pointer.
+  } else if (!HasFlag(NODE_IS_IN_SHADOW_TREE)) {
+    // If we're not in the doc and not in a shadow tree,
+    // update our subtree pointer.
     SetSubtreeRootPointer(aParent->SubtreeRoot());
   }
 
   // This has to be here, rather than in nsGenericHTMLElement::BindToTree,
   //  because it has to happen after updating the parent pointer, but before
   //  recursively binding the kids.
   if (IsHTML()) {
     SetDirOnBind(this, aParent);
@@ -1430,16 +1432,17 @@ Element::UnbindFromTree(bool aDeep, bool
       mParent = nullptr;
       NS_RELEASE(p);
     } else {
       mParent = nullptr;
     }
     SetParentIsContent(false);
   }
   ClearInDocument();
+  UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
   // Begin keeping track of our subtree root.
   SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
 
   if (document) {
     // Notify XBL- & nsIAnonymousContentCreator-generated
     // anonymous content that the document is changing.
     if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
@@ -1465,17 +1468,17 @@ Element::UnbindFromTree(bool aDeep, bool
     DeleteProperty(nsGkAtoms::transitionsOfAfterProperty);
     DeleteProperty(nsGkAtoms::transitionsProperty);
     DeleteProperty(nsGkAtoms::animationsOfBeforeProperty);
     DeleteProperty(nsGkAtoms::animationsOfAfterProperty);
     DeleteProperty(nsGkAtoms::animationsProperty);
   }
 
   // Unset this since that's what the old code effectively did.
-  UnsetFlags(NODE_FORCE_XBL_BINDINGS | NODE_IS_IN_SHADOW_TREE);
+  UnsetFlags(NODE_FORCE_XBL_BINDINGS);
   
 #ifdef MOZ_XUL
   nsXULElement* xulElem = nsXULElement::FromContent(this);
   if (xulElem) {
     xulElem->SetXULBindingParent(nullptr);
   }
   else
 #endif
--- a/content/base/src/ShadowRoot.cpp
+++ b/content/base/src/ShadowRoot.cpp
@@ -67,19 +67,24 @@ NS_IMPL_RELEASE_INHERITED(ShadowRoot, Do
 ShadowRoot::ShadowRoot(nsIContent* aContent,
                        already_AddRefed<nsINodeInfo>&& aNodeInfo,
                        nsXBLPrototypeBinding* aProtoBinding)
   : DocumentFragment(aNodeInfo), mPoolHost(aContent),
     mProtoBinding(aProtoBinding), mShadowElement(nullptr),
     mInsertionPointChanged(false)
 {
   SetHost(aContent);
+
+  // Nodes in a shadow tree should never store a value
+  // in the subtree root pointer, nodes in the shadow tree
+  // track the subtree root using GetContainingShadow().
+  ClearSubtreeRootPointer();
+
   SetFlags(NODE_IS_IN_SHADOW_TREE);
-  // ShadowRoot isn't really in the document but it behaves like it is.
-  SetInDocument();
+
   DOMSlots()->mBindingParent = aContent;
   DOMSlots()->mContainingShadow = this;
 
   // Add the ShadowRoot as a mutation observer on the host to watch
   // for mutations because the insertion points in this ShadowRoot
   // may need to be updated when the host children are modified.
   mPoolHost->AddMutationObserver(this);
 }
@@ -87,17 +92,21 @@ ShadowRoot::ShadowRoot(nsIContent* aCont
 ShadowRoot::~ShadowRoot()
 {
   if (mPoolHost) {
     // mPoolHost may have been unlinked or a new ShadowRoot may have been
     // creating, making this one obsolete.
     mPoolHost->RemoveMutationObserver(this);
   }
 
-  ClearInDocument();
+  UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+  // nsINode destructor expects mSubtreeRoot == this.
+  SetSubtreeRootPointer(this);
+
   SetHost(nullptr);
 }
 
 JSObject*
 ShadowRoot::WrapObject(JSContext* aCx)
 {
   return mozilla::dom::ShadowRootBinding::Wrap(aCx, this);
 }
@@ -110,24 +119,24 @@ ShadowRoot::FromNode(nsINode* aNode)
                "ShadowRoot is a document fragment.");
     return static_cast<ShadowRoot*>(aNode);
   }
 
   return nullptr;
 }
 
 void
-ShadowRoot::Restyle()
+ShadowRoot::StyleSheetChanged()
 {
   mProtoBinding->FlushSkinSheets();
 
   nsIPresShell* shell = OwnerDoc()->GetShell();
   if (shell) {
     OwnerDoc()->BeginUpdate(UPDATE_STYLE);
-    shell->RestyleShadowRoot(this);
+    shell->RecordShadowStyleChange(this);
     OwnerDoc()->EndUpdate(UPDATE_STYLE);
   }
 }
 
 void
 ShadowRoot::InsertSheet(nsCSSStyleSheet* aSheet,
                         nsIContent* aLinkingContent)
 {
@@ -152,31 +161,35 @@ ShadowRoot::InsertSheet(nsCSSStyleSheet*
 
     nsINode* sheetOwnerNode = sheets->ElementAt(i)->GetOwnerNode();
     if (nsContentUtils::PositionIsBefore(aLinkingContent, sheetOwnerNode)) {
       sheets->InsertElementAt(i, aSheet);
       break;
     }
   }
 
-  Restyle();
+  if (aSheet->IsApplicable()) {
+    StyleSheetChanged();
+  }
 }
 
 void
 ShadowRoot::RemoveSheet(nsCSSStyleSheet* aSheet)
 {
   nsTArray<nsRefPtr<nsCSSStyleSheet> >* sheets =
     mProtoBinding->GetOrCreateStyleSheets();
   MOZ_ASSERT(sheets, "Style sheets array should never be null.");
 
   DebugOnly<bool> found = sheets->RemoveElement(aSheet);
   MOZ_ASSERT(found, "Trying to remove a sheet from a ShadowRoot "
                     "that does not exist.");
 
-  Restyle();
+  if (aSheet->IsApplicable()) {
+    StyleSheetChanged();
+  }
 }
 
 Element*
 ShadowRoot::GetElementById(const nsAString& aElementId)
 {
   nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aElementId);
   return entry ? entry->GetIdElement() : nullptr;
 }
@@ -489,17 +502,17 @@ ShadowRoot::ApplyAuthorStyles()
 void
 ShadowRoot::SetApplyAuthorStyles(bool aApplyAuthorStyles)
 {
   mProtoBinding->SetInheritsStyle(aApplyAuthorStyles);
 
   nsIPresShell* shell = OwnerDoc()->GetShell();
   if (shell) {
     OwnerDoc()->BeginUpdate(UPDATE_STYLE);
-    shell->RestyleShadowRoot(this);
+    shell->RecordShadowStyleChange(this);
     OwnerDoc()->EndUpdate(UPDATE_STYLE);
   }
 }
 
 StyleSheetList*
 ShadowRoot::StyleSheets()
 {
   if (!mStyleSheetList) {
--- a/content/base/src/ShadowRoot.h
+++ b/content/base/src/ShadowRoot.h
@@ -122,18 +122,18 @@ public:
     GetElementsByTagName(const nsAString& aNamespaceURI);
   already_AddRefed<nsContentList>
     GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                            const nsAString& aLocalName);
   already_AddRefed<nsContentList>
     GetElementsByClassName(const nsAString& aClasses);
   void GetInnerHTML(nsAString& aInnerHTML);
   void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
+  void StyleSheetChanged();
 protected:
-  void Restyle();
 
   // The pool host is the parent of the nodes that will be distributed
   // into the insertion points in this ShadowRoot. See |ChangeShadowRoot|.
   nsCOMPtr<nsIContent> mPoolHost;
 
   // An array of content insertion points that are a descendant of the ShadowRoot
   // sorted in tree order. Insertion points are responsible for notifying
   // the ShadowRoot when they are removed or added as a descendant. The insertion
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -487,16 +487,17 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
     DataSlots()->mBindingParent = aBindingParent; // Weak, so no addref happens.
     if (aParent->IsInNativeAnonymousSubtree()) {
       SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
     }
     if (aParent->HasFlag(NODE_CHROME_ONLY_ACCESS)) {
       SetFlags(NODE_CHROME_ONLY_ACCESS);
     }
     if (aParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
+      ClearSubtreeRootPointer();
       SetFlags(NODE_IS_IN_SHADOW_TREE);
     }
     ShadowRoot* parentContainingShadow = aParent->GetContainingShadow();
     if (parentContainingShadow) {
       DataSlots()->mContainingShadow = parentContainingShadow;
     }
   }
 
@@ -522,18 +523,19 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
 
     // XXX See the comment in Element::BindToTree
     SetInDocument();
     if (mText.IsBidi()) {
       aDocument->SetBidiEnabled();
     }
     // Clear the lazy frame construction bits.
     UnsetFlags(NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES);
-  } else {
-    // If we're not in the doc, update our subtree pointer.
+  } else if (!HasFlag(NODE_IS_IN_SHADOW_TREE)) {
+    // If we're not in the doc and not in a shadow tree,
+    // update our subtree pointer.
     SetSubtreeRootPointer(aParent->SubtreeRoot());
   }
 
   nsNodeUtils::ParentChainChanged(this);
 
   UpdateEditableState(false);
 
   NS_POSTCONDITION(aDocument == GetCurrentDoc(), "Bound to wrong document");
@@ -544,20 +546,17 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
   return NS_OK;
 }
 
 void
 nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // Unset frame flags; if we need them again later, they'll get set again.
   UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
-             NS_REFRAME_IF_WHITESPACE |
-             // Also unset the shadow tree flag because it can
-             // no longer be a descendant of a ShadowRoot.
-             NODE_IS_IN_SHADOW_TREE);
+             NS_REFRAME_IF_WHITESPACE);
   
   nsIDocument *document = GetCurrentDoc();
   if (document) {
     // Notify XBL- & nsIAnonymousContentCreator-generated
     // anonymous content that the document is changing.
     // This is needed to update the insertion point.
     document->BindingManager()->RemovedFromDocument(this, document);
   }
@@ -566,16 +565,17 @@ nsGenericDOMDataNode::UnbindFromTree(boo
     if (GetParent()) {
       NS_RELEASE(mParent);
     } else {
       mParent = nullptr;
     }
     SetParentIsContent(false);
   }
   ClearInDocument();
+  UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
   // Begin keeping track of our subtree root.
   SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
 
   nsDataSlots *slots = GetExistingDataSlots();
   if (slots) {
     slots->mBindingParent = nullptr;
     slots->mContainingShadow = nullptr;
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -492,16 +492,17 @@ GK_ATOM(noautofocus, "noautofocus")
 GK_ATOM(keepcurrentinview, "keepcurrentinview")
 GK_ATOM(keepobjectsalive, "keepobjectsalive")
 GK_ATOM(key, "key")
 GK_ATOM(keycode, "keycode")
 GK_ATOM(keydown, "keydown")
 GK_ATOM(keygen, "keygen")
 GK_ATOM(keypress, "keypress")
 GK_ATOM(keyset, "keyset")
+GK_ATOM(keysystem, "keysystem")
 GK_ATOM(keytext, "keytext")
 GK_ATOM(keyup, "keyup")
 GK_ATOM(kind, "kind")
 GK_ATOM(label, "label")
 GK_ATOM(lang, "lang")
 GK_ATOM(language, "language")
 GK_ATOM(last, "last")
 GK_ATOM(layer, "layer")
@@ -1939,16 +1940,18 @@ GK_ATOM(onended, "onended")
 GK_ATOM(onratechange, "onratechange")
 GK_ATOM(ondurationchange, "ondurationchange")
 GK_ATOM(onvolumechange, "onvolumechange")
 GK_ATOM(onaddtrack, "onaddtrack")
 GK_ATOM(oncuechange, "oncuechange")
 GK_ATOM(oncurrentchange, "oncurrentchange")
 GK_ATOM(onenter, "onenter")
 GK_ATOM(onexit, "onexit")
+GK_ATOM(onneedkey, "onneedkey")
+GK_ATOM(needkey, "needkey")
 GK_ATOM(onremovetrack, "onremovetrack")
 GK_ATOM(loadstart, "loadstart")
 GK_ATOM(suspend, "suspend")
 GK_ATOM(emptied, "emptied")
 GK_ATOM(stalled, "stalled")
 GK_ATOM(play, "play")
 GK_ATOM(pause, "pause")
 GK_ATOM(loadedmetadata, "loadedmetadata")
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -230,16 +230,51 @@ nsINode::GetTextEditorRootContent(nsIEdi
     nsIContent* rootContent = GetEditorRootContent(editor);
     if (aEditor)
       editor.swap(*aEditor);
     return rootContent;
   }
   return nullptr;
 }
 
+nsINode*
+nsINode::SubtreeRoot() const
+{
+  // There are four cases of interest here.  nsINodes that are really:
+  // 1. nsIDocument nodes - Are always in the document.
+  // 2.a nsIContent nodes not in a shadow tree - Are either in the document,
+  //     or mSubtreeRoot is updated in BindToTree/UnbindFromTree.
+  // 2.b nsIContent nodes in a shadow tree - Are never in the document,
+  //     ignore mSubtreeRoot and return the containing shadow root.
+  // 4. nsIAttribute nodes - Are never in the document, and mSubtreeRoot
+  //    is always 'this' (as set in nsINode's ctor).
+  nsINode* node;
+  if (IsInDoc()) {
+    node = OwnerDocAsNode();
+  } else if (IsContent()) {
+    ShadowRoot* containingShadow = AsContent()->GetContainingShadow();
+    node = containingShadow ? containingShadow : mSubtreeRoot;
+  } else {
+    node = mSubtreeRoot;
+  }
+  NS_ASSERTION(node, "Should always have a node here!");
+#ifdef DEBUG
+  {
+    const nsINode* slowNode = this;
+    const nsINode* iter = slowNode;
+    while ((iter = iter->GetParentNode())) {
+      slowNode = iter;
+    }
+
+    NS_ASSERTION(slowNode == node, "These should always be in sync!");
+  }
+#endif
+  return node;
+}
+
 static nsIContent* GetRootForContentSubtree(nsIContent* aContent)
 {
   NS_ENSURE_TRUE(aContent, nullptr);
 
   // Special case for ShadowRoot because the ShadowRoot itself is
   // the root. This is necessary to prevent selection from crossing
   // the ShadowRoot boundary.
   ShadowRoot* containingShadow = aContent->GetContainingShadow();
@@ -263,33 +298,33 @@ nsINode::GetSelectionRootContent(nsIPres
 {
   NS_ENSURE_TRUE(aPresShell, nullptr);
 
   if (IsNodeOfType(eDOCUMENT))
     return static_cast<nsIDocument*>(this)->GetRootElement();
   if (!IsNodeOfType(eCONTENT))
     return nullptr;
 
-  if (GetCurrentDoc() != aPresShell->GetDocument()) {
+  if (GetCrossShadowCurrentDoc() != aPresShell->GetDocument()) {
     return nullptr;
   }
 
   if (static_cast<nsIContent*>(this)->HasIndependentSelection()) {
     // This node should be a descendant of input/textarea editor.
     nsIContent* content = GetTextEditorRootContent();
     if (content)
       return content;
   }
 
   nsPresContext* presContext = aPresShell->GetPresContext();
   if (presContext) {
     nsIEditor* editor = nsContentUtils::GetHTMLEditor(presContext);
     if (editor) {
       // This node is in HTML editor.
-      nsIDocument* doc = GetCurrentDoc();
+      nsIDocument* doc = GetCrossShadowCurrentDoc();
       if (!doc || doc->HasFlag(NODE_IS_EDITABLE) ||
           !HasFlag(NODE_IS_EDITABLE)) {
         nsIContent* editorRoot = GetEditorRootContent(editor);
         NS_ENSURE_TRUE(editorRoot, nullptr);
         return nsContentUtils::IsInSameAnonymousTree(this, editorRoot) ?
                  editorRoot :
                  GetRootForContentSubtree(static_cast<nsIContent*>(this));
       }
@@ -343,16 +378,27 @@ nsINode::ChildNodes()
 }
 
 void
 nsINode::GetTextContentInternal(nsAString& aTextContent)
 {
   SetDOMStringToNull(aTextContent);
 }
 
+nsIDocument*
+nsINode::GetCrossShadowCurrentDocInternal() const
+{
+  MOZ_ASSERT(HasFlag(NODE_IS_IN_SHADOW_TREE) && IsContent(),
+             "Should only be caled on nodes in the shadow tree.");
+
+  // Cross ShadowRoot boundary.
+  ShadowRoot* containingShadow = AsContent()->GetContainingShadow();
+  return containingShadow->GetHost()->GetCrossShadowCurrentDoc();
+}
+
 #ifdef DEBUG
 void
 nsINode::CheckNotNativeAnonymous() const
 {
   if (!IsNodeOfType(eCONTENT))
     return;
   nsIContent* content = static_cast<const nsIContent *>(this)->GetBindingParent();
   while (content) {
@@ -1456,17 +1502,17 @@ nsINode::doInsertChildAt(nsIContent* aKi
   nsresult rv;
 
   // The id-handling code, and in the future possibly other code, need to
   // react to unexpected attribute changes.
   nsMutationGuard::DidMutate();
 
   // Do this before checking the child-count since this could cause mutations
   nsIDocument* doc = GetCurrentDoc();
-  mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, aNotify);
+  mozAutoDocUpdate updateBatch(GetCrossShadowCurrentDoc(), UPDATE_CONTENT_MODEL, aNotify);
 
   if (OwnerDoc() != aKid->OwnerDoc()) {
     rv = AdoptNodeIntoOwnerDoc(this, aKid);
     NS_ENSURE_SUCCESS(rv, rv);
   } else if (OwnerDoc()->DidDocumentOpen()) {
     rv = CheckForOutdatedParent(this, aKid);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -1596,20 +1642,17 @@ void
 nsINode::doRemoveChildAt(uint32_t aIndex, bool aNotify,
                          nsIContent* aKid, nsAttrAndChildArray& aChildArray)
 {
   NS_PRECONDITION(aKid && aKid->GetParentNode() == this &&
                   aKid == GetChildAt(aIndex) &&
                   IndexOf(aKid) == (int32_t)aIndex, "Bogus aKid");
 
   nsMutationGuard::DidMutate();
-
-  nsIDocument* doc = GetCurrentDoc();
-
-  mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, aNotify);
+  mozAutoDocUpdate updateBatch(GetCrossShadowCurrentDoc(), UPDATE_CONTENT_MODEL, aNotify);
 
   nsIContent* previousSibling = aKid->GetPreviousSibling();
 
   if (GetFirstChild() == aKid) {
     mFirstChild = aKid->GetNextSibling();
   }
 
   aChildArray.RemoveChildAt(aIndex);
@@ -2037,17 +2080,17 @@ nsINode::ReplaceOrInsertBefore(bool aRep
             aError.Throw(NS_ERROR_DOM_HIERARCHY_REQUEST_ERR);
             return nullptr;
           }
         }
       }
     }
   }
 
-  mozAutoDocUpdate batch(GetCurrentDoc(), UPDATE_CONTENT_MODEL, true);
+  mozAutoDocUpdate batch(GetCrossShadowCurrentDoc(), UPDATE_CONTENT_MODEL, true);
   nsAutoMutationBatch mb;
 
   // Figure out which index we want to insert at.  Note that we use
   // nodeToInsertBefore to determine this, because it's possible that
   // aRefChild == aNewChild, in which case we just removed it from the
   // parent list.
   int32_t insPos;
   if (nodeToInsertBefore) {
--- a/content/base/src/nsScriptElement.cpp
+++ b/content/base/src/nsScriptElement.cpp
@@ -109,18 +109,18 @@ bool
 nsScriptElement::MaybeProcessScript()
 {
   nsCOMPtr<nsIContent> cont =
     do_QueryInterface((nsIScriptElement*) this);
 
   NS_ASSERTION(cont->DebugGetSlots()->mMutationObservers.Contains(this),
                "You forgot to add self as observer");
 
-  if (mAlreadyStarted || !mDoneAddingChildren || !cont->IsInDoc() ||
-      mMalformed || !HasScriptContent()) {
+  if (mAlreadyStarted || !mDoneAddingChildren ||
+      !cont->GetCrossShadowCurrentDoc() || mMalformed || !HasScriptContent()) {
     return false;
   }
 
   FreezeUriAsyncDefer();
 
   mAlreadyStarted = true;
 
   nsIDocument* ownerDoc = cont->OwnerDoc();
--- a/content/base/src/nsStyleLinkElement.cpp
+++ b/content/base/src/nsStyleLinkElement.cpp
@@ -312,21 +312,26 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
   ShadowRoot* containingShadow = thisContent->GetContainingShadow();
   if (thisContent->IsHTML(nsGkAtoms::link) &&
       (aOldShadowRoot || containingShadow)) {
     return NS_OK;
   }
 
   Element* oldScopeElement = GetScopeElement(mStyleSheet);
 
-  if (mStyleSheet && aOldDocument) {
-    // We're removing the link element from the document, unload the
-    // stylesheet.  We want to do this even if updates are disabled, since
-    // otherwise a sheet with a stale linking element pointer will be hanging
-    // around -- not good!
+  if (mStyleSheet && (aOldDocument || aOldShadowRoot)) {
+    MOZ_ASSERT(!(aOldDocument && aOldShadowRoot),
+               "ShadowRoot content is never in document, thus "
+               "there should not be a old document and old "
+               "ShadowRoot simultaneously.");
+
+    // We're removing the link element from the document or shadow tree,
+    // unload the stylesheet.  We want to do this even if updates are
+    // disabled, since otherwise a sheet with a stale linking element pointer
+    // will be hanging around -- not good!
     if (aOldShadowRoot) {
       aOldShadowRoot->RemoveSheet(mStyleSheet);
     } else {
       aOldDocument->BeginUpdate(UPDATE_STYLE);
       aOldDocument->RemoveStyleSheet(mStyleSheet);
       aOldDocument->EndUpdate(UPDATE_STYLE);
     }
 
@@ -337,18 +342,17 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
   }
 
   // When static documents are created, stylesheets are cloned manually.
   if (mDontLoadStyle || !mUpdatesEnabled ||
       thisContent->OwnerDoc()->IsStaticDocument()) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIDocument> doc = thisContent->GetDocument();
-
+  nsCOMPtr<nsIDocument> doc = thisContent->GetCrossShadowCurrentDoc();
   if (!doc || !doc->CSSLoader()->GetEnabled()) {
     return NS_OK;
   }
 
   bool isInline;
   nsCOMPtr<nsIURI> uri = GetStyleSheetURL(&isInline);
 
   if (!aForceUpdate && mStyleSheet && !isInline && uri) {
--- a/content/base/test/csp/test_301_redirect.html
+++ b/content/base/test/csp/test_301_redirect.html
@@ -23,20 +23,16 @@ Test that CSP violation reports are not 
 // This is used to watch the redirect of the report POST get blocked
 function examiner() {
   SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
   SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
 }
 
 examiner.prototype  = {
   observe: function(subject, topic, data) {
-    // subject should be an nsURI
-    if(!SpecialPowers.can_QI(subject))
-       return;
-
     if (topic === "specialpowers-http-notify-request") {
       // this is used to fail the test - if we see the POST to the target of the redirect
       // we know this is a fail
       var uri = data;
       if (uri == "http://example.com/some/fake/path")
         window.done(false);
     }
 
--- a/content/base/test/csp/test_302_redirect.html
+++ b/content/base/test/csp/test_302_redirect.html
@@ -23,20 +23,16 @@ Test that CSP violation reports are not 
 // This is used to watch the redirect of the report POST get blocked
 function examiner() {
   SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
   SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
 }
 
 examiner.prototype  = {
   observe: function(subject, topic, data) {
-    // subject should be an nsURI
-    if(!SpecialPowers.can_QI(subject))
-       return;
-
     if (topic === "specialpowers-http-notify-request") {
       // this is used to fail the test - if we see the POST to the target of the redirect
       // we know this is a fail
       var uri = data;
       if (uri == "http://example.com/some/fake/path")
         window.done(false);
     }
 
--- a/content/base/test/csp/test_303_redirect.html
+++ b/content/base/test/csp/test_303_redirect.html
@@ -23,20 +23,16 @@ Test that CSP violation reports are not 
 // This is used to watch the redirect of the report POST get blocked
 function examiner() {
   SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
   SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
 }
 
 examiner.prototype  = {
   observe: function(subject, topic, data) {
-    // subject should be an nsURI
-    if(!SpecialPowers.can_QI(subject))
-       return;
-
     if (topic === "specialpowers-http-notify-request") {
       // this is used to fail the test - if we see the POST to the target of the redirect
       // we know this is a fail
       var uri = data;
       if (uri == "http://example.com/some/fake/path")
         window.done(false);
     }
 
--- a/content/base/test/csp/test_307_redirect.html
+++ b/content/base/test/csp/test_307_redirect.html
@@ -23,20 +23,16 @@ Test that CSP violation reports are not 
 // This is used to watch the redirect of the report POST get blocked
 function examiner() {
   SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
   SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
 }
 
 examiner.prototype  = {
   observe: function(subject, topic, data) {
-    // subject should be an nsURI
-    if(!SpecialPowers.can_QI(subject))
-       return;
-
     if (topic === "specialpowers-http-notify-request") {
       // this is used to fail the test - if we see the POST to the target of the redirect
       // we know this is a fail
       var uri = data;
       if (uri == "http://example.com/some/fake/path")
         window.done(false);
     }
 
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -15,16 +15,26 @@
 #include "DOMMediaStream.h"
 #include "AudioChannelCommon.h"
 #include "DecoderTraits.h"
 #include "nsIAudioChannelAgent.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 #include "mozilla/dom/TextTrackManager.h"
 #include "MediaDecoder.h"
+#include "mozilla/dom/MediaKeys.h"
+
+// Something on Linux #defines None, which is an entry in the
+// MediaWaitingFor enum, so undef it here before including the binfing,
+// so that the build doesn't fail...
+#ifdef None
+#undef None
+#endif
+
+#include "mozilla/dom/HTMLMediaElementBinding.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 class nsIChannel;
 class nsIHttpChannel;
 class nsILoadGroup;
 
@@ -32,16 +42,17 @@ typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
 namespace dom {
+class MediaKeys;
 class TextTrack;
 class TimeRanges;
 class WakeLock;
 }
 }
 
 class nsITimer;
 class nsRange;
@@ -462,27 +473,53 @@ public:
     return GetBoolAttr(nsGkAtoms::muted);
   }
 
   void SetDefaultMuted(bool aMuted, ErrorResult& aRv)
   {
     SetHTMLBoolAttr(nsGkAtoms::muted, aMuted, aRv);
   }
 
+  bool MozMediaStatisticsShowing() const
+  {
+    return mStatsShowing;
+  }
+
+  void SetMozMediaStatisticsShowing(bool aShow)
+  {
+    mStatsShowing = aShow;
+  }
+
   already_AddRefed<DOMMediaStream> GetMozSrcObject() const;
 
   void SetMozSrcObject(DOMMediaStream& aValue);
 
   bool MozPreservesPitch() const
   {
     return mPreservesPitch;
   }
 
   // XPCOM MozPreservesPitch() is OK
 
+  MediaKeys* GetMediaKeys() const;
+
+  already_AddRefed<Promise> SetMediaKeys(MediaKeys* mediaKeys,
+                                         ErrorResult& aRv);
+  
+  MediaWaitingFor WaitingFor() const;
+
+  mozilla::dom::EventHandlerNonNull* GetOnneedkey();
+  void SetOnneedkey(mozilla::dom::EventHandlerNonNull* listener);
+
+  void DispatchNeedKey(const nsTArray<uint8_t>& aInitData,
+                       const nsAString& aInitDataType);
+
+
+  bool IsEventAttributeName(nsIAtom* aName) MOZ_OVERRIDE;
+
   bool MozAutoplayEnabled() const
   {
     return mAutoplayEnabled;
   }
 
   already_AddRefed<DOMMediaStream> MozCaptureStream(ErrorResult& aRv);
 
   already_AddRefed<DOMMediaStream> MozCaptureStreamUntilEnded(ErrorResult& aRv);
@@ -1005,16 +1042,19 @@ protected:
 
   // Reference to the source element last returned by GetNextSource().
   // This is the child source element which we're trying to load from.
   nsCOMPtr<nsIContent> mSourceLoadCandidate;
 
   // Range of time played.
   nsRefPtr<TimeRanges> mPlayed;
 
+  // Encrypted Media Extension media keys.
+  nsRefPtr<MediaKeys> mMediaKeys;
+
   // Stores the time at the start of the current 'played' range.
   double mCurrentPlayRangeStart;
 
   // If true then we have begun downloading the media content.
   // Set to false when completed, or not yet started.
   bool mBegun;
 
   // True when the decoder has loaded enough data to display the
@@ -1043,16 +1083,20 @@ protected:
   enum MutedReasons {
     MUTED_BY_CONTENT               = 0x01,
     MUTED_BY_INVALID_PLAYBACK_RATE = 0x02,
     MUTED_BY_AUDIO_CHANNEL         = 0x04
   };
 
   uint32_t mMuted;
 
+  // True if the media statistics are currently being shown by the builtin
+  // video controls
+  bool mStatsShowing;
+
   // True if the sound is being captured.
   bool mAudioCaptured;
 
   // If TRUE then the media element was actively playing before the currently
   // in progress seeking. If FALSE then the media element is either not seeking
   // or was not actively playing before the current seek. Used to decide whether
   // to raise the 'waiting' event as per 4.7.1.8 in HTML 5 specification.
   bool mPlayingBeforeSeek;
@@ -1134,14 +1178,16 @@ protected:
 
   // Is this media element playing?
   bool mPlayingThroughTheAudioChannel;
 
   // An agent used to join audio channel service.
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
 
   nsRefPtr<TextTrackManager> mTextTrackManager;
+
+  MediaWaitingFor mWaitingFor;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLMediaElement_h
--- a/content/html/content/src/HTMLFormElement.cpp
+++ b/content/html/content/src/HTMLFormElement.cpp
@@ -5,16 +5,17 @@
 
 #include "mozilla/dom/HTMLFormElement.h"
 
 #include "jsapi.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
+#include "mozilla/dom/AutocompleteErrorEvent.h"
 #include "mozilla/dom/HTMLFormControlsCollection.h"
 #include "mozilla/dom/HTMLFormElementBinding.h"
 #include "mozilla/Move.h"
 #include "nsIHTMLDocument.h"
 #include "nsGkAtoms.h"
 #include "nsStyleConsts.h"
 #include "nsPresContext.h"
 #include "nsIDocument.h"
@@ -23,16 +24,17 @@
 #include "nsError.h"
 #include "nsContentUtils.h"
 #include "nsInterfaceHashtable.h"
 #include "nsContentList.h"
 #include "nsCOMArray.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "nsIMutableArray.h"
+#include "nsIAutofillController.h"
 
 // form submission
 #include "nsIFormSubmitObserver.h"
 #include "nsIObserverService.h"
 #include "nsICategoryManager.h"
 #include "nsCategoryManagerUtils.h"
 #include "nsISimpleEnumerator.h"
 #include "nsRange.h"
@@ -295,16 +297,38 @@ HTMLFormElement::Reset()
 
 NS_IMETHODIMP
 HTMLFormElement::CheckValidity(bool* retVal)
 {
   *retVal = CheckValidity();
   return NS_OK;
 }
 
+void
+HTMLFormElement::RequestAutocomplete()
+{
+  bool dummy;
+  nsCOMPtr<nsIDOMWindow> win = do_QueryInterface(OwnerDoc()->GetScriptHandlingObject(dummy));
+  nsCOMPtr<nsIAutofillController> controller(do_GetService("@mozilla.org/autofill-controller;1"));
+
+  if (!controller || !win) {
+    AutocompleteErrorEventInit init;
+    init.mBubbles = true;
+    init.mCancelable = false;
+    init.mReason = AutoCompleteErrorReason::Disabled;
+
+    nsRefPtr<AutocompleteErrorEvent> event =
+      AutocompleteErrorEvent::Constructor(this, NS_LITERAL_STRING("autocompleteerror"), init);
+    (new AsyncEventDispatcher(this, event))->PostDOMEvent();
+    return;
+  }
+
+  controller->RequestAutocomplete(this, win);
+}
+
 bool
 HTMLFormElement::ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::method) {
--- a/content/html/content/src/HTMLFormElement.h
+++ b/content/html/content/src/HTMLFormElement.h
@@ -405,16 +405,18 @@ public:
 #ifdef DEBUG
   static void
   AssertDocumentOrder(const nsTArray<nsGenericHTMLFormElement*>& aControls,
                       nsIContent* aForm);
 #endif
 
   js::ExpandoAndGeneration mExpandoAndGeneration;
 
+  void RequestAutocomplete();
+
 protected:
   virtual JSObject* WrapNode(JSContext* aCx) MOZ_OVERRIDE;
 
   void PostPasswordEvent();
   void EventHandled() { mFormPasswordEventDispatcher = nullptr; }
 
   class FormPasswordEventDispatcher MOZ_FINAL : public AsyncEventDispatcher
   {
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -185,23 +185,23 @@ static const nsAttrValue::EnumTable kInp
   { "titlecase", NS_INPUT_INPUTMODE_TITLECASE },
   { "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED },
   { 0 }
 };
 
 // Default inputmode value is "auto".
 static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
 
-const Decimal HTMLInputElement::kStepScaleFactorDate = 86400000;
-const Decimal HTMLInputElement::kStepScaleFactorNumberRange = 1;
-const Decimal HTMLInputElement::kStepScaleFactorTime = 1000;
-const Decimal HTMLInputElement::kDefaultStepBase = 0;
-const Decimal HTMLInputElement::kDefaultStep = 1;
-const Decimal HTMLInputElement::kDefaultStepTime = 60;
-const Decimal HTMLInputElement::kStepAny = 0;
+const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
+const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
+const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
+const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
+const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
+const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
+const Decimal HTMLInputElement::kStepAny = Decimal(0);
 
 #define NS_INPUT_ELEMENT_STATE_IID                 \
 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */       \
   0xdc3b3d14,                                      \
   0x23e2,                                          \
   0x4479,                                          \
   {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
 }
@@ -1749,17 +1749,17 @@ HTMLInputElement::ConvertStringToNumber(
         return true;
       }
     case NS_FORM_INPUT_TIME:
       uint32_t milliseconds;
       if (!ParseTime(aValue, &milliseconds)) {
         return false;
       }
 
-      aResultValue = int32_t(milliseconds);
+      aResultValue = Decimal(int32_t(milliseconds));
       return true;
     default:
       MOZ_ASSERT(false, "Unrecognized input type");
       return false;
   }
 }
 
 Decimal
@@ -1924,17 +1924,17 @@ HTMLInputElement::ConvertNumberToString(
 
         return true;
       }
     case NS_FORM_INPUT_TIME:
       {
         // Per spec, we need to truncate |aValue| and we should only represent
         // times inside a day [00:00, 24:00[, which means that we should do a
         // modulo on |aValue| using the number of milliseconds in a day (86400000).
-        uint32_t value = NS_floorModulo(aValue.floor(), 86400000).toDouble();
+        uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble();
 
         uint16_t milliseconds = value % 1000;
         value /= 1000;
 
         uint8_t seconds = value % 60;
         value /= 60;
 
         uint8_t minutes = value % 60;
@@ -2050,17 +2050,17 @@ HTMLInputElement::SetValueAsNumber(doubl
 Decimal
 HTMLInputElement::GetMinimum() const
 {
   MOZ_ASSERT(DoesValueAsNumberApply(),
              "GetMinimum() should only be used for types that allow .valueAsNumber");
 
   // Only type=range has a default minimum
   Decimal defaultMinimum =
-    mType == NS_FORM_INPUT_RANGE ? 0 : Decimal::nan();
+    mType == NS_FORM_INPUT_RANGE ? Decimal(0) : Decimal::nan();
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
     return defaultMinimum;
   }
 
   nsAutoString minStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
 
@@ -2071,17 +2071,17 @@ HTMLInputElement::GetMinimum() const
 Decimal
 HTMLInputElement::GetMaximum() const
 {
   MOZ_ASSERT(DoesValueAsNumberApply(),
              "GetMaximum() should only be used for types that allow .valueAsNumber");
 
   // Only type=range has a default maximum
   Decimal defaultMaximum =
-    mType == NS_FORM_INPUT_RANGE ? 100 : Decimal::nan();
+    mType == NS_FORM_INPUT_RANGE ? Decimal(100) : Decimal::nan();
 
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
     return defaultMaximum;
   }
 
   nsAutoString maxStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
 
@@ -2133,17 +2133,17 @@ HTMLInputElement::GetValueIfStepped(int3
       return NS_ERROR_DOM_INVALID_STATE_ERR;
     }
     // Allow the spin buttons and up/down arrow keys to do something sensible:
     step = GetDefaultStep();
   }
 
   Decimal value = GetValueAsDecimal();
   if (value.isNaN()) {
-    value = 0;
+    value = Decimal(0);
   }
 
   Decimal minimum = GetMinimum();
 
   Decimal maximum = GetMaximum();
   if (!maximum.isNaN()) {
     // "max - (max - stepBase) % step" is the nearest valid value to max.
     maximum = maximum - NS_floorModulo(maximum - GetStepBase(), step);
@@ -2171,24 +2171,24 @@ HTMLInputElement::GetValueIfStepped(int3
     if (aStep > 0) {
       value -= NS_floorModulo(value - GetStepBase(), step);
     } else if (aStep < 0) {
       value -= NS_floorModulo(value - GetStepBase(), step);
       value += step;
     }
   }
 
-  value += step * aStep;
+  value += step * Decimal(aStep);
 
   // For date inputs, the value can hold a string that is not a day. We do not
   // want to round it, as it might result in a step mismatch. Instead we want to
   // clamp to the next valid value.
   if (mType == NS_FORM_INPUT_DATE &&
-      NS_floorModulo(value - GetStepBase(), GetStepScaleFactor()) != 0) {
-    MOZ_ASSERT(GetStep() > 0);
+      NS_floorModulo(Decimal(value - GetStepBase()), GetStepScaleFactor()) != Decimal(0)) {
+    MOZ_ASSERT(GetStep() > Decimal(0));
     Decimal validStep = EuclidLCM<Decimal>(GetStep().floor(),
                                            GetStepScaleFactor().floor());
     if (aStep > 0) {
       value -= NS_floorModulo(value - GetStepBase(), validStep);
       value += validStep;
     } else if (aStep < 0) {
       value -= NS_floorModulo(value - GetStepBase(), validStep);
     }
@@ -4111,20 +4111,20 @@ HTMLInputElement::PostHandleEvent(EventC
                   newValue = minimum;
                   break;
                 case  NS_VK_END:
                   newValue = maximum;
                   break;
                 case  NS_VK_PAGE_UP:
                   // For PgUp/PgDn we jump 10% of the total range, unless step
                   // requires us to jump more.
-                  newValue = value + std::max(step, (maximum - minimum) / 10);
+                  newValue = value + std::max(step, (maximum - minimum) / Decimal(10));
                   break;
                 case  NS_VK_PAGE_DOWN:
-                  newValue = value - std::max(step, (maximum - minimum) / 10);
+                  newValue = value - std::max(step, (maximum - minimum) / Decimal(10));
                   break;
               }
               SetValueOfRangeForUserEvent(newValue);
               aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
             }
           }
 
         } break; // NS_KEY_PRESS || NS_KEY_UP
@@ -4562,43 +4562,43 @@ HTMLInputElement::SanitizeValue(nsAStrin
         // parse out from aValue needs to be sanitized.
         bool needSanitization = false;
 
         Decimal value;
         bool ok = ConvertStringToNumber(aValue, value);
         if (!ok) {
           needSanitization = true;
           // Set value to midway between minimum and maximum.
-          value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/2;
+          value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/Decimal(2);
         } else if (value < minimum || maximum < minimum) {
           needSanitization = true;
           value = minimum;
         } else if (value > maximum) {
           needSanitization = true;
           value = maximum;
         }
 
         Decimal step = GetStep();
         if (step != kStepAny) {
           Decimal stepBase = GetStepBase();
           // There could be rounding issues below when dealing with fractional
           // numbers, but let's ignore that until ECMAScript supplies us with a
           // decimal number type.
           Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
-          if (deltaToStep != 0) {
+          if (deltaToStep != Decimal(0)) {
             // "suffering from a step mismatch"
             // Round the element's value to the nearest number for which the
             // element would not suffer from a step mismatch, and which is
             // greater than or equal to the minimum, and, if the maximum is not
             // less than the minimum, which is less than or equal to the
             // maximum, if there is a number that matches these constraints:
-            MOZ_ASSERT(deltaToStep > 0, "stepBelow/stepAbove will be wrong");
+            MOZ_ASSERT(deltaToStep > Decimal(0), "stepBelow/stepAbove will be wrong");
             Decimal stepBelow = value - deltaToStep;
             Decimal stepAbove = value - deltaToStep + step;
-            Decimal halfStep = step / 2;
+            Decimal halfStep = step / Decimal(2);
             bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
             bool stepAboveInRange = stepAbove >= minimum &&
                                     stepAbove <= maximum;
             bool stepBelowInRange = stepBelow >= minimum &&
                                     stepBelow <= maximum;
 
             if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
               needSanitization = true;
@@ -6271,17 +6271,17 @@ HTMLInputElement::GetStep() const
   GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
 
   if (stepStr.LowerCaseEqualsLiteral("any")) {
     // The element can't suffer from step mismatch if there is no step.
     return kStepAny;
   }
 
   Decimal step = StringToDecimal(stepStr);
-  if (!step.isFinite() || step <= 0) {
+  if (!step.isFinite() || step <= Decimal(0)) {
     step = GetDefaultStep();
   }
 
   return step * GetStepScaleFactor();
 }
 
 // nsIConstraintValidation
 
@@ -6457,30 +6457,30 @@ HTMLInputElement::HasStepMismatch(bool a
 {
   if (!DoesStepApply()) {
     return false;
   }
 
   Decimal value = GetValueAsDecimal();
   if (value.isNaN()) {
     if (aUseZeroIfValueNaN) {
-      value = 0;
+      value = Decimal(0);
     } else {
       // The element can't suffer from step mismatch if it's value isn't a number.
       return false;
     }
   }
 
   Decimal step = GetStep();
   if (step == kStepAny) {
     return false;
   }
 
   // Value has to be an integral multiple of step.
-  return NS_floorModulo(value - GetStepBase(), step) != 0;
+  return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
 }
 
 /**
  * Splits the string on the first "@" character and punycode encodes the first
  * and second parts separately before rejoining them with an "@" and returning
  * the result via the aEncodedEmail out-param. Returns false if there is no
  * "@" caracter, if the "@" character is at the start or end, or if the
  * conversion to punycode fails.
@@ -6876,17 +6876,17 @@ HTMLInputElement::GetValidationMessage(n
     case VALIDITY_STATE_STEP_MISMATCH:
     {
       nsXPIDLString message;
 
       Decimal value = GetValueAsDecimal();
       MOZ_ASSERT(!value.isNaN());
 
       Decimal step = GetStep();
-      MOZ_ASSERT(step != kStepAny && step > 0);
+      MOZ_ASSERT(step != kStepAny && step > Decimal(0));
 
       // In case this is a date and the step is not an integer, we don't want to
       // display the dates corresponding to the truncated timestamps of valueLow
       // and valueHigh because they might suffer from a step mismatch as well.
       // Instead we want the timestamps to correspond to a rounded day. That is,
       // we want a multiple of the step scale factor (1 day) as well as of step.
       if (mType == NS_FORM_INPUT_DATE) {
         step = EuclidLCM<Decimal>(step.floor(),
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -4,16 +4,18 @@
  * 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 "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLMediaElementBinding.h"
 #include "mozilla/dom/ElementInlines.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/MathAlgorithms.h"
+#include "mozilla/dom/MediaKeyNeededEvent.h"
+#include "mozilla/AsyncEventDispatcher.h"
 
 #include "base/basictypes.h"
 #include "nsIDOMHTMLMediaElement.h"
 #include "nsIDOMHTMLSourceElement.h"
 #include "TimeRanges.h"
 #include "nsGenericHTMLElement.h"
 #include "nsAttrValueInlines.h"
 #include "nsPresContext.h"
@@ -418,36 +420,38 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream);
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
   if (tmp->mSrcStream) {
     // Need to EndMediaStreamPlayback to clear mSrcStream and make sure everything
     // gets unhooked correctly.
     tmp->EndSrcMediaStreamPlayback();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
   for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
-    NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams[i].mStream);
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams[i].mStream)
   }
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed);
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 // nsIDOMHTMLMediaElement
@@ -890,16 +894,19 @@ void HTMLMediaElement::LoadFromSourceChi
     nsAutoString type;
     if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
         GetCanPlay(type) == CANPLAY_NO) {
       DispatchAsyncSourceError(child);
       const char16_t* params[] = { type.get(), src.get() };
       ReportLoadError("MediaLoadUnsupportedTypeAttribute", params, ArrayLength(params));
       continue;
     }
+    // TODO: "If candidate has a keySystem attribute whose value represents a
+    //       Key System that the user agent knows it cannot use with type,
+    //       then end the synchronous section[...]" (Bug 1016707)
     nsAutoString media;
     if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::media, media) && !media.IsEmpty()) {
       nsCSSParser cssParser;
       nsRefPtr<nsMediaList> mediaList(new nsMediaList());
       cssParser.ParseMediaList(media, nullptr, 0, mediaList, false);
       nsIPresShell* presShell = OwnerDoc()->GetShell();
       if (presShell && !mediaList->Matches(presShell->GetPresContext(), nullptr)) {
         DispatchAsyncSourceError(child);
@@ -1977,16 +1984,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mPlayed(new TimeRanges),
     mCurrentPlayRangeStart(-1.0),
     mBegun(false),
     mLoadedFirstFrame(false),
     mAutoplaying(true),
     mAutoplayEnabled(true),
     mPaused(true),
     mMuted(0),
+    mStatsShowing(false),
     mAudioCaptured(false),
     mPlayingBeforeSeek(false),
     mPausedForInactiveDocumentOrChannel(false),
     mEventDeliveryPaused(false),
     mWaitingFired(false),
     mIsRunningLoadMethod(false),
     mIsLoadingFromSourceChildren(false),
     mDelayingLoadEvent(false),
@@ -1998,17 +2006,18 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mHasSelfReference(false),
     mShuttingDown(false),
     mSuspendedForPreloadNone(false),
     mMediaSecurityVerified(false),
     mCORSMode(CORS_NONE),
     mHasAudio(false),
     mDownloadSuspendedByCache(false),
     mAudioChannelFaded(false),
-    mPlayingThroughTheAudioChannel(false)
+    mPlayingThroughTheAudioChannel(false),
+    mWaitingFor(MediaWaitingFor::None)
 {
 #ifdef PR_LOGGING
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
   }
@@ -3889,16 +3898,82 @@ NS_IMETHODIMP HTMLMediaElement::CanPlayC
 
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
   UpdateChannelMuteState(static_cast<AudioChannelState>(canPlay));
   mPaused.SetCanPlay(canPlay != AUDIO_CHANNEL_STATE_MUTED);
   return NS_OK;
 }
 
+MediaKeys*
+HTMLMediaElement::GetMediaKeys() const
+{
+  return mMediaKeys;
+}
+
+already_AddRefed<Promise>
+HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
+                               ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global =
+    do_QueryInterface(OwnerDoc()->GetInnerWindow());
+  if (!global) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+  // TODO: Need to shutdown existing MediaKeys instance? bug 1016709.
+  nsRefPtr<Promise> promise = new Promise(global);
+  if (mMediaKeys != aMediaKeys) {
+    mMediaKeys = aMediaKeys;
+  }
+  promise->MaybeResolve(JS::UndefinedHandleValue);
+  return promise.forget();
+}
+
+MediaWaitingFor
+HTMLMediaElement::WaitingFor() const
+{
+  return mWaitingFor;
+}
+
+EventHandlerNonNull*
+HTMLMediaElement::GetOnneedkey()
+{
+  EventListenerManager *elm = GetExistingListenerManager();
+  return elm ? elm->GetEventHandler(nsGkAtoms::onneedkey, EmptyString())
+              : nullptr;
+}
+
+void
+HTMLMediaElement::SetOnneedkey(EventHandlerNonNull* handler)
+{
+  EventListenerManager *elm = GetOrCreateListenerManager();
+  if (elm) {
+    elm->SetEventHandler(nsGkAtoms::onneedkey, EmptyString(), handler);
+  }
+}
+
+void
+HTMLMediaElement::DispatchNeedKey(const nsTArray<uint8_t>& aInitData,
+                                  const nsAString& aInitDataType)
+{
+  nsRefPtr<MediaKeyNeededEvent> event(
+    MediaKeyNeededEvent::Constructor(this, aInitDataType, aInitData));
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, event);
+  asyncDispatcher->PostDOMEvent();
+}
+
+bool
+HTMLMediaElement::IsEventAttributeName(nsIAtom* aName)
+{
+  return aName == nsGkAtoms::onneedkey ||
+         nsGenericHTMLElement::IsEventAttributeName(aName);
+}
+
 NS_IMETHODIMP HTMLMediaElement::WindowVolumeChanged()
 {
   SetVolumeInternal();
   return NS_OK;
 }
 
 /* readonly attribute TextTrackList textTracks; */
 TextTrackList*
--- a/content/html/content/src/HTMLScriptElement.cpp
+++ b/content/html/content/src/HTMLScriptElement.cpp
@@ -57,17 +57,17 @@ HTMLScriptElement::BindToTree(nsIDocumen
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (aDocument) {
+  if (GetCrossShadowCurrentDoc()) {
     MaybeProcessScript();
   }
 
   return NS_OK;
 }
 
 bool
 HTMLScriptElement::ParseAttribute(int32_t aNamespaceID,
--- a/content/html/content/src/HTMLSourceElement.h
+++ b/content/html/content/src/HTMLSourceElement.h
@@ -59,16 +59,26 @@ public:
   {
     GetHTMLAttr(nsGkAtoms::media, aMedia);
   }
   void SetMedia(const nsAString& aMedia, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::media, aMedia, rv);
   }
 
+  void GetKeySystem(nsString& aKeySystem) const
+  {
+    GetHTMLAttr(nsGkAtoms::keysystem, aKeySystem);
+  }
+
+  void SetKeySystem(const nsAString& aKeySystem)
+  {
+    SetHTMLAttr(nsGkAtoms::keysystem, aKeySystem);
+  }
+
 protected:
   virtual JSObject* WrapNode(JSContext* aCx) MOZ_OVERRIDE;
 
 protected:
   virtual void GetItemValueText(nsAString& text) MOZ_OVERRIDE;
   virtual void SetItemValueText(const nsAString& text) MOZ_OVERRIDE;
 };
 
new file mode 100644
--- /dev/null
+++ b/content/media/eme/CDMProxy.cpp
@@ -0,0 +1,199 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/CDMProxy.h"
+#include "nsString.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "MainThreadUtils.h"
+
+// TODO: Change the functions in this file to do IPC via the Gecko Media
+// Plugins API. In the meantime, the code here merely implements the
+// interface we expect will be required when the IPC is working.
+
+namespace mozilla {
+
+CDMProxy::CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem)
+  : mKeys(aKeys)
+  , mKeySystem(aKeySystem)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_COUNT_CTOR(CDMProxy);
+}
+
+CDMProxy::~CDMProxy()
+{
+  MOZ_COUNT_DTOR(CDMProxy);
+}
+
+void
+CDMProxy::Init(PromiseId aPromiseId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mGMPThread) {
+    nsCOMPtr<mozIGeckoMediaPluginService> mps =
+      do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+    if (!mps) {
+      RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+      return;
+    }
+    mps->GetThread(getter_AddRefs(mGMPThread));
+    if (!mGMPThread) {
+      RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
+      return;
+    }
+  }
+
+  // TODO: Dispatch task to GMPThread to initialize CDM via IPC.
+
+  mKeys->OnCDMCreated(aPromiseId);
+}
+
+static int sFakeSessionIdNum = 0;
+
+void
+CDMProxy::CreateSession(dom::SessionType aSessionType,
+                        PromiseId aPromiseId,
+                        const nsAString& aInitDataType,
+                        const Uint8Array& aInitData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mGMPThread);
+
+  // TODO: Dispatch task to GMPThread to call CDM CreateSession via IPC.
+
+  // Make a fake session id. We'll get this from the CDM normally.
+  nsAutoString id;
+  id.AppendASCII("FakeSessionId_");
+  id.AppendInt(sFakeSessionIdNum++);
+
+  mKeys->OnSessionActivated(aPromiseId, id);
+}
+
+void
+CDMProxy::LoadSession(PromiseId aPromiseId,
+                      const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mGMPThread);
+
+  // TODO: Dispatch task to GMPThread to call CDM LoadSession via IPC.
+  // make MediaKeys::mPendingSessions CC'd
+
+  mKeys->OnSessionActivated(aPromiseId, aSessionId);
+}
+
+void
+CDMProxy::SetServerCertificate(PromiseId aPromiseId,
+                               const Uint8Array& aCertData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mGMPThread);
+
+  // TODO: Dispatch task to GMPThread to call CDM SetServerCertificate via IPC.
+
+  ResolvePromise(aPromiseId);
+}
+
+static int sUpdateCount = 0;
+
+void
+CDMProxy::UpdateSession(const nsAString& aSessionId,
+                        PromiseId aPromiseId,
+                        const Uint8Array& aResponse)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mGMPThread);
+  NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+  // TODO: Dispatch task to GMPThread to call CDM UpdateSession via IPC.
+
+  nsRefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  nsAutoCString str(NS_LITERAL_CSTRING("Update_"));
+  str.AppendInt(sUpdateCount++);
+  nsTArray<uint8_t> msg;
+  msg.AppendElements(str.get(), str.Length());
+  session->DispatchKeyMessage(msg, NS_LITERAL_STRING("http://bogus.url"));
+  ResolvePromise(aPromiseId);
+}
+
+void
+CDMProxy::CloseSession(const nsAString& aSessionId,
+                       PromiseId aPromiseId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+  // TODO: Dispatch task to GMPThread to call CDM CloseSession via IPC.
+
+  nsRefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+
+  // Pretend that the CDM actually does close the session...
+  // "...the [MediaKeySession's] closed attribute promise is resolved
+  // when the session is closed."
+  session->OnClosed();
+
+  // "The promise is resolved when the request has been processed."
+  ResolvePromise(aPromiseId);
+}
+
+void
+CDMProxy::RemoveSession(const nsAString& aSessionId,
+                        PromiseId aPromiseId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // TODO: Dispatch task to GMPThread to call CDM RemoveSession via IPC.
+
+  // Assume CDM immediately removes session's data, then close the session
+  // as per the spec.
+  CloseSession(aSessionId, aPromiseId);
+}
+
+void
+CDMProxy::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mKeys.Clear();
+}
+
+void
+CDMProxy::RejectPromise(PromiseId aId, nsresult aCode)
+{
+  if (NS_IsMainThread()) {
+    if (!mKeys.IsNull()) {
+      mKeys->RejectPromise(aId, aCode);
+    } else {
+      NS_WARNING("CDMProxy unable to reject promise!");
+    }
+  } else {
+    nsRefPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode));
+    NS_DispatchToMainThread(task);
+  }
+}
+
+void
+CDMProxy::ResolvePromise(PromiseId aId)
+{
+  if (NS_IsMainThread()) {
+    if (!mKeys.IsNull()) {
+      mKeys->ResolvePromise(aId);
+    } else {
+      NS_WARNING("CDMProxy unable to resolve promise!");
+    }
+  } else {
+    nsRefPtr<nsIRunnable> task;
+    task = NS_NewRunnableMethodWithArg<PromiseId>(this,
+                                                  &CDMProxy::ResolvePromise,
+                                                  aId);
+    NS_DispatchToMainThread(task);
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/CDMProxy.h
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 CDMProxy_h_
+#define CDMProxy_h_
+
+#include "nsString.h"
+#include "nsAutoPtr.h"
+#include "nsProxyRelease.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/TypedArray.h"
+
+class nsIThread;
+
+namespace mozilla {
+
+namespace dom {
+class MediaKeySession;
+}
+
+// A placeholder proxy to the CDM.
+// TODO: The functions here need to do IPC to talk to the CDM via the
+// Gecko Media Plugin API, which we'll need to extend for H.264 and EME
+// content.
+// Note: Promises are passed in via a PromiseId, so that the ID can be
+// passed via IPC to the CDM, which can then signal when to reject or
+// resolve the promise using its PromiseId.
+class CDMProxy {
+  typedef dom::PromiseId PromiseId;
+  typedef dom::SessionType SessionType;
+  typedef dom::Uint8Array Uint8Array;
+public:
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CDMProxy)
+
+  // Main thread only.
+  CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem);
+
+  // Main thread only.
+  // Loads the CDM corresponding to mKeySystem.
+  // Calls MediaKeys::OnCDMCreated() when the CDM is created.
+  void Init(PromiseId aPromiseId);
+
+  // Main thread only.
+  // Uses the CDM to create a key session.
+  // Caller is responsible for calling aInitData.ComputeLengthAndData().
+  // Calls MediaKeys::OnSessionActivated() when session is created.
+  void CreateSession(dom::SessionType aSessionType,
+                     PromiseId aPromiseId,
+                     const nsAString& aInitDataType,
+                     const Uint8Array& aInitData);
+
+  // Main thread only.
+  // Uses the CDM to load a presistent session stored on disk.
+  // Calls MediaKeys::OnSessionActivated() when session is loaded.
+  void LoadSession(PromiseId aPromiseId,
+                   const nsAString& aSessionId);
+
+  // Main thread only.
+  // Sends a new certificate to the CDM.
+  // Caller is responsible for calling aCert.ComputeLengthAndData().
+  // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+  // processed the request.
+  void SetServerCertificate(PromiseId aPromiseId,
+                            const Uint8Array& aCert);
+
+  // Main thread only.
+  // Sends an update to the CDM.
+  // Caller is responsible for calling aResponse.ComputeLengthAndData().
+  // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+  // processed the request.
+  void UpdateSession(const nsAString& aSessionId,
+                     PromiseId aPromiseId,
+                     const Uint8Array& aResponse);
+
+  // Main thread only.
+  // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+  // processed the request.
+  // If processing this operation results in the session actually closing,
+  // we also call MediaKeySession::OnClosed(), which in turn calls
+  // MediaKeys::OnSessionClosed().
+  void CloseSession(const nsAString& aSessionId,
+                    PromiseId aPromiseId);
+
+  // Main thread only.
+  // Removes all data for a persisent session.
+  // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
+  // processed the request.
+  void RemoveSession(const nsAString& aSessionId,
+                     PromiseId aPromiseId);
+
+  // Main thread only.
+  void Shutdown();
+
+private:
+
+  class RejectPromiseTask : public nsRunnable {
+  public:
+    RejectPromiseTask(CDMProxy* aProxy,
+                      PromiseId aId,
+                      nsresult aCode)
+      : mProxy(aProxy)
+      , mId(aId)
+      , mCode(aCode)
+    {
+    }
+    NS_METHOD Run() {
+      mProxy->RejectPromise(mId, mCode);
+      return NS_OK;
+    }
+  private:
+    nsRefPtr<CDMProxy> mProxy;
+    PromiseId mId;
+    nsresult mCode;
+  };
+
+  // Reject promise with DOMException corresponding to aExceptionCode.
+  // Can be called from any thread.
+  void RejectPromise(PromiseId aId, nsresult aExceptionCode);
+  // Resolves promise with "undefined".
+  // Can be called from any thread.
+  void ResolvePromise(PromiseId aId);
+
+  ~CDMProxy();
+
+  // Helper to enforce that a raw pointer is only accessed on the main thread.
+  template<class Type>
+  class MainThreadOnlyRawPtr {
+  public:
+    MainThreadOnlyRawPtr(Type* aPtr)
+      : mPtr(aPtr)
+    {
+      MOZ_ASSERT(NS_IsMainThread());
+    }
+
+    bool IsNull() const {
+      MOZ_ASSERT(NS_IsMainThread());
+      return !mPtr;
+    }
+
+    void Clear() {
+      MOZ_ASSERT(NS_IsMainThread());
+      mPtr = nullptr;
+    }
+
+    Type* operator->() const {
+      MOZ_ASSERT(NS_IsMainThread());
+      return mPtr;
+    }
+  private:
+    Type* mPtr;
+  };
+
+  // Our reference back to the MediaKeys object.
+  // WARNING: This is a non-owning reference that is cleared by MediaKeys
+  // destructor. only use on main thread, and always nullcheck before using!
+  MainThreadOnlyRawPtr<dom::MediaKeys> mKeys;
+
+  const nsAutoString mKeySystem;
+
+  // Gecko Media Plugin thread. All interactions with the out-of-process
+  // EME plugin must come from this thread.
+  nsRefPtr<nsIThread> mGMPThread;
+};
+
+} // namespace mozilla
+
+#endif // CDMProxy_h_
new file mode 100644
--- /dev/null
+++ b/content/media/eme/EMELog.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "EMELog.h"
+#include "mozilla/NullPtr.h"
+
+namespace mozilla {
+
+#ifdef PR_LOGGING
+
+PRLogModuleInfo* GetEMELog() {
+  static PRLogModuleInfo* log = nullptr;
+  if (!log) {
+    log = PR_NewLogModule("EME");
+  }
+  return log;
+}
+
+PRLogModuleInfo* GetEMEVerboseLog() {
+  static PRLogModuleInfo* log = nullptr;
+  if (!log) {
+    log = PR_NewLogModule("EMEV");
+  }
+  return log;
+}
+
+#endif
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/EMELog.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "prlog.h"
+
+namespace mozilla {
+
+#ifdef PR_LOGGING
+
+#ifndef EME_LOG
+PRLogModuleInfo* GetEMELog();
+#define EME_LOG(...) PR_LOG(GetEMELog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#endif
+
+#ifndef EME_VERBOSE_LOG
+PRLogModuleInfo* GetEMEVerboseLog();
+#define EME_VERBOSE_LOG(...) PR_LOG(GetEMEVerboseLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+
+#else
+
+#ifndef EME_LOG
+#define EME_LOG(...)
+#endif
+
+#ifndef EME_VERBOSE_LOG
+#define EME_VERBOSE_LOG(...)
+#endif
+
+#endif
+
+#endif // PR_LOGGING
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeyError.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "MediaKeyError.h"
+#include "mozilla/dom/MediaKeyErrorBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+MediaKeyError::MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode)
+  : Event(aOwner, nullptr, nullptr)
+  , mSystemCode(aSystemCode)
+{
+  SetIsDOMBinding();
+}
+
+MediaKeyError::~MediaKeyError()
+{
+}
+
+uint32_t
+MediaKeyError::SystemCode() const
+{
+  return mSystemCode;
+}
+
+JSObject*
+MediaKeyError::WrapObject(JSContext* aCx)
+{
+  return MediaKeyErrorBinding::Wrap(aCx, this);
+}
+
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeyError.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeyError_h
+#define mozilla_dom_MediaKeyError_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/Event.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaKeyError MOZ_FINAL : public Event
+{
+public:
+  NS_FORWARD_TO_EVENT
+
+  MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode);
+  ~MediaKeyError();
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  uint32_t SystemCode() const;
+
+private:
+  uint32_t mSystemCode;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeyMessageEvent.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+#include "js/GCAPI.h"
+#include "jsfriendapi.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeyMessageEvent)
+
+NS_IMPL_ADDREF_INHERITED(MediaKeyMessageEvent, Event)
+NS_IMPL_RELEASE_INHERITED(MediaKeyMessageEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMessage)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaKeyMessageEvent, Event)
+  tmp->mMessage = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeyMessageEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaKeyMessageEvent::MediaKeyMessageEvent(EventTarget* aOwner)
+  : Event(aOwner, nullptr, nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
+
+MediaKeyMessageEvent::~MediaKeyMessageEvent()
+{
+  mMessage = nullptr;
+  mozilla::DropJSObjects(this);
+}
+
+MediaKeyMessageEvent*
+MediaKeyMessageEvent::AsMediaKeyMessageEvent()
+{
+  return this;
+}
+
+JSObject*
+MediaKeyMessageEvent::WrapObject(JSContext* aCx)
+{
+  return MediaKeyMessageEventBinding::Wrap(aCx, this);
+}
+
+already_AddRefed<MediaKeyMessageEvent>
+MediaKeyMessageEvent::Constructor(EventTarget* aOwner,
+                                  const nsAString& aURL,
+                                  const nsTArray<uint8_t>& aMessage)
+{
+  nsRefPtr<MediaKeyMessageEvent> e = new MediaKeyMessageEvent(aOwner);
+  e->InitEvent(NS_LITERAL_STRING("message"), false, false);
+  e->mRawMessage = aMessage;
+  e->mDestinationURL = aURL;
+  e->SetTrusted(true);
+  return e.forget();
+}
+
+already_AddRefed<MediaKeyMessageEvent>
+MediaKeyMessageEvent::Constructor(const GlobalObject& aGlobal,
+                                  const nsAString& aType,
+                                  const MediaKeyMessageEventInit& aEventInitDict,
+                                  ErrorResult& aRv)
+{
+  nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+  nsRefPtr<MediaKeyMessageEvent> e = new MediaKeyMessageEvent(owner);
+  bool trusted = e->Init(owner);
+  e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+  if (aEventInitDict.mMessage.WasPassed()) {
+    const auto& a = aEventInitDict.mMessage.Value();
+    a.ComputeLengthAndData();
+    e->mMessage = Uint8Array::Create(aGlobal.GetContext(), owner, a.Length(), a.Data());
+  } else {
+    e->mMessage = Uint8Array::Create(aGlobal.GetContext(), owner, 0, nullptr);
+  }
+  if (!e->mMessage) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+  e->mDestinationURL = aEventInitDict.mDestinationURL;
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
+JSObject*
+MediaKeyMessageEvent::GetMessage(JSContext* cx, ErrorResult& aRv)
+{
+  if (!mMessage) {
+    mMessage = Uint8Array::Create(cx,
+                                  this,
+                                  mRawMessage.Length(),
+                                  mRawMessage.Elements());
+    if (!mMessage) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+    mRawMessage.Clear();
+  }
+  JS::ExposeObjectToActiveJS(mMessage);
+  return mMessage;
+}
+
+void
+MediaKeyMessageEvent::GetDestinationURL(nsString& aRetVal) const
+{
+  aRetVal = mDestinationURL;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeyMessageEvent.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeyMessageEvent_h__
+#define mozilla_dom_MediaKeyMessageEvent_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/TypedArray.h"
+#include "js/TypeDecls.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaKeyMessageEventInit;
+
+class MediaKeyMessageEvent MOZ_FINAL : public Event
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MediaKeyMessageEvent, Event)
+  virtual ~MediaKeyMessageEvent();
+protected:
+  MediaKeyMessageEvent(EventTarget* aOwner);
+
+  JS::Heap<JSObject*> mMessage;
+  nsString mDestinationURL;
+
+public:
+  virtual MediaKeyMessageEvent* AsMediaKeyMessageEvent();
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  static already_AddRefed<MediaKeyMessageEvent>
+    Constructor(EventTarget* aOwner,
+                const nsAString& aURL,
+                const nsTArray<uint8_t>& aMessage);
+
+  static already_AddRefed<MediaKeyMessageEvent>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aType,
+              const MediaKeyMessageEventInit& aEventInitDict,
+              ErrorResult& aRv);
+
+  JSObject* GetMessage(JSContext* cx, ErrorResult& aRv);
+
+  void GetDestinationURL(nsString& aRetVal) const;
+
+private:
+  nsTArray<uint8_t> mRawMessage;
+};
+
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeyMessageEvent_h__
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeyNeededEvent.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "MediaKeyNeededEvent.h"
+#include "mozilla/dom/MediaKeyNeededEventBinding.h"
+#include "nsContentUtils.h"
+#include "jsfriendapi.h"
+#include "nsINode.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeyNeededEvent)
+
+NS_IMPL_ADDREF_INHERITED(MediaKeyNeededEvent, Event)
+NS_IMPL_RELEASE_INHERITED(MediaKeyNeededEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaKeyNeededEvent, Event)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(MediaKeyNeededEvent, Event)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mInitData)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaKeyNeededEvent, Event)
+  tmp->mInitData = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeyNeededEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaKeyNeededEvent::MediaKeyNeededEvent(EventTarget* aOwner)
+  : Event(aOwner, nullptr, nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
+
+MediaKeyNeededEvent::~MediaKeyNeededEvent()
+{
+  mInitData = nullptr;
+  mozilla::DropJSObjects(this);
+}
+
+JSObject*
+MediaKeyNeededEvent::WrapObject(JSContext* aCx)
+{
+  return MediaKeyNeededEventBinding::Wrap(aCx, this);
+}
+
+already_AddRefed<MediaKeyNeededEvent>
+MediaKeyNeededEvent::Constructor(EventTarget* aOwner,
+                                 const nsAString& aInitDataType,
+                                 const nsTArray<uint8_t>& aInitData)
+{
+  nsRefPtr<MediaKeyNeededEvent> e = new MediaKeyNeededEvent(aOwner);
+  e->InitEvent(NS_LITERAL_STRING("needkey"), false, false);
+  e->mInitDataType = aInitDataType;
+  e->mRawInitData = aInitData;
+  e->SetTrusted(true);
+  return e.forget();
+}
+
+already_AddRefed<MediaKeyNeededEvent>
+MediaKeyNeededEvent::Constructor(const GlobalObject& aGlobal,
+                                 const nsAString& aType,
+                                 const MediaKeyNeededEventInit& aEventInitDict,
+                                 ErrorResult& aRv)
+{
+  nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+  nsRefPtr<MediaKeyNeededEvent> e = new MediaKeyNeededEvent(owner);
+  bool trusted = e->Init(owner);
+  e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable);
+  e->mInitDataType = aEventInitDict.mInitDataType;
+  if (aEventInitDict.mInitData.WasPassed() &&
+      !aEventInitDict.mInitData.Value().IsNull()) {
+    const auto& a = aEventInitDict.mInitData.Value().Value();
+    a.ComputeLengthAndData();
+    e->mInitData = Uint8Array::Create(aGlobal.GetContext(), owner, a.Length(), a.Data());
+    if (!e->mInitData) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+  }
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
+void
+MediaKeyNeededEvent::GetInitDataType(nsString& aRetVal) const
+{
+  aRetVal = mInitDataType;
+}
+
+JSObject*
+MediaKeyNeededEvent::GetInitData(JSContext* cx, ErrorResult& aRv)
+{
+  if (mRawInitData.Length()) {
+    mInitData = Uint8Array::Create(cx,
+                                   this,
+                                   mRawInitData.Length(),
+                                   mRawInitData.Elements());
+    if (!mInitData) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+    mRawInitData.Clear();
+  }
+  if (mInitData) {
+    JS::ExposeObjectToActiveJS(mInitData);
+  }
+  return mInitData;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeyNeededEvent.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeyNeededEvent_h__
+#define mozilla_dom_MediaKeyNeededEvent_h__
+
+#include "mozilla/dom/MediaKeyNeededEventBinding.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class MediaKeyNeededEvent MOZ_FINAL : public Event
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(MediaKeyNeededEvent, Event)
+  virtual ~MediaKeyNeededEvent();
+protected:
+  MediaKeyNeededEvent(EventTarget* aOwner);
+
+  nsString mInitDataType;
+  JS::Heap<JSObject*> mInitData;
+
+public:
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  static already_AddRefed<MediaKeyNeededEvent>
+  Constructor(EventTarget* aOwner,
+              const nsAString& aInitDataType,
+              const nsTArray<uint8_t>& aInitData);
+
+  static already_AddRefed<MediaKeyNeededEvent>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aType,
+              const MediaKeyNeededEventInit& aEventInitDict,
+              ErrorResult& aRv);
+
+  void GetInitDataType(nsString& aRetVal) const;
+
+  JSObject* GetInitData(JSContext* cx, ErrorResult& aRv);
+private:
+  nsTArray<uint8_t> mRawInitData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MediaKeyNeededEvent_h__
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeySession.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyNeededEvent.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/AsyncEventDispatcher.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySession,
+                                   DOMEventTargetHelper,
+                                   mMediaKeyError,
+                                   mKeys,
+                                   mClosed)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeySession)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MediaKeySession, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MediaKeySession, DOMEventTargetHelper)
+
+MediaKeySession::MediaKeySession(nsPIDOMWindow* aParent,
+                                 MediaKeys* aKeys,
+                                 const nsAString& aKeySystem,
+                                 SessionType aSessionType)
+  : DOMEventTargetHelper(aParent)
+  , mKeys(aKeys)
+  , mKeySystem(aKeySystem)
+  , mSessionType(aSessionType)
+  , mIsClosed(false)
+{
+  MOZ_ASSERT(aParent);
+  mClosed = mKeys->MakePromise();
+}
+
+void MediaKeySession::Init(const nsAString& aSessionId)
+{
+  mSessionId = aSessionId;
+}
+
+MediaKeySession::~MediaKeySession()
+{
+}
+
+MediaKeyError*
+MediaKeySession::GetError() const
+{
+  return mMediaKeyError;
+}
+
+void
+MediaKeySession::GetKeySystem(nsString& aKeySystem) const
+{
+  aKeySystem = mKeySystem;
+}
+
+void
+MediaKeySession::GetSessionId(nsString& aSessionId) const
+{
+  aSessionId = mSessionId;
+}
+
+JSObject*
+MediaKeySession::WrapObject(JSContext* aCx)
+{
+  return MediaKeySessionBinding::Wrap(aCx, this);
+}
+
+double
+MediaKeySession::Expiration() const
+{
+  return JS::GenericNaN();
+}
+
+Promise*
+MediaKeySession::Closed() const
+{
+  return mClosed;
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Update(const Uint8Array& aResponse)
+{
+  nsRefPtr<Promise> promise(mKeys->MakePromise());
+  aResponse.ComputeLengthAndData();
+  if (IsClosed() ||
+      !mKeys->GetCDMProxy() ||
+      !aResponse.Length()) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return promise.forget();
+  }
+  mKeys->GetCDMProxy()->UpdateSession(mSessionId,
+                                      mKeys->StorePromise(promise),
+                                      aResponse);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Close()
+{
+  nsRefPtr<Promise> promise(mKeys->MakePromise());
+  if (IsClosed() || !mKeys->GetCDMProxy()) {
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+    return promise.forget();
+  }
+  mKeys->GetCDMProxy()->CloseSession(mSessionId, mKeys->StorePromise(promise));
+
+  return promise.forget();
+}
+
+void
+MediaKeySession::OnClosed()
+{
+  if (IsClosed()) {
+    return;
+  }
+  mIsClosed = true;
+  // TODO: reset usableKeyIds
+  mKeys->OnSessionClosed(this);
+  mKeys = nullptr;
+  mClosed->MaybeResolve(JS::UndefinedHandleValue);
+}
+
+bool
+MediaKeySession::IsClosed() const
+{
+  return mIsClosed;
+}
+
+already_AddRefed<Promise>
+MediaKeySession::Remove()
+{
+  nsRefPtr<Promise> promise(mKeys->MakePromise());
+  if (mSessionType != SessionType::Persistent) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    // "The operation is not supported on session type sessions."
+    return promise.forget();
+  }
+  if (IsClosed() || !mKeys->GetCDMProxy()) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    // "The session is closed."
+    return promise.forget();
+  }
+  mKeys->GetCDMProxy()->RemoveSession(mSessionId, mKeys->StorePromise(promise));
+  return promise.forget();
+}
+
+void
+MediaKeySession::DispatchKeyMessage(const nsTArray<uint8_t>& aMessage,
+                                    const nsString& aURL)
+{
+  nsRefPtr<MediaKeyMessageEvent> event(
+    MediaKeyMessageEvent::Constructor(this, aURL, aMessage));
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, event);
+  asyncDispatcher->PostDOMEvent();
+}
+
+void
+MediaKeySession::DispatchKeyError(uint32_t aSystemCode)
+{
+  RefPtr<MediaKeyError> event(new MediaKeyError(this, aSystemCode));
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, event);
+  asyncDispatcher->PostDOMEvent();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeySession.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MediaKeySession_h
+#define mozilla_dom_MediaKeySession_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/dom/Date.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/MediaKeySessionBinding.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+
+struct JSContext;
+
+namespace mozilla {
+
+class CDMProxy;
+
+namespace dom {
+
+class MediaKeyError;
+
+class MediaKeySession MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaKeySession,
+                                           DOMEventTargetHelper)
+public:
+  MediaKeySession(nsPIDOMWindow* aParent,
+                  MediaKeys* aKeys,
+                  const nsAString& aKeySystem,
+                  SessionType aSessionType);
+
+  void Init(const nsAString& aSessionId);
+
+  ~MediaKeySession();
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  // Mark this as resultNotAddRefed to return raw pointers
+  MediaKeyError* GetError() const;
+
+  void GetKeySystem(nsString& aRetval) const;
+
+  void GetSessionId(nsString& aRetval) const;
+
+  // Number of ms since epoch at which expiration occurs, or NaN if unknown.
+  // TODO: The type of this attribute is still under contention.
+  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25902
+  double Expiration() const;
+
+  Promise* Closed() const;
+
+  already_AddRefed<Promise> Update(const Uint8Array& response);
+
+  already_AddRefed<Promise> Close();
+
+  already_AddRefed<Promise> Remove();
+
+  void DispatchKeyMessage(const nsTArray<uint8_t>& aMessage,
+                          const nsString& aURL);
+
+  void DispatchKeyError(uint32_t system_code);
+
+  void OnClosed();
+
+  bool IsClosed() const;
+
+private:
+  nsRefPtr<Promise> mClosed;
+
+  nsRefPtr<MediaKeyError> mMediaKeyError;
+  nsRefPtr<MediaKeys> mKeys;
+  const nsString mKeySystem;
+  nsString mSessionId;
+  const SessionType mSessionType;
+  bool mIsClosed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeys.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/CDMProxy.h"
+#include "nsContentUtils.h"
+#include "EMELog.h"
+
+namespace mozilla {
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
+                                      mParent,
+                                      mKeySessions,
+                                      mPromises,
+                                      mPendingSessions);
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeys)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+MediaKeys::MediaKeys(nsPIDOMWindow* aParent, const nsAString& aKeySystem)
+  : mParent(aParent),
+    mKeySystem(aKeySystem)
+{
+  SetIsDOMBinding();
+}
+
+MediaKeys::~MediaKeys()
+{
+  if (mProxy) {
+    mProxy->Shutdown();
+    mProxy = nullptr;
+  }
+}
+
+nsPIDOMWindow*
+MediaKeys::GetParentObject() const
+{
+  return mParent;
+}
+
+JSObject*
+MediaKeys::WrapObject(JSContext* aCx)
+{
+  return MediaKeysBinding::Wrap(aCx, this);
+}
+
+void
+MediaKeys::GetKeySystem(nsString& retval) const
+{
+  retval = mKeySystem;
+}
+
+already_AddRefed<Promise>
+MediaKeys::SetServerCertificate(const Uint8Array& aCert)
+{
+  aCert.ComputeLengthAndData();
+  nsRefPtr<Promise> promise(MakePromise());
+  mProxy->SetServerCertificate(StorePromise(promise), aCert);
+  return promise.forget();
+}
+
+/* static */
+IsTypeSupportedResult
+MediaKeys::IsTypeSupported(const GlobalObject& aGlobal,
+                           const nsAString& aKeySystem,
+                           const Optional<nsAString>& aInitDataType,
+                           const Optional<nsAString>& aContentType,
+                           const Optional<nsAString>& aCapability)
+{
+  // TODO: Query list of known CDMs and their supported content types.
+  // TODO: Should really get spec changed to this is async, so we can wait
+  //       for user to consent to running plugin.
+  return IsTypeSupportedResult::Maybe;
+}
+
+already_AddRefed<Promise>
+MediaKeys::MakePromise()
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    NS_WARNING("Passed non-global to MediaKeys ctor!");
+    return nullptr;
+  }
+  nsRefPtr<Promise> promise = new Promise(global);
+  return promise.forget();
+}
+
+PromiseId
+MediaKeys::StorePromise(Promise* aPromise)
+{
+  static uint32_t sEMEPromiseCount = 1;
+  MOZ_ASSERT(aPromise);
+  uint32_t id = sEMEPromiseCount++;
+  mPromises.Put(id, aPromise);
+  return id;
+}
+
+already_AddRefed<Promise>
+MediaKeys::RetrievePromise(PromiseId aId)
+{
+  nsRefPtr<Promise> promise;
+  mPromises.Remove(aId, getter_AddRefs(promise));
+  return promise.forget();
+}
+
+void
+MediaKeys::RejectPromise(PromiseId aId, nsresult aExceptionCode)
+{
+  nsRefPtr<Promise> promise(RetrievePromise(aId));
+  if (!promise) {
+    NS_WARNING("MediaKeys tried to reject a non-existent promise");
+    return;
+  }
+  if (mPendingSessions.Contains(aId)) {
+    // This promise could be a createSession or loadSession promise,
+    // so we might have a pending session waiting to be resolved into
+    // the promise on success. We've been directed to reject to promise,
+    // so we can throw away the corresponding session object.
+    mPendingSessions.Remove(aId);
+  }
+
+  MOZ_ASSERT(NS_FAILED(aExceptionCode));
+  promise->MaybeReject(aExceptionCode);
+}
+
+void
+MediaKeys::ResolvePromise(PromiseId aId)
+{
+  nsRefPtr<Promise> promise(RetrievePromise(aId));
+  if (!promise) {
+    NS_WARNING("MediaKeys tried to resolve a non-existent promise");
+    return;
+  }
+  // We should not resolve CreateSession or LoadSession calls via this path,
+  // OnSessionActivated() should be called instead.
+  MOZ_ASSERT(!mPendingSessions.Contains(aId));
+  promise->MaybeResolve(JS::UndefinedHandleValue);
+}
+
+/* static */
+already_AddRefed<Promise>
+MediaKeys::Create(const GlobalObject& aGlobal,
+                  const nsAString& aKeySystem,
+                  ErrorResult& aRv)
+{
+  // CDMProxy keeps MediaKeys alive until it resolves the promise and thus
+  // returns the MediaKeys object to JS.
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!window) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<MediaKeys> keys = new MediaKeys(window, aKeySystem);
+  nsRefPtr<Promise> promise(keys->MakePromise());
+  if (!promise) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  if (!aKeySystem.EqualsASCII("org.w3.clearkey")) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
+  keys->mProxy = new CDMProxy(keys, aKeySystem);
+  keys->mProxy->Init(keys->StorePromise(promise));
+
+  return promise.forget();
+}
+
+void
+MediaKeys::OnCDMCreated(PromiseId aId)
+{
+  nsRefPtr<Promise> promise(RetrievePromise(aId));
+  if (!promise) {
+    NS_WARNING("MediaKeys tried to resolve a non-existent promise");
+    return;
+  }
+  nsRefPtr<MediaKeys> keys(this);
+  promise->MaybeResolve(keys);
+}
+
+already_AddRefed<Promise>
+MediaKeys::LoadSession(const nsAString& aSessionId)
+{
+  nsRefPtr<Promise> promise(MakePromise());
+
+  if (aSessionId.IsEmpty()) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    // "The sessionId parameter is empty."
+    return promise.forget();
+  }
+
+  // TODO: The spec doesn't specify what to do in this case...
+  if (mKeySessions.Contains(aSessionId)) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return promise.forget();
+  }
+
+  // Create session.
+  nsRefPtr<MediaKeySession> session(
+    new MediaKeySession(GetParentObject(), this, mKeySystem, SessionType::Persistent));
+
+  // Proxy owns session object until resolving promise.
+  mProxy->LoadSession(StorePromise(promise),
+                      aSessionId);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+MediaKeys::CreateSession(const nsAString& initDataType,
+                         const Uint8Array& aInitData,
+                         SessionType aSessionType)
+{
+  aInitData.ComputeLengthAndData();
+  nsRefPtr<Promise> promise(MakePromise());
+  nsRefPtr<MediaKeySession> session = new MediaKeySession(GetParentObject(),
+                                                          this,
+                                                          mKeySystem,
+                                                          aSessionType);
+  auto pid = StorePromise(promise);
+  // Hang onto session until the CDM has finished setting it up.
+  mPendingSessions.Put(pid, session);
+  mProxy->CreateSession(aSessionType,
+                        pid,
+                        initDataType,
+                        aInitData);
+
+  return promise.forget();
+}
+
+void
+MediaKeys::OnSessionActivated(PromiseId aId, const nsAString& aSessionId)
+{
+  nsRefPtr<Promise> promise(RetrievePromise(aId));
+  if (!promise) {
+    NS_WARNING("MediaKeys tried to resolve a non-existent promise");
+    return;
+  }
+  MOZ_ASSERT(mPendingSessions.Contains(aId));
+
+  nsRefPtr<MediaKeySession> session;
+  if (!mPendingSessions.Get(aId, getter_AddRefs(session)) || !session) {
+    NS_WARNING("Received activation for non-existent session!");
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return;
+  }
+
+  // Session has completed creation/loading, remove it from mPendingSessions,
+  // and resolve the promise with it. We store it in mKeySessions, so we can
+  // find it again if we need to send messages to it etc.
+  mPendingSessions.Remove(aId);
+  session->Init(aSessionId);
+  mKeySessions.Put(aSessionId, session);
+  promise->MaybeResolve(session);
+}
+
+void
+MediaKeys::OnSessionClosed(MediaKeySession* aSession)
+{
+  nsAutoString id;
+  aSession->GetSessionId(id);
+  mKeySessions.Remove(id);
+}
+
+already_AddRefed<MediaKeySession>
+MediaKeys::GetSession(const nsAString& aSessionId)
+{
+  nsRefPtr<MediaKeySession> session;
+  mKeySessions.Get(aSessionId, getter_AddRefs(session));
+  return session.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/eme/MediaKeys.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_mediakeys_h__
+#define mozilla_dom_mediakeys_h__
+
+#include "nsIDOMMediaError.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+
+namespace mozilla {
+
+class CDMProxy;
+
+namespace dom {
+
+class MediaKeySession;
+
+typedef nsRefPtrHashtable<nsStringHashKey, MediaKeySession> KeySessionHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, dom::Promise> PromiseHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, MediaKeySession> PendingKeySessionsHashMap;
+typedef uint32_t PromiseId;
+
+// This class is used on the main thread only.
+// Note: it's addref/release is not (and can't be) thread safe!
+class MediaKeys MOZ_FINAL : public nsISupports,
+                            public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeys)
+
+  MediaKeys(nsPIDOMWindow* aParentWindow, const nsAString& aKeySystem);
+
+  ~MediaKeys();
+
+  nsPIDOMWindow* GetParentObject() const;
+
+  virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  // Javascript: readonly attribute DOMString keySystem;
+  void GetKeySystem(nsString& retval) const;
+
+  // JavaScript: MediaKeys.createSession()
+  already_AddRefed<Promise> CreateSession(const nsAString& aInitDataType,
+                                          const Uint8Array& aInitData,
+                                          SessionType aSessionType);
+
+  // JavaScript: MediaKeys.loadSession()
+  already_AddRefed<Promise> LoadSession(const nsAString& aSessionId);
+
+  // JavaScript: MediaKeys.SetServerCertificate()
+  already_AddRefed<Promise> SetServerCertificate(const Uint8Array& aServerCertificate);
+
+  // JavaScript: MediaKeys.create()
+  static
+  already_AddRefed<Promise> Create(const GlobalObject& aGlobal,
+                                   const nsAString& aKeySystem,
+                                   ErrorResult& aRv);
+
+  // JavaScript: MediaKeys.IsTypeSupported()
+  static IsTypeSupportedResult IsTypeSupported(const GlobalObject& aGlobal,
+                                               const nsAString& aKeySystem,
+                                               const Optional<nsAString>& aInitDataType,
+                                               const Optional<nsAString>& aContentType,
+                                               const Optional<nsAString>& aCapability);
+
+  already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
+
+  // Called once a Create() operation succeeds.
+  void OnCDMCreated(PromiseId aId);
+  // Called once a CreateSession or LoadSession succeeds.
+  void OnSessionActivated(PromiseId aId, const nsAString& aSessionId);
+  // Called once a session has closed.
+  void OnSessionClosed(MediaKeySession* aSession);
+
+  CDMProxy* GetCDMProxy() { return mProxy; }
+
+  // Makes a new promise, or nullptr on failure.
+  already_AddRefed<Promise> MakePromise();
+  // Stores promise in mPromises, returning an ID that can be used to retrieve
+  // it later. The ID is passed to the CDM, so that it can signal specific
+  // promises to be resolved.
+  PromiseId StorePromise(Promise* aPromise);
+
+  // Reject promise with DOMException corresponding to aExceptionCode.
+  void RejectPromise(PromiseId aId, nsresult aExceptionCode);
+  // Resolves promise with "undefined".
+  void ResolvePromise(PromiseId aId);
+
+private:
+
+  // Removes promise from mPromises, and returns it.
+  already_AddRefed<Promise> RetrievePromise(PromiseId aId);
+
+  // Owning ref to proxy. The proxy has a weak reference back to the MediaKeys,
+  // and the MediaKeys destructor clears the proxy's reference to the MediaKeys.
+  nsRefPtr<CDMProxy> mProxy;
+
+  nsCOMPtr<nsPIDOMWindow> mParent;
+  nsString mKeySystem;
+  KeySessionHashMap mKeySessions;
+  PromiseHashMap mPromises;
+  PendingKeySessionsHashMap mPendingSessions;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_mediakeys_h__
new file mode 100644
--- /dev/null
+++ b/content/media/eme/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+    'MediaKeyError.h',
+    'MediaKeyMessageEvent.h',
+    'MediaKeyNeededEvent.h',
+    'MediaKeys.h',
+    'MediaKeySession.h',
+]
+
+EXPORTS.mozilla += [
+    'CDMProxy.h',
+]
+
+UNIFIED_SOURCES += [
+    'CDMProxy.cpp',
+    'EMELog.cpp',
+    'MediaKeyError.cpp',
+    'MediaKeyMessageEvent.cpp',
+    'MediaKeyNeededEvent.cpp',
+    'MediaKeys.cpp',
+    'MediaKeySession.cpp',
+]
+
+FINAL_LIBRARY = 'gklayout'
+
+FAIL_ON_WARNINGS = True
--- a/content/media/gtest/TestVideoTrackEncoder.cpp
+++ b/content/media/gtest/TestVideoTrackEncoder.cpp
@@ -241,16 +241,19 @@ TEST(VP8VideoTrackEncoder, FetchMetaData
 
     // METADATA should be depend on how to initiate encoder.
     EXPECT_TRUE(vp8Meta->mWidth == params[i].mWidth);
     EXPECT_TRUE(vp8Meta->mHeight == params[i].mHeight);
   }
 }
 
 // Encode test
+// XXX(bug 1018402): Disable this test when compiled with VS2013 because it
+// crashes.
+#if !defined(_MSC_VER) || _MSC_VER < 1800
 TEST(VP8VideoTrackEncoder, FrameEncode)
 {
   // Initiate VP8 encoder
   TestVP8TrackEncoder encoder;
   InitParam param = {true, 640, 480, 90000};
   encoder.TestInit(param);
 
   // Create YUV images as source.
@@ -270,16 +273,17 @@ TEST(VP8VideoTrackEncoder, FrameEncode)
 
   // track change notification.
   encoder.NotifyQueuedTrackChanges(nullptr, 0, 0, 0, 0, segment);
 
   // Pull Encoded Data back from encoder.
   EncodedFrameContainer container;
   EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 }
+#endif // _MSC_VER
 
 // EOS test
 TEST(VP8VideoTrackEncoder, EncodeComplete)
 {
   // Initiate VP8 encoder
   TestVP8TrackEncoder encoder;
   InitParam param = {true, 640, 480, 90000};
   encoder.TestInit(param);
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -45,16 +45,18 @@ if CONFIG['MOZ_APPLEMEDIA']:
 PARALLEL_DIRS += ['webrtc']
 
 if CONFIG['MOZ_OMX_DECODER']:
     PARALLEL_DIRS += ['omx']
     PARALLEL_DIRS += ['omx/mediaresourcemanager']
 
 PARALLEL_DIRS += ['webspeech']
 
+PARALLEL_DIRS += ['eme']
+
 TEST_DIRS += [
     'test',
     'gtest',
 ]
 
 EXPORTS += [
     'AbstractMediaDecoder.h',
     'AudioChannelFormat.h',
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -295,173 +295,162 @@ support-files =
   wavedata_u8.wav
   wavedata_u8.wav^headers^
 
 [test_access_control.html]
 [test_aspectratio_mp4.html]
 [test_audio1.html]
 [test_audio2.html]
 [test_audioDocumentTitle.html]
-skip-if = true # bug 475110
+skip-if = true # bug 475110 - disabled since we don't play Wave files standalone
 [test_autoplay.html]
 [test_autoplay_contentEditable.html]
-skip-if = buildapp == 'b2g' # bug 899074 - timeouts
 [test_buffered.html]
-skip-if = toolkit == 'android' || os == "win" || (toolkit == 'gonk' && !debug) # See bug 832768 and 864682, b2g(assertion failures)
 [test_bug448534.html]
-skip-if = buildapp == 'b2g' # b2g(Timed out, bug 894922? Bug 902677 is for the timing out of a lot of media tests)
+skip-if = os == 'win' # bug 894922
 [test_bug463162.xhtml]
 [test_bug465498.html]
-skip-if = os == "win" || (toolkit == 'gonk' && !debug) # See bug 832768 and 864682
 [test_bug493187.html]
-skip-if = os == "win" || (toolkit == 'gonk' && !debug) # See bug 707777
 [test_bug495145.html]
 [test_bug495300.html]
 [test_bug654550.html]
 [test_bug686942.html]
 [test_bug726904.html]
-skip-if = true # bug 754860
 [test_bug874897.html]
 [test_bug883173.html]
 [test_bug895091.html]
 [test_bug895305.html]
 [test_bug919265.html]
 [test_bug957847.html]
 [test_can_play_type.html]
 [test_can_play_type_mpeg.html]
-skip-if = buildapp == 'b2g' # b2g(7 failures out of 27)
+skip-if = buildapp == 'b2g' # bug 1021675
 [test_can_play_type_no_ogg.html]
 [test_can_play_type_ogg.html]
-skip-if = buildapp == 'b2g' || e10s
+skip-if = buildapp == 'b2g' || e10s # b2g(bug 1021675)
 [test_chaining.html]
-skip-if = buildapp == 'b2g' # timeouts
 [test_clone_media_element.html]
 [test_closing_connections.html]
 [test_constants.html]
 [test_contentDuration1.html]
 [test_contentDuration2.html]
 [test_contentDuration3.html]
 [test_contentDuration4.html]
 [test_contentDuration5.html]
 [test_contentDuration6.html]
 [test_contentDuration7.html]
 [test_controls.html]
 [test_currentTime.html]
 [test_decode_error.html]
 [test_decoder_disable.html]
 [test_defaultMuted.html]
 [test_delay_load.html]
-skip-if = buildapp == 'b2g' # b2g(6 failures)
+skip-if = buildapp == 'b2g' # bug 1021676
 [test_error_in_video_document.html]
-skip-if = true # bug 608634
 [test_error_on_404.html]
 [test_fastSeek.html]
 [test_info_leak.html]
-skip-if = buildapp == 'b2g' # b2g(2 failures)
 [test_invalid_reject.html]
 [test_load.html]
 [test_load_candidates.html]
 [test_load_same_resource.html]
 [test_load_source.html]
 [test_loop.html]
 [test_media_selection.html]
-skip-if = os == "win" || (toolkit == 'gonk' && !debug) # See bug 897843, b2g(timed out)
+skip-if = toolkit == 'gonk' && !debug # bug 1021677
 [test_media_sniffer.html]
 [test_mediarecorder_avoid_recursion.html]
 [test_mediarecorder_creation.html]
 [test_mediarecorder_creation_fail.html]
 [test_mediarecorder_getencodeddata.html]
 [test_mediarecorder_record_4ch_audiocontext.html]
-skip-if = (toolkit == 'gonk' && !debug)
+skip-if = toolkit == 'gonk' && !debug # bug 1021678
 [test_mediarecorder_record_audiocontext.html]
 [test_mediarecorder_record_audiocontext_mlk.html]
 [test_mediarecorder_record_gum_video_timeslice.html]
-skip-if = buildapp == 'b2g' || (toolkit == 'android') # mimetype check, bug 969289
+skip-if = buildapp == 'b2g' || toolkit == 'android' # mimetype check, bug 969289
 [test_mediarecorder_record_immediate_stop.html]
 [test_mediarecorder_record_no_timeslice.html]
 [test_mediarecorder_record_nosrc.html]
 [test_mediarecorder_record_session.html]
 [test_mediarecorder_record_startstopstart.html]
 [test_mediarecorder_record_stopms.html]
 [test_mediarecorder_record_timeslice.html]
 [test_mediarecorder_reload_crash.html]
 [test_mediarecorder_unsupported_src.html]
 [test_metadata.html]
 [test_mixed_principals.html]
-skip-if = true # bug 567954 and 574586
+skip-if = (os == 'linux'  && debug) || (toolkit == 'android' && debug) || toolkit == 'gonk' # bug 567954 and intermittent leaks
 [test_mozHasAudio.html]
 [test_networkState.html]
 [test_new_audio.html]
 [test_no_load_event.html]
 [test_paused.html]
 [test_paused_after_ended.html]
 [test_play_events.html]
 [test_play_events_2.html]
 [test_play_twice.html]
 # Seamonkey: Bug 598252, B2G: Bug 982100, Android: Bug 758476, bug 981086
 skip-if = appname == "seamonkey" || toolkit == 'gonk' || toolkit == 'android'
 [test_playback.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # Disabled on Android & B2G due to bug 668973
 [test_playback_errors.html]
 [test_playback_rate.html]
-# Win: Bug 814533, B2G & Android Debug: Bug 1020538
+# Win: Bug 814533, B2G & Android Debug: Bug 1020538, see assertion annotations in test also
 skip-if = os == 'win' || buildapp == 'b2g' || (toolkit == 'android' && debug)
 [test_playback_rate_playpause.html]
-skip-if = true # bug 897108
+skip-if = os == 'linux' || os == 'win' # bug 897108, see assertion annotations in test also
 [test_played.html]
-skip-if = true # bug 751539
+skip-if = true # bug 1021794
 [test_preload_actions.html]
+skip-if = toolkit == 'android' # bug 886188
 [test_preload_attribute.html]
 [test_preload_suspend.html]
 skip-if = true # bug 493692
 [test_progress.html]
 [test_reactivate.html]
 [test_readyState.html]
 [test_referer.html]
 [test_replay_metadata.html]
 [test_reset_events_async.html]
 [test_reset_src.html]
 [test_resume.html]
-skip-if = true # disabled - No bug :-(
+skip-if = true # bug 1021673
 [test_seek_out_of_range.html]
 [test_seek.html]
-skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) # See bug 832678, 795271, and 857424 # android(bug 845162) androidx86(bug 845162)
 [test_seek2.html]
 [test_seekable1.html]
 [test_seekable2.html]
 [test_seekable3.html]
-skip-if = buildapp == 'b2g' # timeouts
 [test_seekLies.html]
 [test_source.html]
 [test_source_media.html]
 [test_source_null.html]
 [test_source_write.html]
 [test_standalone.html]
 [test_streams_autoplay.html]
 [test_streams_element_capture.html]
 [test_streams_element_capture_createObjectURL.html]
 [test_streams_element_capture_playback.html]
 [test_streams_element_capture_reset.html]
 skip-if = buildapp == 'b2g' # bug 901102
 [test_streams_gc.html]
-skip-if = buildapp == 'b2g' # Value being assigned to HTMLMediaElement.currentTime is not a finite floating-point value
+skip-if = buildapp == 'b2g' # bug 1021682
 [test_streams_srcObject.html]
 [test_streams_tracks.html]
 [test_texttrack.html]
 [test_texttrackcue.html]
 [test_texttracklist.html]
 [test_texttrackregion.html]
 [test_timeupdate_small_files.html]
 [test_trackelementevent.html]
 [test_trackevent.html]
 [test_unseekable.html]
-skip-if = buildapp == 'b2g'
 [test_video_to_canvas.html]
 [test_videoDocumentTitle.html]
-skip-if = true # bug 492821
 [test_VideoPlaybackQuality.html]
 [test_VideoPlaybackQuality_disabled.html]
 [test_volume.html]
 [test_webvtt_disabled.html]
 
 
 # The tests below contain backend-specific tests. Write backend independent
 # tests rather than adding to this list.
--- a/content/media/test/test_playback_rate.html
+++ b/content/media/test/test_playback_rate.html
@@ -16,18 +16,16 @@
 //            file MediaDecoderStateMachine.cpp, line 559
 // ASSERTION: Clock should go forwards if the playback rate is > 0.:
 //            'mCurrentFrameTime <= clock_time || mPlaybackRate <= 0',
 //            file MediaDecoderStateMachine.cpp, line 2379
 // This test is currently disabled on Android debug for wildly-varying numbers of the above.
 SimpleTest.expectAssertions(5, 9);
 if (navigator.platform.startsWith("Win")) {
   SimpleTest.expectAssertions(4, 5);
-} else if (navigator.platform.startsWith("Mac")) {
-  SimpleTest.expectAssertions(5, 7);
 }
 
 let manager = new MediaTestManager;
 
 function rangeCheck(lhs, rhs, threshold) {
   var diff = Math.abs(lhs - rhs);
   if (diff < threshold) {
     return true;
--- a/content/media/test/test_playback_rate_playpause.html
+++ b/content/media/test/test_playback_rate_playpause.html
@@ -5,23 +5,26 @@
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type='application/javascript;version=1.8'>
 
+// In varying quantities:
+// ASSERTION: Clock should go forwards if the playback rate is > 0.:
+//            'mCurrentFrameTime <= clock_time || mPlaybackRate <= 0',
+//            file MediaDecoderStateMachine.cpp, line 2379
 if (navigator.platform.startsWith("Win")) {
-  SimpleTest.expectAssertions(0, 1);
+  SimpleTest.expectAssertions(1, 6);
 } else if (navigator.platform.startsWith("Mac")) {
-  SimpleTest.expectAssertions(0, 2);
-} else if (navigator.platform.startsWith("Linux")) {
-  // Bug 897024
-  SimpleTest.expectAssertions(0, 2);
+  SimpleTest.expectAssertions(3, 7);
+} else if (navigator.platform.startsWith("Linux arm")) {
+  SimpleTest.expectAssertions(4, 11);
 }
 
 let manager = new MediaTestManager;
 
 function ontimeupdate(e) {
   var t = e.target;
   if (t.currentTime != 0.0) {
     dump(t.token + " t.currentTime != 0.0.\n");
--- a/content/media/webrtc/AudioOutputObserver.h
+++ b/content/media/webrtc/AudioOutputObserver.h
@@ -38,17 +38,17 @@ public:
 private:
   uint32_t mPlayoutFreq;
   uint32_t mPlayoutChannels;
 
   nsAutoPtr<webrtc::SingleRwFifo> mPlayoutFifo;
   uint32_t mChunkSize;
 
   // chunking to 10ms support
-  nsAutoPtr<FarEndAudioChunk> mSaved;
+  FarEndAudioChunk *mSaved; // can't be nsAutoPtr since we need to use moz_free()
   uint32_t mSamplesSaved;
 };
 
 // XXX until there's a registration API in MSG
 extern StaticAutoPtr<AudioOutputObserver> gFarendObserver;
 
 }
 
--- a/content/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -49,33 +49,35 @@ NS_IMPL_ISUPPORTS0(MediaEngineWebRTCAudi
 
 // XXX temp until MSG supports registration
 StaticAutoPtr<AudioOutputObserver> gFarendObserver;
 
 AudioOutputObserver::AudioOutputObserver()
   : mPlayoutFreq(0)
   , mPlayoutChannels(0)
   , mChunkSize(0)
+  , mSaved(nullptr)
   , mSamplesSaved(0)
 {
   // Buffers of 10ms chunks
   mPlayoutFifo = new webrtc::SingleRwFifo(MAX_AEC_FIFO_DEPTH/10);
 }
 
 AudioOutputObserver::~AudioOutputObserver()
 {
   Clear();
 }
 
 void
 AudioOutputObserver::Clear()
 {
   while (mPlayoutFifo->size() > 0) {
-    (void) mPlayoutFifo->Pop();
+    moz_free(mPlayoutFifo->Pop());
   }
+  moz_free(mSaved);
   mSaved = nullptr;
 }
 
 FarEndAudioChunk *
 AudioOutputObserver::Pop()
 {
   return (FarEndAudioChunk *) mPlayoutFifo->Pop();
 }
@@ -149,17 +151,18 @@ AudioOutputObserver::InsertFarEnd(const 
 
     if (mSamplesSaved >= mChunkSize) {
       int free_slots = mPlayoutFifo->capacity() - mPlayoutFifo->size();
       if (free_slots <= 0) {
         // XXX We should flag an overrun for the reader.  We can't drop data from it due to
         // thread safety issues.
         break;
       } else {
-        mPlayoutFifo->Push((int8_t *) mSaved.forget()); // takes ownership
+        mPlayoutFifo->Push((int8_t *) mSaved); // takes ownership
+        mSaved = nullptr;
         mSamplesSaved = 0;
       }
     }
   }
 }
 
 void
 MediaEngineWebRTCAudioSource::GetName(nsAString& aName)
@@ -512,34 +515,34 @@ MediaEngineWebRTCAudioSource::Process(in
   int length, int samplingFreq, bool isStereo)
 {
   // On initial capture, throw away all far-end data except the most recent sample
   // since it's already irrelevant and we want to keep avoid confusing the AEC far-end
   // input code with "old" audio.
   if (!mStarted) {
     mStarted  = true;
     while (gFarendObserver->Size() > 1) {
-      FarEndAudioChunk *buffer = gFarendObserver->Pop(); // only call if size() > 0
-      free(buffer);
+      moz_free(gFarendObserver->Pop()); // only call if size() > 0
     }
   }
 
   while (gFarendObserver->Size() > 0) {
     FarEndAudioChunk *buffer = gFarendObserver->Pop(); // only call if size() > 0
     if (buffer) {
       int length = buffer->mSamples;
-      if (mVoERender->ExternalPlayoutData(buffer->mData,
-                                          gFarendObserver->PlayoutFrequency(),
-                                          gFarendObserver->PlayoutChannels(),
-                                          mPlayoutDelay,
-                                          length) == -1) {
+      int res = mVoERender->ExternalPlayoutData(buffer->mData,
+                                                gFarendObserver->PlayoutFrequency(),
+                                                gFarendObserver->PlayoutChannels(),
+                                                mPlayoutDelay,
+                                                length);
+      moz_free(buffer);
+      if (res == -1) {
         return;
       }
     }
-    free(buffer);
   }
 
 #ifdef PR_LOGGING
   mSamples += length;
   if (mSamples > samplingFreq) {
     mSamples %= samplingFreq; // just in case mSamples >> samplingFreq
     if (PR_LOG_TEST(GetMediaManagerLog(), PR_LOG_DEBUG)) {
       webrtc::EchoStatistics echo;
--- a/docshell/test/chrome/chrome.ini
+++ b/docshell/test/chrome/chrome.ini
@@ -78,8 +78,9 @@ skip-if = toolkit == "gtk2"
 [test_bug846906.xul]
 [test_bug89419.xul]
 [test_bug909218.html]
 [test_bug92598.xul]
 [test_mozFrameType.xul]
 [test_principalInherit.xul]
 [test_private_hidden_window.html]
 [test_viewsource_forbidden_in_iframe.xul]
+skip-if = true # bug 1019315
--- a/dom/apps/src/PermissionsTable.jsm
+++ b/dom/apps/src/PermissionsTable.jsm
@@ -158,17 +158,17 @@ this.PermissionsTable =  { geolocation: 
                            },
                            fmradio: {
                              app: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            attention: {
                              app: DENY_ACTION,
-                             privileged: DENY_ACTION,
+                             privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "webapps-manage": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "backgroundservice": {
@@ -293,17 +293,17 @@ this.PermissionsTable =  { geolocation: 
                            "wappush": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-capture": {
                              app: PROMPT_ACTION,
                              privileged: PROMPT_ACTION,
-                             certified: PROMPT_ACTION
+                             certified: ALLOW_ACTION
                            },
                            "nfc": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION,
                              access: ["read", "write"]
                            },
                            "nfc-manager": {
@@ -319,17 +319,17 @@ this.PermissionsTable =  { geolocation: 
                            "downloads": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "video-capture": {
                              app: PROMPT_ACTION,
                              privileged: PROMPT_ACTION,
-                             certified: PROMPT_ACTION
+                             certified: ALLOW_ACTION
                            },
                            "feature-detection": {
                              app: DENY_ACTION,
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                          };
 
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -943,19 +943,22 @@ this.DOMApplicationRegistry = {
         let app = this.webapps[aResult.id];
         let manifest = aResult.manifest;
         if (!manifest) {
           // If we can't load the manifest, we probably have a corrupted
           // registry. We delete the app since we can't do anything with it.
           delete this.webapps[aResult.id];
           return;
         }
-        app.name = manifest.name;
+
+        let localeManifest = new ManifestHelper(manifest, app.origin);
+
+        app.name = localeManifest.name;
         app.csp = manifest.csp || "";
-        app.role = manifest.role || "";
+        app.role = localeManifest.role;
         if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
           app.redirects = this.sanitizeRedirects(manifest.redirects);
         }
         this._registerSystemMessages(manifest, app);
         this._registerInterAppConnections(manifest, app);
         appsToRegister.push({ manifest: manifest, app: app });
       });
       this._registerActivitiesForApps(appsToRegister, aRunUpdate);
@@ -1328,36 +1331,36 @@ this.DOMApplicationRegistry = {
 
   startDownload: Task.async(function*(aManifestURL) {
     debug("startDownload for " + aManifestURL);
 
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!app) {
       debug("startDownload: No app found for " + aManifestURL);
-      return;
+      throw new Error("NO_SUCH_APP");
     }
 
     if (app.downloading) {
       debug("app is already downloading. Ignoring.");
-      return;
+      throw new Error("APP_IS_DOWNLOADING");
     }
 
     // If the caller is trying to start a download but we have nothing to
     // download, send an error.
     if (!app.downloadAvailable) {
       this.broadcastMessage("Webapps:UpdateState", {
         error: "NO_DOWNLOAD_AVAILABLE",
         manifestURL: app.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
-      return;
+      throw new Error("NO_DOWNLOAD_AVAILABLE");
     }
 
     // First of all, we check if the download is supposed to update an
     // already installed application.
     let isUpdate = (app.installState == "installed");
 
     // An app download would only be triggered for two reasons: an app
     // update or while retrying to download a previously failed or canceled
@@ -1404,17 +1407,17 @@ this.DOMApplicationRegistry = {
 
       return;
     }
 
     let json = yield AppsUtils.loadJSONAsync(file.path);
     if (!json) {
       debug("startDownload: No update manifest found at " + file.path + " " +
             aManifestURL);
-      return;
+      throw new Error("MISSING_UPDATE_MANIFEST");
     }
 
     let manifest = new ManifestHelper(json, app.manifestURL);
     let [aId, aManifest] = yield this.downloadPackage(manifest, {
         manifestURL: aManifestURL,
         origin: app.origin,
         installOrigin: app.installOrigin,
         downloadSize: app.downloadSize
@@ -1445,114 +1448,113 @@ this.DOMApplicationRegistry = {
       manifestURL: aManifestURL
     });
     if (app.installState == "pending") {
       // We restarted a failed download, apply it automatically.
       this.applyDownload(aManifestURL);
     }
   }),
 
-  applyDownload: function applyDownload(aManifestURL) {
+  applyDownload: Task.async(function*(aManifestURL) {
     debug("applyDownload for " + aManifestURL);
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
-    if (!app || (app && !app.readyToApplyDownload)) {
-      return;
+    if (!app) {
+      throw new Error("NO_SUCH_APP");
+    }
+    if (!app.readyToApplyDownload) {
+      throw new Error("NOT_READY_TO_APPLY_DOWNLOAD");
     }
 
     // We need to get the old manifest to unregister web activities.
-    this.getManifestFor(aManifestURL).then((aOldManifest) => {
-      // Move the application.zip and manifest.webapp files out of TmpD
-      let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
-      let manFile = tmpDir.clone();
-      manFile.append("manifest.webapp");
-      let appFile = tmpDir.clone();
-      appFile.append("application.zip");
-
-      let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
-      appFile.moveTo(dir, "application.zip");
-      manFile.moveTo(dir, "manifest.webapp");
-
-      // Move the staged update manifest to a non staged one.
-      let staged = dir.clone();
-      staged.append("staged-update.webapp");
-
-      // If we are applying after a restarted download, we have no
-      // staged update manifest.
-      if (staged.exists()) {
-        staged.moveTo(dir, "update.webapp");
-      }
-
-      try {
-        tmpDir.remove(true);
-      } catch(e) { }
-
-      // Clean up the deprecated manifest cache if needed.
-      if (id in this._manifestCache) {
-        delete this._manifestCache[id];
+    let oldManifest = yield this.getManifestFor(aManifestURL);
+    // Move the application.zip and manifest.webapp files out of TmpD
+    let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
+    let manFile = tmpDir.clone();
+    manFile.append("manifest.webapp");
+    let appFile = tmpDir.clone();
+    appFile.append("application.zip");
+
+    let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
+    appFile.moveTo(dir, "application.zip");
+    manFile.moveTo(dir, "manifest.webapp");
+
+    // Move the staged update manifest to a non staged one.
+    let staged = dir.clone();
+    staged.append("staged-update.webapp");
+
+    // If we are applying after a restarted download, we have no
+    // staged update manifest.
+    if (staged.exists()) {
+      staged.moveTo(dir, "update.webapp");
+    }
+
+    try {
+      tmpDir.remove(true);
+    } catch(e) { }
+
+    // Clean up the deprecated manifest cache if needed.
+    if (id in this._manifestCache) {
+      delete this._manifestCache[id];
+    }
+
+    // Flush the zip reader cache to make sure we use the new application.zip
+    // when re-launching the application.
+    let zipFile = dir.clone();
+    zipFile.append("application.zip");
+    Services.obs.notifyObservers(zipFile, "flush-cache-entry", null);
+
+    // Get the manifest, and set properties.
+    let newManifest = yield this.getManifestFor(aManifestURL);
+    app.downloading = false;
+    app.downloadAvailable = false;
+    app.downloadSize = 0;
+    app.installState = "installed";
+    app.readyToApplyDownload = false;
+
+    // Update the staged properties.
+    if (app.staged) {
+      for (let prop in app.staged) {
+        app[prop] = app.staged[prop];
       }
-
-      // Flush the zip reader cache to make sure we use the new application.zip
-      // when re-launching the application.
-      let zipFile = dir.clone();
-      zipFile.append("application.zip");
-      Services.obs.notifyObservers(zipFile, "flush-cache-entry", null);
-
-      // Get the manifest, and set properties.
-      this.getManifestFor(aManifestURL).then((aData) => {
-        app.downloading = false;
-        app.downloadAvailable = false;
-        app.downloadSize = 0;
-        app.installState = "installed";
-        app.readyToApplyDownload = false;
-
-        // Update the staged properties.
-        if (app.staged) {
-          for (let prop in app.staged) {
-            app[prop] = app.staged[prop];
-          }
-          delete app.staged;
-        }
-
-        delete app.retryingDownload;
-
-        // Update the asm.js scripts we need to compile.
-        ScriptPreloader.preload(app, aData)
-          .then(() => this._saveApps()).then(() => {
-          // Update the handlers and permissions for this app.
-          this.updateAppHandlers(aOldManifest, aData, app);
-
-          AppsUtils.loadJSONAsync(staged.path).then((aUpdateManifest) => {
-            let appObject = AppsUtils.cloneAppObject(app);
-            appObject.updateManifest = aUpdateManifest;
-            this.notifyUpdateHandlers(appObject, aData, appFile.path);
-          });
-
-          if (supportUseCurrentProfile()) {
-            PermissionsInstaller.installPermissions(
-              { manifest: aData,
-                origin: app.origin,
-                manifestURL: app.manifestURL },
-              true);
-          }
-          this.updateDataStore(this.webapps[id].localId, app.origin,
-                               app.manifestURL, aData, app.appStatus);
-          this.broadcastMessage("Webapps:UpdateState", {
-            app: app,
-            manifest: aData,
-            manifestURL: app.manifestURL
-          });
-          this.broadcastMessage("Webapps:FireEvent", {
-            eventType: "downloadapplied",
-            manifestURL: app.manifestURL
-          });
-        });
-      });
+      delete app.staged;
+    }
+
+    delete app.retryingDownload;
+
+    // Update the asm.js scripts we need to compile.
+    yield ScriptPreloader.preload(app, newManifest);
+    yield this._saveApps();
+    // Update the handlers and permissions for this app.
+    this.updateAppHandlers(oldManifest, newManifest, app);
+
+    let updateManifest = yield AppsUtils.loadJSONAsync(staged.path);
+    let appObject = AppsUtils.cloneAppObject(app);
+    appObject.updateManifest = updateManifest;
+    this.notifyUpdateHandlers(appObject, newManifest, appFile.path);
+
+    if (supportUseCurrentProfile()) {
+      PermissionsInstaller.installPermissions(
+        { manifest: newManifest,
+          origin: app.origin,
+          manifestURL: app.manifestURL },
+        true);
+    }
+    this.updateDataStore(this.webapps[id].localId, app.origin,
+                         app.manifestURL, newManifest, app.appStatus);
+    this.broadcastMessage("Webapps:UpdateState", {
+      app: app,
+      manifest: newManifest,
+      manifestURL: app.manifestURL
     });
-  },
+    this.broadcastMessage("Webapps:FireEvent", {
+      eventType: "downloadapplied",
+      manifestURL: app.manifestURL
+    });
+  }),
 
   startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) {
     if (!aManifest.appcache_path) {
       return;
     }
 
     // If the manifest has an appcache_path property, use it to populate the
     // appcache.
@@ -2768,19 +2770,21 @@ this.DOMApplicationRegistry = {
       let hash = yield this._computeFileHash(zipFile.path);
 
       let responseStatus = requestChannel.responseStatus;
       let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash);
 
       if (oldPackage) {
         debug("package's etag or hash unchanged; sending 'applied' event");
         // The package's Etag or hash has not changed.
-        // We send a "applied" event right away.
+        // We send an "applied" event right away so code awaiting that event
+        // can proceed to access the app.  We also throw an error to alert
+        // the caller that the package wasn't downloaded.
         this._sendAppliedEvent(aNewApp, oldApp, id);
-        return;
+        throw new Error("PACKAGE_UNCHANGED");
       }
 
       let newManifest = yield this._openAndReadPackage(zipFile, oldApp, aNewApp,
               isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash);
 
       AppDownloadManager.remove(aNewApp.manifestURL);
 
       return [oldApp.id, newManifest];
@@ -3483,16 +3487,18 @@ this.DOMApplicationRegistry = {
         manifestURL: aNewApp.manifestURL
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL:  aNewApp.manifestURL
       });
     });
     AppDownloadManager.remove(aNewApp.manifestURL);
+
+    throw aError;
   },
 
   doUninstall: function(aData, aMm) {
     this.uninstall(aData.manifestURL,
       function onsuccess() {
         aMm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
       },
       function onfailure() {
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -793,17 +793,17 @@ Navigator::Vibrate(const nsTArray<uint32
   if (doc->Hidden()) {
     // Hidden documents cannot start or stop a vibration.
     return false;
   }
 
   nsTArray<uint32_t> pattern(aPattern);
 
   if (pattern.Length() > sMaxVibrateListLen) {
-    pattern.SetLength(sMaxVibrateMS);
+    pattern.SetLength(sMaxVibrateListLen);
   }
 
   for (size_t i = 0; i < pattern.Length(); ++i) {
     if (pattern[i] > sMaxVibrateMS) {
       pattern[i] = sMaxVibrateMS;
     }
   }
 
--- a/dom/crypto/HmacKeyAlgorithm.h
+++ b/dom/crypto/HmacKeyAlgorithm.h
@@ -28,17 +28,16 @@ public:
                    uint32_t aLength,
                    const nsString& aHash)
     : KeyAlgorithm(aGlobal, aName)
     , mHash(new KeyAlgorithm(aGlobal, aHash))
     , mLength(aLength)
   {
     switch (mHash->Mechanism()) {
       case CKM_SHA_1: mMechanism = CKM_SHA_1_HMAC; break;
-      case CKM_SHA224: mMechanism = CKM_SHA224_HMAC; break;
       case CKM_SHA256: mMechanism = CKM_SHA256_HMAC; break;
       case CKM_SHA384: mMechanism = CKM_SHA384_HMAC; break;
       case CKM_SHA512: mMechanism = CKM_SHA512_HMAC; break;
       default: mMechanism = UNKNOWN_CK_MECHANISM; break;
     }
   }
 
   ~HmacKeyAlgorithm()
--- a/dom/crypto/KeyAlgorithm.cpp
+++ b/dom/crypto/KeyAlgorithm.cpp
@@ -35,18 +35,16 @@ KeyAlgorithm::KeyAlgorithm(nsIGlobalObje
   if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
     mMechanism = CKM_AES_CBC_PAD;
   } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR)) {
     mMechanism = CKM_AES_CTR;
   } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM)) {
     mMechanism = CKM_AES_GCM;
   } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
     mMechanism = CKM_SHA_1;
-  } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_SHA224)) {
-    mMechanism = CKM_SHA224;
   } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
     mMechanism = CKM_SHA256;
   } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
     mMechanism = CKM_SHA384;
   } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
     mMechanism = CKM_SHA512;
   } else if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1)) {
     mMechanism = CKM_RSA_PKCS;
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -13,17 +13,16 @@
 #include "js/StructuredClone.h"
 
 // WebCrypto algorithm names
 #define WEBCRYPTO_ALG_AES_CBC       "AES-CBC"
 #define WEBCRYPTO_ALG_AES_CTR       "AES-CTR"
 #define WEBCRYPTO_ALG_AES_GCM       "AES-GCM"
 #define WEBCRYPTO_ALG_SHA1          "SHA-1"
 #define WEBCRYPTO_ALG_SHA256        "SHA-256"
-#define WEBCRYPTO_ALG_SHA224        "SHA-224"
 #define WEBCRYPTO_ALG_SHA384        "SHA-384"
 #define WEBCRYPTO_ALG_SHA512        "SHA-512"
 #define WEBCRYPTO_ALG_HMAC          "HMAC"
 #define WEBCRYPTO_ALG_RSAES_PKCS1   "RSAES-PKCS1-v1_5"
 #define WEBCRYPTO_ALG_RSASSA_PKCS1  "RSASSA-PKCS1-v1_5"
 
 // WebCrypto key formats
 #define WEBCRYPTO_KEY_FORMAT_RAW    "raw"
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -484,18 +484,16 @@ public:
     // is RSASSA-PKCS1-v1_5, and that only happens if we've constructed
     // an RsaHashedKeyAlgorithm
     nsRefPtr<RsaHashedKeyAlgorithm> rsaAlg = static_cast<RsaHashedKeyAlgorithm*>(aKey.Algorithm());
     nsRefPtr<KeyAlgorithm> hashAlg = rsaAlg->Hash();
 
     switch (hashAlg->Mechanism()) {
       case CKM_SHA_1:
         mOidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break;
-      case CKM_SHA224:
-        mOidTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION; break;
       case CKM_SHA256:
         mOidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break;
       case CKM_SHA384:
         mOidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; break;
       case CKM_SHA512:
         mOidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; break;
       default: {
         mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
@@ -593,18 +591,16 @@ public:
     mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
     if (NS_FAILED(mEarlyRv)) {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
       return;
     }
 
     if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA1))   {
       mOidTag = SEC_OID_SHA1;
-    } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA224)) {
-      mOidTag = SEC_OID_SHA224;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
       mOidTag = SEC_OID_SHA256;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
       mOidTag = SEC_OID_SHA384;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
       mOidTag = SEC_OID_SHA512;
     } else {
       mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
@@ -1017,18 +1013,17 @@ public:
         mEarlyRv = NS_ERROR_DOM_DATA_ERR;
         return;
       }
       algorithm = new AesKeyAlgorithm(global, algName, mLength);
       allowedUsages = Key::ENCRYPT | Key::DECRYPT;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
       RootedDictionary<HmacKeyGenParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
-      if (NS_FAILED(mEarlyRv) || !params.mLength.WasPassed() ||
-          !params.mHash.WasPassed()) {
+      if (NS_FAILED(mEarlyRv) || !params.mHash.WasPassed()) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
 
       nsString hashName;
       if (params.mHash.Value().IsString()) {
         hashName.Assign(params.mHash.Value().GetAsString());
       } else {
@@ -1036,17 +1031,34 @@ public:
         mEarlyRv = Coerce(aCx, hashAlg, params.mHash.Value());
         if (NS_FAILED(mEarlyRv) || !hashAlg.mName.WasPassed()) {
           mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
           return;
         }
         hashName.Assign(hashAlg.mName.Value());
       }
 
-      mLength = params.mLength.Value();
+      if (params.mLength.WasPassed()) {
+        mLength = params.mLength.Value();
+      } else {
+        KeyAlgorithm hashAlg(global, hashName);
+        switch (hashAlg.Mechanism()) {
+          case CKM_SHA_1: mLength = 128; break;
+          case CKM_SHA256: mLength = 256; break;
+          case CKM_SHA384: mLength = 384; break;
+          case CKM_SHA512: mLength = 512; break;
+          default: mLength = 0; break;
+        }
+      }
+
+      if (mLength == 0) {
+        mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+        return;
+      }
+
       algorithm = new HmacKeyAlgorithm(global, algName, mLength, hashName);
       allowedUsages = Key::SIGN | Key::VERIFY;
     } else {
       mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
       return;
     }
 
     // Add key usages
--- a/dom/crypto/test/tests.js
+++ b/dom/crypto/test/tests.js
@@ -306,16 +306,72 @@ TestArray.addTest(
 
     crypto.subtle.importKey("raw", tv.raw, alg, false, ['encrypt'])
       .then(doIndexedDB, error(that));
   }
 );
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
+  "Generate a 256-bit HMAC-SHA-256 key",
+  function() {
+    var that = this;
+    var alg = { name: "HMAC", length: 256, hash: {name: "SHA-256"} };
+    crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+      complete(that, function(x) {
+        return hasKeyFields(x) && x.algorithm.length == 256;
+      }),
+      error(that)
+    );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Generate a 256-bit HMAC-SHA-256 key without specifying a key length",
+  function() {
+    var that = this;
+    var alg = { name: "HMAC", hash: {name: "SHA-256"} };
+    crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+      complete(that, function(x) {
+        return hasKeyFields(x) && x.algorithm.length == 256;
+      }),
+      error(that)
+    );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Fail generating an HMAC key when specifying an invalid hash algorithm",
+  function() {
+    var that = this;
+    var alg = { name: "HMAC", hash: {name: "SHA-123"} };
+    crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+      error(that),
+      complete(that, function() { return true; })
+    );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Fail generating an HMAC key when specifying a zero length",
+  function() {
+    var that = this;
+    var alg = { name: "HMAC", hash: {name: "SHA-256"}, length: 0 };
+    crypto.subtle.generateKey(alg, true, ["sign", "verify"]).then(
+      error(that),
+      complete(that, function() { return true; })
+    );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
   "Generate a 192-bit AES key",
   function() {
     var that = this;
     var alg = { name: "AES-GCM", length: 192 };
     crypto.subtle.generateKey(alg, true, ["encrypt"]).then(
       complete(that, function(x) {
         return hasKeyFields(x);
       }),
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -288,17 +288,16 @@ EVENT(mozfullscreenerror,
 EVENT(mozpointerlockchange,
       NS_POINTERLOCKCHANGE,
       EventNameType_HTML,
       NS_EVENT)
 EVENT(mozpointerlockerror,
       NS_POINTERLOCKERROR,
       EventNameType_HTML,
       NS_EVENT)
-
 EVENT(pointerdown,
       NS_POINTER_DOWN,
       EventNameType_All,
       NS_POINTER_EVENT)
 EVENT(pointermove,
       NS_POINTER_MOVE,
       EventNameType_All,
       NS_POINTER_EVENT)
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1435,17 +1435,17 @@ EventStateManager::FireContextClick()
         
         if (frameSel && frameSel->GetMouseDownState()) {
           // note that this can cause selection changed events to fire if we're in
           // a text field, which will null out mCurrentTarget
           frameSel->SetMouseDownState(false);
         }
       }
 
-      nsIDocument* doc = mGestureDownContent->GetCurrentDoc();
+      nsIDocument* doc = mGestureDownContent->GetCrossShadowCurrentDoc();
       AutoHandlingUserInputStatePusher userInpStatePusher(true, &event, doc);
 
       // dispatch to DOM
       EventDispatcher::Dispatch(mGestureDownContent, mPresContext, &event,
                                 nullptr, &status);
 
       // We don't need to dispatch to frame handling because no frames
       // watch NS_CONTEXTMENU except for nsMenuFrame and that's only for
@@ -2006,17 +2006,17 @@ EventStateManager::DoScrollZoom(nsIFrame
   nsIContent *content = aTargetFrame->GetContent();
   if (content &&
       !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) &&
       !nsContentUtils::IsInChromeDocshell(content->OwnerDoc()))
     {
       // positive adjustment to decrease zoom, negative to increase
       int32_t change = (adjustment > 0) ? -1 : 1;
 
-      if (Preferences::GetBool("browser.zoom.full") || content->GetCurrentDoc()->IsSyntheticDocument()) {
+      if (Preferences::GetBool("browser.zoom.full") || content->OwnerDoc()->IsSyntheticDocument()) {
         ChangeFullZoom(change);
       } else {
         ChangeTextSize(change);
       }
     }
 }
 
 static nsIFrame*
@@ -2747,17 +2747,17 @@ EventStateManager::PostHandleEvent(nsPre
         // <body> element is clicked, we should redirect the focus to the
         // the <body> element.  E.g., when an user click bottom of the editor
         // where is outside of the <body> element, the <body> should be focused
         // and the user can edit immediately after that.
         //
         // NOTE: The newFocus isn't editable that also means it's not in
         // designMode.  In designMode, all contents are not focusable.
         if (newFocus && !newFocus->IsEditable()) {
-          nsIDocument *doc = newFocus->GetCurrentDoc();
+          nsIDocument *doc = newFocus->GetCrossShadowCurrentDoc();
           if (doc && newFocus == doc->GetRootElement()) {
             nsIContent *bodyContent =
               nsLayoutUtils::GetEditableRootContentByContentEditable(doc);
             if (bodyContent) {
               nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
               if (bodyFrame) {
                 currFrame = bodyFrame;
                 newFocus = bodyContent;
@@ -4628,17 +4628,18 @@ EventStateManager::SetContentState(nsICo
     } else {
       NS_ASSERTION(aState == NS_EVENT_STATE_HOVER, "How did that happen?");
       nsIContent* newHover;
       
       if (mPresContext->IsDynamic()) {
         newHover = aContent;
       } else {
         NS_ASSERTION(!aContent ||
-                     aContent->GetCurrentDoc() == mPresContext->PresShell()->GetDocument(),
+                     aContent->GetCrossShadowCurrentDoc() ==
+                       mPresContext->PresShell()->GetDocument(),
                      "Unexpected document");
         nsIFrame *frame = aContent ? aContent->GetPrimaryFrame() : nullptr;
         if (frame && nsLayoutUtils::IsViewportScrollbarFrame(frame)) {
           // The scrollbars of viewport should not ignore the hover state.
           // Because they are *not* the content of the web page.
           newHover = aContent;
         } else {
           // All contents of the web page should ignore the hover state.
--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -33,27 +33,16 @@ public:
   // In that case, we could be sure that only macros at the end were creating
   // EventStates instances with mStates set to something else than 0.
   // Unfortunately, this constructor is needed at at least two places now.
   explicit MOZ_CONSTEXPR EventStates(InternalType aStates)
     : mStates(aStates)
   {
   }
 
-  MOZ_CONSTEXPR EventStates(const EventStates& aEventStates)
-    : mStates(aEventStates.mStates)
-  {
-  }
-
-  EventStates& operator=(const EventStates& aEventStates)
-  {
-    mStates = aEventStates.mStates;
-    return *this;
-  }
-
   EventStates MOZ_CONSTEXPR operator|(const EventStates& aEventStates) const
   {
     return EventStates(mStates | aEventStates.mStates);
   }
 
   EventStates& operator|=(const EventStates& aEventStates)
   {
     mStates |= aEventStates.mStates;
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -5,82 +5,80 @@ support-files =
   mediaStreamPlayback.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
   blacksilence.js
   turnConfig.js
 
 [test_dataChannel_basicAudio.html]
-skip-if = toolkit == 'gonk' #Bug 962984 for debug, bug 963244 for opt
+skip-if = toolkit == 'gonk' # Bug 962984 for debug, bug 963244 for opt
 [test_dataChannel_basicAudioVideo.html]
-# Disabled on OS X for bug 930481 timeouts
-skip-if = os == 'mac' || toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicAudioVideoCombined.html]
-# Disabled on OS X for bug 930481 timeouts
-skip-if = os == 'mac' || toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_basicDataOnly.html]
 [test_dataChannel_basicVideo.html]
-skip-if = toolkit=='gonk' # b2g emulator seems to bee too slow (Bug 1016498 and 1008080)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_bug1013809.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = (toolkit == 'gonk' && debug) # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
 [test_dataChannel_noOffer.html]
 [test_getUserMedia_basicAudio.html]
-skip-if = (toolkit == 'gonk' && debug) #debug-only failure
+skip-if = (toolkit == 'gonk' && debug) # debug-only failure
 [test_getUserMedia_basicVideo.html]
-skip-if = (toolkit == 'gonk' && debug) #debug-only failure
+skip-if = (toolkit == 'gonk' && debug) # debug-only failure
 [test_getUserMedia_basicVideoAudio.html]
-skip-if = (toolkit == 'gonk' && debug) #debug-only failure, turned an intermittent (bug 962579) into a permanant orange
+skip-if = (toolkit == 'gonk' && debug) # debug-only failure, turned an intermittent (bug 962579) into a permanant orange
 [test_getUserMedia_constraints.html]
-skip-if = (toolkit=='gonk' || toolkit=='android') # Bug 907352, backwards-compatible behavior on mobile only
+skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 907352, backwards-compatible behavior on mobile only
 [test_getUserMedia_constraints_mobile.html]
-skip-if = (toolkit!='gonk' && toolkit!='android') # Bug 907352, backwards-compatible behavior on mobile only
+skip-if = toolkit != 'gonk' && toolkit != 'android' # Bug 907352, backwards-compatible behavior on mobile only
 [test_getUserMedia_exceptions.html]
 [test_getUserMedia_gumWithinGum.html]
 [test_getUserMedia_playAudioTwice.html]
 [test_getUserMedia_playVideoAudioTwice.html]
-skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926558
+skip-if = (toolkit == 'gonk' && debug) # debug-only failure; bug 926558
 [test_getUserMedia_playVideoTwice.html]
 [test_getUserMedia_stopAudioStream.html]
 [test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
 [test_getUserMedia_stopVideoAudioStream.html]
-skip-if = (toolkit == 'gonk' && debug) #debug-only failure
+skip-if = (toolkit == 'gonk' && debug) # debug-only failure
 [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html]
 [test_getUserMedia_stopVideoStream.html]
 [test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
 [test_getUserMedia_peerIdentity.html]
 [test_peerConnection_addCandidateInHaveLocalOffer.html]
 [test_peerConnection_basicAudio.html]
-skip-if = (toolkit == 'gonk' && debug) #Bug 962984, test fail on b2g debug build
+skip-if = (toolkit == 'gonk' && debug) # Bug 962984, test fail on b2g debug build
 [test_peerConnection_basicAudioVideo.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicAudioVideoCombined.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_basicVideo.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_bug822674.html]
 [test_peerConnection_bug825703.html]
 [test_peerConnection_bug827843.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_bug834153.html]
 [test_peerConnection_bug835370.html]
-skip-if = toolkit=='gonk' # b2g emulator seems to bee too slow (Bug 1016498 and 1008080)
 [test_peerConnection_bug1013809.html]
+skip-if = (toolkit == 'gonk' && debug) # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
 [test_peerConnection_close.html]
 [test_peerConnection_errorCallbacks.html]
 [test_peerConnection_offerRequiresReceiveAudio.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_offerRequiresReceiveVideo.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_offerRequiresReceiveVideoAudio.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_setLocalAnswerInHaveLocalOffer.html]
 [test_peerConnection_setLocalAnswerInStable.html]
 [test_peerConnection_setLocalOfferInHaveRemoteOffer.html]
 [test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html]
 [test_peerConnection_setRemoteAnswerInStable.html]
 [test_peerConnection_setRemoteOfferInHaveLocalOffer.html]
 [test_peerConnection_throwInCallbacks.html]
-skip-if = toolkit=='gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_toJSON.html]
 
 # Bug 950317: Hack for making a cleanup hook after finishing all WebRTC cases
 [test_zmedia_cleanup.html]
--- a/dom/network/tests/unit/xpcshell.ini
+++ b/dom/network/tests/unit/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head =
 tail =
 
 [test_tcpsocket.js]
 [test_multisend.js]
 [test_tcpserversocket.js]
 run-sequentially = Uses hardcoded port, bug 903830.
+skip-if = os == 'mac' # bug 953208 - frequent timeouts on OSX
--- a/dom/src/notification/Notification.cpp
+++ b/dom/src/notification/Notification.cpp
@@ -391,18 +391,23 @@ NotificationTask::Run()
 }
 
 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
 
 NS_IMETHODIMP
 NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
                               const char16_t* aData)
 {
+  nsCOMPtr<nsPIDOMWindow> window = mNotification->GetOwner();
+  if (!window) {
+    // Window has been closed, this observer is not valid anymore
+    return NS_ERROR_FAILURE;
+  }
+
   if (!strcmp("alertclickcallback", aTopic)) {
-    nsCOMPtr<nsPIDOMWindow> window = mNotification->GetOwner();
     nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
     if (doc) {
       nsContentUtils::DispatchChromeEvent(doc, window,
                                           NS_LITERAL_STRING("DOMWebNotificationClicked"),
                                           true, true);
     }
     mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("click"));
   } else if (!strcmp("alertfinished", aTopic)) {
--- a/dom/system/NetworkGeolocationProvider.js
+++ b/dom/system/NetworkGeolocationProvider.js
@@ -117,17 +117,17 @@ WifiGeoPositionProvider.prototype = {
     try {
       Services.obs.addObserver(this, SETTING_CHANGED_TOPIC, false);
       let settings = Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService);
       settings.createLock().get(SETTING_DEBUG_ENABLED, settingsCallback);
     } catch(ex) {
       // This platform doesn't have the settings interface, and that is just peachy
     }
 
-    if (gWifiScanningEnabled) {
+    if (gWifiScanningEnabled && Cc["@mozilla.org/wifi/monitor;1"]) {
       this.wifiService = Cc["@mozilla.org/wifi/monitor;1"].getService(Components.interfaces.nsIWifiMonitor);
       this.wifiService.startWatching(this);
     }
     this.timeoutTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     this.timeoutTimer.initWithCallback(this,
                                        gTimeToWaitBeforeSending,
                                        this.timeoutTimer.TYPE_REPEATING_SLACK);
     LOG("startup called.");
--- a/dom/tests/mochitest/webcomponents/test_shadowroot_style.html
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html
@@ -5,20 +5,23 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <title>Test for ShadowRoot styling</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <div class="tall" id="bodydiv"></div>
+<div id="container"></div>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
 <script>
 // Create ShadowRoot.
+var container = document.getElementById("container");
 var elem = document.createElement("div");
+container.appendChild(elem); // Put ShadowRoot host in document.
 var root = elem.createShadowRoot();
 
 // A style element that will be appended into the ShadowRoot.
 var shadowStyle = document.createElement("style");
 shadowStyle.innerHTML = ".tall { height: 100px; } .fat { padding-left: inherit; }";
 
 root.innerHTML = '<div id="divtostyle" class="tall fat"></div>';
 var divToStyle = root.getElementById("divtostyle");
--- a/dom/tests/mochitest/webcomponents/test_shadowroot_style_multiple_shadow.html
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style_multiple_shadow.html
@@ -5,20 +5,23 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <title>Test for ShadowRoot styles with multiple ShadowRoot on host.</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <div class="tall" id="bodydiv"></div>
+<div id="container"></div>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
 <script>
 // Create ShadowRoot.
+var container = document.getElementById("container");
 var elem = document.createElement("div");
+container.appendChild(elem); // Put ShadowRoot host in document.
 var firstRoot = elem.createShadowRoot();
 var secondRoot = elem.createShadowRoot();
 var thirdRoot = elem.createShadowRoot();
 
 // A style element that will be appended into the ShadowRoot.
 var firstStyle = document.createElement("style");
 firstRoot.appendChild(firstStyle);
 is(firstRoot.styleSheets.length, 1, "firstStyle should be the only style in firstRoot.");
--- a/dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html
+++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style_order.html
@@ -4,20 +4,23 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=806506
 -->
 <head>
   <title>Test for ShadowRoot style order</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
+<div id="container"></div>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
 <script>
 // Create ShadowRoot.
+var container = document.getElementById("container");
 var elem = document.createElement("div");
+container.appendChild(elem); // Put ShadowRoot host in document.
 var root = elem.createShadowRoot();
 
 // Style elements that will be appended into the ShadowRoot.
 var tallShadowStyle = document.createElement("style");
 tallShadowStyle.innerHTML = ".tall { height: 100px; }";
 
 var veryTallShadowStyle = document.createElement("style");
 veryTallShadowStyle.innerHTML = ".tall { height: 200px; }";
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AutocompleteErrorEvent.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; 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/. */
+
+enum AutoCompleteErrorReason {
+  "",
+  "cancel",
+  "disabled",
+  "invalid"
+};
+
+[Pref="dom.forms.requestAutocomplete",
+ Constructor(DOMString type, optional AutocompleteErrorEventInit eventInitDict)]
+interface AutocompleteErrorEvent : Event
+{
+  readonly attribute AutoCompleteErrorReason reason;
+};
+
+dictionary AutocompleteErrorEventInit : EventInit
+{
+  AutoCompleteErrorReason reason = "";
+};
--- a/dom/webidl/HTMLFormElement.webidl
+++ b/dom/webidl/HTMLFormElement.webidl
@@ -40,9 +40,12 @@ interface HTMLFormElement : HTMLElement 
   getter Element (unsigned long index);
   // TODO this should be: getter (RadioNodeList or HTMLInputElement or HTMLImageElement) (DOMString name);
   getter nsISupports (DOMString name);
 
   [Throws]
   void submit();
   void reset();
   boolean checkValidity();
+
+  [Pref="dom.forms.requestAutocomplete"]
+  void requestAutocomplete();
 };
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -97,16 +97,19 @@ interface HTMLMediaElement : HTMLElement
 };
 
 // Mozilla extensions:
 partial interface HTMLMediaElement {
   attribute MediaStream? mozSrcObject;
   attribute boolean mozPreservesPitch;
   readonly attribute boolean mozAutoplayEnabled;
 
+  // NB: for internal use with the video controls:
+  [Func="IsChromeOrXBL"] attribute boolean mozMediaStatisticsShowing;
+
   // Mozilla extension: stream capture
   [Throws]
   MediaStream mozCaptureStream();
   [Throws]
   MediaStream mozCaptureStreamUntilEnded();
   readonly attribute boolean mozAudioCaptured;
 
   // Mozilla extension: return embedded metadata from the stream as a
@@ -125,8 +128,30 @@ partial interface HTMLMediaElement {
   [SetterThrows]
   attribute AudioChannel mozAudioChannelType;
 
   // In addition the media element has this new events:
   // * onmozinterruptbegin - called when the media element is interrupted
   //   because of the audiochannel manager.
   // * onmozinterruptend - called when the interruption is concluded
 };
+
+enum MediaWaitingFor {
+  "none",
+  "data",
+  "key"
+};
+
+// Encrypted Media Extensions
+partial interface HTMLMediaElement {
+  [Pref="media.eme.enabled"]
+  readonly attribute MediaKeys? mediaKeys;
+  
+  // Promise<any>
+  [Pref="media.eme.enabled", Throws, NewObject]
+  Promise setMediaKeys(MediaKeys? mediaKeys);
+  
+  [Pref="media.eme.enabled"]
+  attribute EventHandler onneedkey;
+
+  [Pref="media.eme.enabled"]
+  readonly attribute MediaWaitingFor waitingFor;
+};
--- a/dom/webidl/HTMLSourceElement.webidl
+++ b/dom/webidl/HTMLSourceElement.webidl
@@ -14,8 +14,14 @@
 interface HTMLSourceElement : HTMLElement {
            [SetterThrows]
            attribute DOMString src;
            [SetterThrows]
            attribute DOMString type;
            [SetterThrows]
            attribute DOMString media;
 };
+
+// Encrypted Media Extensions
+partial interface HTMLSourceElement {
+  [Pref="media.eme.enabled"]
+  attribute DOMString keySystem;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaKeyError.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+// According to the spec, "The future of error events and MediaKeyError
+// is uncertain."
+// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21798
+[Pref="media.eme.enabled"]
+interface MediaKeyError : Event {
+  readonly attribute unsigned long systemCode;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaKeyMessageEvent.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+[Pref="media.eme.enabled", Constructor(DOMString type, optional MediaKeyMessageEventInit eventInitDict)]
+interface MediaKeyMessageEvent : Event {
+  [Throws]
+  readonly attribute Uint8Array message;
+  readonly attribute DOMString? destinationURL;
+};
+
+dictionary MediaKeyMessageEventInit : EventInit {
+  Uint8Array message;
+  DOMString? destinationURL = "";
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaKeyNeededEvent.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+[Pref="media.eme.enabled", Constructor(DOMString type, optional MediaKeyNeededEventInit eventInitDict)]
+interface MediaKeyNeededEvent : Event {
+  readonly attribute DOMString initDataType;
+  [Throws]
+  readonly attribute Uint8Array? initData;
+};
+
+dictionary MediaKeyNeededEventInit : EventInit {
+  DOMString initDataType = "";
+  Uint8Array? initData;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaKeySession.webidl
@@ -0,0 +1,43 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+[Pref="media.eme.enabled"]
+interface MediaKeySession : EventTarget {
+  // error state
+  readonly attribute MediaKeyError? error;
+
+  // session properties
+  readonly attribute DOMString keySystem;
+  readonly attribute DOMString sessionId;
+
+  // Invalid WebIDL, doesn't work.
+  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25594
+  // readonly attribute Array<Uint8Array> usableKeyIds;
+
+  readonly attribute unrestricted double expiration;
+
+  // Promise<any>
+  readonly attribute Promise closed;
+
+  // session operations
+  //Promise<any>
+  [NewObject]
+  Promise update(Uint8Array response);
+
+  // Promise<any>
+  [NewObject]
+  Promise close();
+
+  // Promise<any>
+  [NewObject]
+  Promise remove();
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MediaKeys.webidl
@@ -0,0 +1,37 @@
+/* -*- Mode: IDL; 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+ *
+ * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
+ * W3C liability, trademark and document use rules apply.
+ */
+
+enum IsTypeSupportedResult { "" /* empty string */, "maybe", "probably" };
+enum SessionType { "temporary", "persistent" };
+
+[Pref="media.eme.enabled"]
+interface MediaKeys {
+  readonly attribute DOMString keySystem;
+
+  // Promise<MediaKeySession>
+  [NewObject]
+  Promise createSession(DOMString initDataType, Uint8Array initData, optional SessionType sessionType = "temporary");
+
+  // Promise<MediaKeySession>
+  [NewObject]
+  Promise loadSession(DOMString sessionId);
+
+  // Promise<any>
+  [NewObject]
+  Promise setServerCertificate(Uint8Array serverCertificate);
+
+  // Promise<MediaKeys>
+  [Throws,NewObject]
+  static Promise create(DOMString keySystem);
+  static IsTypeSupportedResult isTypeSupported(DOMString keySystem, optional DOMString initDataType, optional DOMString contentType, optional DOMString capability);
+
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -226,16 +226,21 @@ WEBIDL_FILES = [
     'KeyEvent.webidl',
     'LegacyQueryInterface.webidl',
     'LinkStyle.webidl',
     'LocalMediaStream.webidl',
     'Location.webidl',
     'LockedFile.webidl',
     'MediaElementAudioSourceNode.webidl',
     'MediaError.webidl',
+    'MediaKeyError.webidl',
+    'MediaKeyMessageEvent.webidl',
+    'MediaKeyNeededEvent.webidl',
+    'MediaKeys.webidl',
+    'MediaKeySession.webidl',
     'MediaList.webidl',
     'MediaQueryList.webidl',
     'MediaRecorder.webidl',
     'MediaSource.webidl',
     'MediaStream.webidl',
     'MediaStreamAudioDestinationNode.webidl',
     'MediaStreamAudioSourceNode.webidl',
     'MediaStreamTrack.webidl',
@@ -618,16 +623,17 @@ if CONFIG['MOZ_B2G_FM']:
     ]
 
 if not CONFIG['MOZ_DISABLE_CRYPTOLEGACY']:
     WEBIDL_FILES += [
         'CRMFObject.webidl',
     ]
 
 GENERATED_EVENTS_WEBIDL_FILES = [
+    'AutocompleteErrorEvent.webidl',
     'BlobEvent.webidl',
     'CallGroupErrorEvent.webidl',
     'CFStateChangeEvent.webidl',
     'DataErrorEvent.webidl',
     'DataStoreChangeEvent.webidl',
     'DeviceLightEvent.webidl',
     'DeviceProximityEvent.webidl',
     'DownloadEvent.webidl',
--- a/dom/workers/WorkerFeature.h
+++ b/dom/workers/WorkerFeature.h
@@ -23,16 +23,22 @@ BEGIN_WORKERS_NAMESPACE
  * +-------------+-------------+-----------------+----------------+
  * | Terminating |     yes     |       yes       |   no timeout   |
  * +-------------+-------------+-----------------+----------------+
  * |  Canceling  |     yes     |       yes       | short duration |
  * +-------------+-------------+-----------------+----------------+
  * |   Killing   |     yes     |       yes       |   doesn't run  |
  * +-------------+-------------+-----------------+----------------+
  */
+
+#ifdef Status
+/* Xlib headers insist on this for some reason... Nuke it because
+   it'll override our member name */
+#undef Status
+#endif
 enum Status
 {
   // Not yet scheduled.
   Pending = 0,
 
   // This status means that the close handler has not yet been scheduled.
   Running,
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -440,16 +440,37 @@ APZCTreeManager::ReceiveInputEvent(const
         // then null it out so we don't keep a dangling reference and leak things.
         if (mTouchCount == 0) {
           mApzcForInputBlock = nullptr;
           mInOverscrolledApzc = false;
           ClearOverscrollHandoffChain();
         }
       }
       break;
+    } case PANGESTURE_INPUT: {
+      const PanGestureInput& panInput = aEvent.AsPanGestureInput();
+      bool inOverscrolledApzc = false;
+      nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(panInput.mPanStartPoint,
+                                                            &inOverscrolledApzc);
+      if (apzc) {
+        if (panInput.mType == PanGestureInput::PANGESTURE_START ||
+            panInput.mType == PanGestureInput::PANGESTURE_MOMENTUMSTART) {
+          BuildOverscrollHandoffChain(apzc);
+        }
+        apzc->GetGuid(aOutTargetGuid);
+        GetInputTransforms(apzc, transformToApzc, transformToGecko);
+        PanGestureInput inputForApzc(panInput);
+        ApplyTransform(&(inputForApzc.mPanStartPoint), transformToApzc);
+        result = apzc->ReceiveInputEvent(inputForApzc);
+        if (panInput.mType == PanGestureInput::PANGESTURE_END ||
+            panInput.mType == PanGestureInput::PANGESTURE_MOMENTUMEND) {
+          ClearOverscrollHandoffChain();
+        }
+      }
+      break;
     } case PINCHGESTURE_INPUT: {
       const PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
       bool inOverscrolledApzc = false;
       nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(pinchInput.mFocusPoint,
                                                             &inOverscrolledApzc);
       if (apzc) {
         apzc->GetGuid(aOutTargetGuid);
         GetInputTransforms(apzc, transformToApzc, transformToGecko);
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -211,23 +211,46 @@ typedef GeckoContentController::APZState
  *
  * "apz.num_paint_duration_samples"
  * Number of samples to store of how long it took to paint after the previous
  *
  * "apz.overscroll.enabled"
  * Pref that enables overscrolling. If this is disabled, excess scroll that
  * cannot be handed off is discarded.
  *
- * "apz.overscroll.snap_back_accel"
- * Amount of acceleration applied during the snap-back animation.
+ * "apz.overscroll.fling_friction"
+ * Amount of friction applied during flings when in overscroll.
+ *
+ * "apz.overscroll.fling_stopped_threshold"
+ * When flinging in an overscrolled state, if the velocity goes below this
+ * number, we stop the fling.
+ * Units: screen pixels per millisecond
+ *
+ * "apz.overscroll.clamping"
+ * The maximum proportion of the composition bounds which can become blank
+ * as a result of overscroll, along the axis of overscroll.
  *
- * "apz.overscroll.snap_back_init_vel"
- * Initial velocity of a snap-back animation along one axis.
- * Units: screen pixels per millisecond
- * requests.
+ * "apz.overscroll.z_effect"
+ * The fraction of "apz.overscroll.clamping" which can become blank as a result
+ * of overscroll, along the axis opposite to the axis of overscroll. Called
+ * "z_effect" because the shrinking that brings about the blank space on the
+ * opposite axis creates the effect of the page moving away from you along a
+ * "z" axis.
+ *
+ * "apz.overscroll.snap_back.spring_stiffness"
+ * The stiffness of the spring used in the physics model for the overscroll
+ * snap-back animation.
+ *
+ * "apz.overscroll.snap_back.spring_damping"
+ * The friction of the spring used in the physics model for the overscroll
+ * snap-back animation.
+ *
+ * "apz.overscroll.snap_back.mass"
+ * The mass of the page in the physics model for the overscroll snap-back
+ * animation.
  *
  * "apz.pan_repaint_interval"
  * Maximum amount of time while panning before sending a viewport change. This
  * will asynchronously repaint the page. It is also forced when panning stops.
  *
  * "apz.test.logging_enabled"
  * Enable logging of APZ test data (see bug 961289).
  *
@@ -466,27 +489,25 @@ private:
   // |mResolution| fields on this.
   CSSPoint mEndOffset;
   CSSToScreenScale mEndZoom;
 };
 
 class OverscrollSnapBackAnimation: public AsyncPanZoomAnimation {
 public:
   OverscrollSnapBackAnimation(AsyncPanZoomController& aApzc)
-    : mApzc(aApzc)
-  {
-    mApzc.mX.StartSnapBack();
-    mApzc.mY.StartSnapBack();
-  }
+    : mApzc(aApzc) {}
 
   virtual bool Sample(FrameMetrics& aFrameMetrics,
                       const TimeDuration& aDelta) MOZ_OVERRIDE
   {
-    return mApzc.mX.SampleSnapBack(aDelta)
-        || mApzc.mY.SampleSnapBack(aDelta);
+    // Can't inline these variables due to short-circuit evaluation.
+    bool continueX = mApzc.mX.SampleSnapBack(aDelta);
+    bool continueY = mApzc.mY.SampleSnapBack(aDelta);
+    return continueX || continueY;
   }
 
 private:
   AsyncPanZoomController& mApzc;
 };
 
 void
 AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
@@ -688,16 +709,31 @@ nsEventStatus AsyncPanZoomController::Ha
       case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break;
       case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break;
       case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break;
       case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break;
       default: NS_WARNING("Unhandled multitouch"); break;
     }
     break;
   }
+  case PANGESTURE_INPUT: {
+    const PanGestureInput& panGestureInput = aEvent.AsPanGestureInput();
+    switch (panGestureInput.mType) {
+      case PanGestureInput::PANGESTURE_MAYSTART: rv = OnPanMayBegin(panGestureInput); break;
+      case PanGestureInput::PANGESTURE_CANCELLED: rv = OnPanCancelled(panGestureInput); break;
+      case PanGestureInput::PANGESTURE_START: rv = OnPanBegin(panGestureInput); break;
+      case PanGestureInput::PANGESTURE_PAN: rv = OnPan(panGestureInput, true); break;
+      case PanGestureInput::PANGESTURE_END: rv = OnPanEnd(panGestureInput); break;
+      case PanGestureInput::PANGESTURE_MOMENTUMSTART: rv = OnPanMomentumStart(panGestureInput); break;
+      case PanGestureInput::PANGESTURE_MOMENTUMPAN: rv = OnPan(panGestureInput, false); break;
+      case PanGestureInput::PANGESTURE_MOMENTUMEND: rv = OnPanMomentumEnd(panGestureInput); break;
+      default: NS_WARNING("Unhandled pan gesture"); break;
+    }
+    break;
+  }
   default: NS_WARNING("Unhandled input event"); break;
   }
 
   return rv;
 }
 
 nsEventStatus AsyncPanZoomController::HandleGestureEvent(const InputData& aEvent)
 {
@@ -1059,16 +1095,103 @@ AsyncPanZoomController::ConvertToGecko(c
       ReentrantMonitorAutoEnter lock(mMonitor);
       *aOut = layoutPoint / mFrameMetrics.mDevPixelsPerCSSPixel;
     }
     return true;
   }
   return false;
 }
 
+nsEventStatus AsyncPanZoomController::OnPanMayBegin(const PanGestureInput& aEvent) {
+  APZC_LOG("%p got a pan-maybegin in state %d\n", this, mState);
+
+  mX.StartTouch(aEvent.mPanStartPoint.x, aEvent.mTime);
+  mY.StartTouch(aEvent.mPanStartPoint.y, aEvent.mTime);
+  CancelAnimation();
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnPanCancelled(const PanGestureInput& aEvent) {
+  APZC_LOG("%p got a pan-cancelled in state %d\n", this, mState);
+
+  mX.CancelTouch();
+  mY.CancelTouch();
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+
+nsEventStatus AsyncPanZoomController::OnPanBegin(const PanGestureInput& aEvent) {
+  APZC_LOG("%p got a pan-begin in state %d\n", this, mState);
+
+  mX.StartTouch(aEvent.mPanStartPoint.x, aEvent.mTime);
+  mY.StartTouch(aEvent.mPanStartPoint.y, aEvent.mTime);
+
+  if (GetAxisLockMode() == FREE) {
+    SetState(PANNING);
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
+  float dx = aEvent.mPanDisplacement.x, dy = aEvent.mPanDisplacement.y;
+  double angle = atan2(dy, dx); // range [-pi, pi]
+  angle = fabs(angle); // range [0, pi]
+
+  HandlePanning(angle);
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, bool aFingersOnTouchpad) {
+  APZC_LOG("%p got a pan-pan in state %d\n", this, mState);
+
+  // We need to update the axis velocity in order to get a useful display port
+  // size and position. We need to do so even if this is a momentum pan (i.e.
+  // aFingersOnTouchpad == false); in that case the "with touch" part is not
+  // really appropriate, so we may want to rethink this at some point.
+  mX.UpdateWithTouchAtDevicePoint(aEvent.mPanStartPoint.x, aEvent.mTime);
+  mY.UpdateWithTouchAtDevicePoint(aEvent.mPanStartPoint.y, aEvent.mTime);
+
+  HandlePanningUpdate(aEvent.mPanDisplacement.x, aEvent.mPanDisplacement.y);
+
+  CallDispatchScroll(aEvent.mPanStartPoint, aEvent.mPanStartPoint + aEvent.mPanDisplacement, 0);
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) {
+  APZC_LOG("%p got a pan-end in state %d\n", this, mState);
+
+  mX.EndTouch(aEvent.mTime);
+  mY.EndTouch(aEvent.mTime);
+  RequestContentRepaint();
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnPanMomentumStart(const PanGestureInput& aEvent) {
+  APZC_LOG("%p got a pan-momentumstart in state %d\n", this, mState);
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
+nsEventStatus AsyncPanZoomController::OnPanMomentumEnd(const PanGestureInput& aEvent) {
+  APZC_LOG("%p got a pan-momentumend in state %d\n", this, mState);
+
+  // We need to reset the velocity to zero. We don't really have a "touch"
+  // here because the touch has already ended long before the momentum
+  // animation started, but I guess it doesn't really matter for now.
+  mX.CancelTouch();
+  mY.CancelTouch();
+
+  RequestContentRepaint();
+
+  return nsEventStatus_eConsumeNoDefault;
+}
+
 nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
   APZC_LOG("%p got a long-press in state %d\n", this, mState);
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     int32_t modifiers = WidgetModifiersToDOMModifiers(aEvent.modifiers);
     CSSPoint geckoScreenPoint;
     if (ConvertToGecko(aEvent.mPoint, &geckoScreenPoint)) {
       SetState(WAITING_CONTENT_RESPONSE);
@@ -1209,16 +1332,17 @@ void AsyncPanZoomController::HandlePanni
       SetState(NOTHING);
     }
   } else {
     SetState(NOTHING);
   }
 }
 
 void AsyncPanZoomController::HandlePanning(double aAngle) {
+  ReentrantMonitorAutoEnter lock(mMonitor);
   if (!gfxPrefs::APZCrossSlideEnabled() && (!mX.CanScrollNow() || !mY.CanScrollNow())) {
     SetState(PANNING);
   } else if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) {
     mY.SetAxisLocked(true);
     if (mX.CanScrollNow()) {
       SetState(PANNING_LOCKED_X);
     } else {
       SetState(CROSS_SLIDING_X);
@@ -1232,16 +1356,41 @@ void AsyncPanZoomController::HandlePanni
       SetState(CROSS_SLIDING_Y);
       mY.SetAxisLocked(true);
     }
   } else {
     SetState(PANNING);
   }
 }
 
+void AsyncPanZoomController::HandlePanningUpdate(float aDX, float aDY) {
+  // If we're axis-locked, check if the user is trying to break the lock
+  if (GetAxisLockMode() == STICKY && !mPanDirRestricted) {
+
+    double angle = atan2(aDY, aDX); // range [-pi, pi]
+    angle = fabs(angle); // range [0, pi]
+
+    float breakThreshold = AXIS_BREAKOUT_THRESHOLD * APZCTreeManager::GetDPI();
+
+    if (fabs(aDX) > breakThreshold || fabs(aDY) > breakThreshold) {
+      if (mState == PANNING_LOCKED_X || mState == CROSS_SLIDING_X) {
+        if (!IsCloseToHorizontal(angle, AXIS_BREAKOUT_ANGLE)) {
+          mY.SetAxisLocked(false);
+          SetState(PANNING);
+        }
+      } else if (mState == PANNING_LOCKED_Y || mState == CROSS_SLIDING_Y) {
+        if (!IsCloseToVertical(angle, AXIS_BREAKOUT_ANGLE)) {
+          mX.SetAxisLocked(false);
+          SetState(PANNING);
+        }
+      }
+    }
+  }
+}
+
 nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent);
   float dx = mX.PanDistance(point.x);
   float dy = mY.PanDistance(point.y);
 
   // When the touch move breaks through the pan threshold, reposition the touch down origin
@@ -1409,41 +1558,19 @@ bool AsyncPanZoomController::CallDispatc
       && treeManagerLocal->DispatchScroll(this, aStartPoint, aEndPoint,
                                           aOverscrollHandoffChainIndex);
 }
 
 void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
   ScreenIntPoint prevTouchPoint(mX.GetPos(), mY.GetPos());
   ScreenIntPoint touchPoint = GetFirstTouchScreenPoint(aEvent);
 
-  // If we're axis-locked, check if the user is trying to break the lock
-  if (GetAxisLockMode() == STICKY && !mPanDirRestricted) {
-    ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent);
-    float dx = mX.PanDistance(point.x);
-    float dy = mY.PanDistance(point.y);
-
-    double angle = atan2(dy, dx); // range [-pi, pi]
-    angle = fabs(angle); // range [0, pi]
-
-    float breakThreshold = AXIS_BREAKOUT_THRESHOLD * APZCTreeManager::GetDPI();
-
-    if (fabs(dx) > breakThreshold || fabs(dy) > breakThreshold) {
-      if (mState == PANNING_LOCKED_X || mState == CROSS_SLIDING_X) {
-        if (!IsCloseToHorizontal(angle, AXIS_BREAKOUT_ANGLE)) {
-          mY.SetAxisLocked(false);
-          SetState(PANNING);
-        }
-      } else if (mState == PANNING_LOCKED_Y || mState == CROSS_SLIDING_Y) {
-        if (!IsCloseToVertical(angle, AXIS_BREAKOUT_ANGLE)) {
-          mX.SetAxisLocked(false);
-          SetState(PANNING);
-        }
-      }
-    }
-  }
+  float dx = mX.PanDistance(touchPoint.x);
+  float dy = mY.PanDistance(touchPoint.y);
+  HandlePanningUpdate(dx, dy);
 
   UpdateWithTouchAtDevicePoint(aEvent);
 
   if (prevTouchPoint != touchPoint) {
     CallDispatchScroll(prevTouchPoint, touchPoint, 0);
   }
 }
 
@@ -1461,18 +1588,24 @@ bool FlingAnimation::Sample(FrameMetrics
   // SampleContentTransformForFrame() with the same sample time. If we allow
   // the negative aDelta to be processed, it will yield a displacement in the
   // direction opposite to the fling, which can cause us to overscroll and
   // hand off the fling to _our_ parent, which effectively kills the fling.
   if (aDelta.ToMilliseconds() <= 0) {
     return true;
   }
 
-  bool shouldContinueFlingX = mApzc.mX.FlingApplyFrictionOrCancel(aDelta),
-       shouldContinueFlingY = mApzc.mY.FlingApplyFrictionOrCancel(aDelta);
+  bool overscrolled = mApzc.IsOverscrolled();
+  float friction = overscrolled ? gfxPrefs::APZOverscrollFlingFriction()
+                                : gfxPrefs::APZFlingFriction();
+  float threshold = overscrolled ? gfxPrefs::APZOverscrollFlingStoppedThreshold()
+                                 : gfxPrefs::APZFlingStoppedThreshold();
+
+  bool shouldContinueFlingX = mApzc.mX.FlingApplyFrictionOrCancel(aDelta, friction, threshold),
+       shouldContinueFlingY = mApzc.mY.FlingApplyFrictionOrCancel(aDelta, friction, threshold);
   // If we shouldn't continue the fling, let's just stop and repaint.
   if (!shouldContinueFlingX && !shouldContinueFlingY) {
     // If we are in overscroll, schedule the snap-back animation that relieves it.
     if (mApzc.IsOverscrolled()) {
       mDeferredTasks.append(NewRunnableMethod(&mApzc, &AsyncPanZoomController::StartSnapBack));
     }
     return false;
   }
@@ -1816,96 +1949,102 @@ bool AsyncPanZoomController::UpdateAnima
     mLastSampleTime = aSampleTime;
     return true;
   }
   return false;
 }
 
 void AsyncPanZoomController::ApplyOverscrollEffect(ViewTransform* aTransform) const {
   // The overscroll effect applied here is a combination of a translation in
-  // the direction of overscroll, and shrinking in both directions. For
-  // example, when overscrolling past the top of the page, the rectangle of
-  // content that filled the composition bounds will now fill a smaller
-  // rectangle at the bottom of the composition bounds, centred horizontally.
-  // The magnitude of the translation and the shrinking depends on the amount
-  // of the overscroll.
+  // the direction of overscroll, and shrinking in both directions.
   // With the effect applied, we can think of the composited region as being
   // made up of the following subregions.
-  //  (1) The shrunk content that used to fill the composited region.
+  //  (1) The shrunk content (or a portion of it) that used to fill the
+  //      composited region.
   //  (2) The space created along the axis that has overscroll. This space is
   //      blank, filled by the background color of the overscrolled content.
   //      TODO(botond): Implement handling of background color.
   //  (3) The space created along the other axis. There may or may not be
   //      content available to fill this space, depending on our scroll
   //      position along this axis. If there is content, it's shown from the
   //      displayport. (TODO: Currently we don't take any measures to ensure
   //      that the displayport is large enough to have this content. Perhaps
   //      we should.) Otherwise, these spaces are also blank like (2).
   // To illustrate, for the case where we are overscrolling past the top of
   // the page, these regions are (1) the bottom-centre region, (2) the top
   // regions, and (3) the bottom-left and bottom-right regions of the
   // composited area, respectively.
 
   // The maximum proportion of the composition length which can become blank
   // space along an axis as we overscroll along that axis.
-  const float CLAMPING = 0.5;
+  const float kClamping = gfxPrefs::APZOverscrollClamping();
 
   // The proportion of the composition length which will become blank space
   // along each axis as a result of overscroll along that axis. Since
   // Axis::ApplyResistance() keeps the magnitude of the overscroll in the range
   // [0, GetCompositionLength()], these scale factors should be in the range
-  // [0, CLAMPING].
-  float spacePropX = CLAMPING * fabsf(mX.GetOverscroll()) / mX.GetCompositionLength();
-  float spacePropY = CLAMPING * fabsf(mY.GetOverscroll()) / mY.GetCompositionLength();
+  // [0, kClamping].
+  float spacePropX = kClamping * fabsf(mX.GetOverscroll()) / mX.GetCompositionLength();
+  float spacePropY = kClamping * fabsf(mY.GetOverscroll()) / mY.GetCompositionLength();
+
+  // The fraction of the proportions above which will become blank space along
+  // the _opposite_ axis as we zoom out as a result of overscroll along an axis.
+  // This creates a 3D effect, as in the layer were moving backward along a "z"
+  // axis.
+  const float kZEffect = gfxPrefs::APZOverscrollZEffect();
 
   // The translation to apply for overscroll along the x axis.
   CSSPoint translationX;
-  if (mX.GetOverscroll() < 0) {
-    // Overscroll on left.
-    // Keep content at the midpoint of the screen's right edge fixed.
-    translationX.x = spacePropX * mX.GetCompositionLength();
-    translationX.y = (spacePropX * mY.GetCompositionLength()) / 2;
-  } else if (mX.GetOverscroll() > 0) {
-    // Overscroll on right.
-    // Keep content at the midpoint of the screen's left edge fixed.
-    translationX.y = (spacePropX * mY.GetCompositionLength()) / 2;
+  if (mX.IsOverscrolled()) {
+    // Keep the content centred vertically as we zoom out.
+    translationX.y = (spacePropX * kZEffect * mY.GetCompositionLength()) / 2;
+
+    if (mX.GetOverscroll() < 0) {
+      // Overscroll on left.
+      translationX.x = spacePropX * mX.GetCompositionLength();
+    } else {
+      // Overscroll on right.
+      // Note that zooming out already moves the content at the right edge
+      // of the composition bounds to the left, but since the zooming is
+      // dampened by kZEffect, it doesn't take us as far as we want to go.
+      translationX.x = - (spacePropX * (1 - kZEffect) * mX.GetCompositionLength());
+    }
   }
 
   // The translation to apply for overscroll along the y axis.
   CSSPoint translationY;
-  if (mY.GetOverscroll() < 0) {
-    // Overscroll at top.
-    // Keep content at the midpoint of the screen's bottom edge fixed.
-    translationY.x = (spacePropY * mX.GetCompositionLength()) / 2;
-    translationY.y = spacePropY * mY.GetCompositionLength();
-  } else if (mY.GetOverscroll() > 0) {
-    // Overscroll at bottom.
-    // Keep content at the midpoint of the screen's top edge fixed.
-    translationY.x = (spacePropY * mX.GetCompositionLength()) / 2;
+  if (mY.IsOverscrolled()) {
+    // Keep the content centred horizontally as we zoom out.
+    translationY.x = (spacePropY * kZEffect * mX.GetCompositionLength()) / 2;
+
+    if (mY.GetOverscroll() < 0) {
+      // Overscroll at top.
+      translationY.y = spacePropY * mY.GetCompositionLength();
+    } else {
+      // Overscroll at bottom.
+      // Note that zooming out already moves the content at the bottom edge
+      // of the composition bounds up, but since the zooming is
+      // dampened by kZEffect, it doesn't take us as far as we want to go.
+      translationY.y = - (spacePropY * (1 - kZEffect) * mY.GetCompositionLength());
+    }
   }
 
   // Combine the transformations along the two axes.
-  // TODO(botond): This method of combination is imperfect, and results in a
-  // funny-looking snap-back animation when we have overscroll along both axes.
-  // We should fine-tune this.
-  float spaceProp = std::max(spacePropX, spacePropY);
-  CSSPoint translation(std::max(translationX.x, translationY.x),
-                       std::max(translationX.y, translationY.y));
+  float spaceProp = sqrtf(spacePropX * spacePropX + spacePropY * spacePropY);
+  CSSPoint translation = translationX + translationY;
 
-  // The prpoportion of the composition length which will be taken up by the
-  // original content; this is the scale we will apply to the content.
-  float contentProp = 1 - spaceProp;
+  float scale = 1 - (kZEffect * spaceProp);
 
   // In a ViewTransform, the translation is applied before the scale. We want
   // to apply our translation after our scale, so we compensate for that here.
-  translation.x /= contentProp;
-  translation.y /= contentProp;
+  translation.x /= scale;
+  translation.y /= scale;
 
   // Finally, apply the transformations.
-  aTransform->mScale.scale *= contentProp;
+  aTransform->mScale.scale *= scale;
   aTransform->mTranslation += translation * mFrameMetrics.LayersPixelsPerCSSPixel();
 }
 
 bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
                                                             ViewTransform* aNewTransform,
                                                             ScreenPoint& aScrollOffset) {
   // The eventual return value of this function. The compositor needs to know
   // whether or not to advance by a frame as soon as it can. For example, if a
@@ -1918,19 +2057,21 @@ bool AsyncPanZoomController::SampleConte
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     requestAnimationFrame = UpdateAnimation(aSampleTime, &deferredTasks);
 
     aScrollOffset = mFrameMetrics.GetScrollOffset() * mFrameMetrics.GetZoom();
     *aNewTransform = GetCurrentAsyncTransform();
 
-    // GetCurrentAsyncTransform() does not consider any overscroll we may have.
-    // Adjust the transform to account for that.
-    ApplyOverscrollEffect(aNewTransform);
+    if (IsOverscrolled()) {
+      // GetCurrentAsyncTransform() does not consider any overscroll we may have.
+      // Adjust the transform to account for that.
+      ApplyOverscrollEffect(aNewTransform);
+    }
 
     LogRendertraceRect(GetGuid(), "viewport", "red",
       CSSRect(mFrameMetrics.GetScrollOffset(),
               ParentLayerSize(mFrameMetrics.mCompositionBounds.Size()) / mFrameMetrics.GetZoomToParent()));
 
     mCurrentAsyncScrollOffset = mFrameMetrics.GetScrollOffset();
   }
 
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -383,16 +383,27 @@ protected:
 
   /**
    * Helper method for scales ending. Redraws the screen if necessary and does
    * any cleanup after a scale has ended.
    */
   nsEventStatus OnScaleEnd(const PinchGestureInput& aEvent);
 
   /**
+   * Helper methods for handling pan events.
+   */
+  nsEventStatus OnPanMayBegin(const PanGestureInput& aEvent);
+  nsEventStatus OnPanCancelled(const PanGestureInput& aEvent);
+  nsEventStatus OnPanBegin(const PanGestureInput& aEvent);
+  nsEventStatus OnPan(const PanGestureInput& aEvent, bool aFingersOnTouchpad);
+  nsEventStatus OnPanEnd(const PanGestureInput& aEvent);
+  nsEventStatus OnPanMomentumStart(const PanGestureInput& aEvent);
+  nsEventStatus OnPanMomentumEnd(const PanGestureInput& aEvent);
+
+  /**
    * Helper methods for long press gestures.
    */
   nsEventStatus OnLongPress(const TapGestureInput& aEvent);
   nsEventStatus OnLongPressUp(const TapGestureInput& aEvent);
 
   /**
    * Helper method for single tap gestures.
    */
@@ -467,16 +478,21 @@ protected:
   void HandlePanningWithTouchAction(double angle, TouchBehaviorFlags value);
 
   /**
    * Sets the panning state ignoring the touch action value.
    */
   void HandlePanning(double angle);
 
   /**
+   * Update the panning state and axis locks.
+   */
+  void HandlePanningUpdate(float aDX, float aDY);
+
+  /**
    * Sets up anything needed for panning. This takes us out of the "TOUCHING"
    * state and starts actually panning us.
    */
   nsEventStatus StartPanning(const MultiTouchInput& aStartPoint);
 
   /**
    * Wrapper for Axis::UpdateWithTouchAtDevicePoint(). Calls this function for
    * both axes and factors in the time delta from the last update.
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -116,31 +116,37 @@ void Axis::OverscrollBy(float aOverscrol
   }
   mOverscroll += aOverscroll;
 }
 
 float Axis::GetOverscroll() const {
   return mOverscroll;
 }
 
-void Axis::StartSnapBack() {
-  float initialSnapBackVelocity = gfxPrefs::APZSnapBackInitialVelocity();
-  if (mOverscroll > 0) {
-    mVelocity = -initialSnapBackVelocity;
-  } else {
-    mVelocity = initialSnapBackVelocity;
-  }
-}
-
 bool Axis::SampleSnapBack(const TimeDuration& aDelta) {
-  // Accelerate the snap-back as time goes on.
-  // Note: this method of acceleration isn't perfectly smooth, as it assumes
+  // Apply spring physics to the snap-back as time goes on.
+  // Note: this method of sampling isn't perfectly smooth, as it assumes
   // a constant velocity over 'aDelta', instead of an accelerating velocity.
   // (The way we applying friction to flings has the same issue.)
-  mVelocity *= pow(1.0f + gfxPrefs::APZSnapBackAcceleration(), float(aDelta.ToMilliseconds()));
+  // Hooke's law with damping:
+  //   F = -kx - bv
+  // where
+  //   k is a constant related to the stiffness of the spring
+  //     The larger the constant, the stiffer the spring.
+  //   x is the displacement of the end of the spring from its equilibrium
+  //     In our scenario, it's the amount of overscroll on the axis.
+  //   b is a constant that provides damping (friction)
+  //   v is the velocity of the point at the end of the spring
+  // See http://gafferongames.com/game-physics/spring-physics/
+  const float kSpringStiffness = gfxPrefs::APZOverscrollSnapBackSpringStiffness();
+  const float kSpringFriction = gfxPrefs::APZOverscrollSnapBackSpringFriction();
+  const float kMass = gfxPrefs::APZOverscrollSnapBackMass();
+  float force = -1 * kSpringStiffness * mOverscroll - kSpringFriction * mVelocity;
+  float acceleration = force / kMass;
+  mVelocity += acceleration * aDelta.ToMilliseconds();
   float screenDisplacement = mVelocity * aDelta.ToMilliseconds();
   float cssDisplacement = screenDisplacement / GetFrameMetrics().GetZoom().scale;
   if (mOverscroll > 0) {
     if (cssDisplacement > 0) {
       NS_WARNING("Overscroll snap-back animation is moving in the wrong direction!");
       return false;
     }
     mOverscroll = std::max(mOverscroll + cssDisplacement, 0.0f);
@@ -205,25 +211,27 @@ void Axis::CancelTouch() {
 bool Axis::CanScroll() const {
   return GetPageLength() - GetCompositionLength() > COORDINATE_EPSILON;
 }
 
 bool Axis::CanScrollNow() const {
   return !mAxisLocked && CanScroll();
 }
 
-bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta) {
-  if (fabsf(mVelocity) <= gfxPrefs::APZFlingStoppedThreshold()) {
+bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta,
+                                      float aFriction,
+                                      float aThreshold) {
+  if (fabsf(mVelocity) <= aThreshold) {
     // If the velocity is very low, just set it to 0 and stop the fling,
     // otherwise we'll just asymptotically approach 0 and the user won't
     // actually see any changes.
     mVelocity = 0.0f;
     return false;
   } else {
-    mVelocity *= pow(1.0f - gfxPrefs::APZFlingFriction(), float(aDelta.ToMilliseconds()));
+    mVelocity *= pow(1.0f - aFriction, float(aDelta.ToMilliseconds()));
   }
   return true;
 }
 
 Axis::Overscroll Axis::DisplacementWillOverscroll(float aDisplacement) {
   // If the current pan plus a displacement takes the window to the left of or
   // above the current page rect.
   bool minus = GetOrigin() + aDisplacement < GetPageStart();
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -94,21 +94,16 @@ public:
   void OverscrollBy(float aOverscroll);
 
   /**
    * Return the amount of overscroll on this axis, in CSS pixels.
    */
   float GetOverscroll() const;
 
   /**
-   * Start a snap-back animation to relieve overscroll.
-   */
-  void StartSnapBack();
-
-  /**
    * Sample the snap-back animation to relieve overscroll.
    * |aDelta| is the time since the last sample.
    */
   bool SampleSnapBack(const TimeDuration& aDelta);
 
   /**
    * Return whether this axis is overscrolled in either direction.
    */
@@ -125,20 +120,25 @@ public:
    * Gets the distance between the starting position of the touch supplied in
    * startTouch() and the supplied position.
    */
   float PanDistance(float aPos);
 
   /**
    * Applies friction during a fling, or cancels the fling if the velocity is
    * too low. Returns true if the fling should continue to another frame, or
-   * false if it should end. |aDelta| is the amount of time that has passed
-   * since the last time friction was applied.
+   * false if it should end.
+   * |aDelta| is the amount of time that has passed since the last time
+   * friction was applied.
+   * |aFriction| is the amount of friction to apply.
+   * |aThreshold| is the velocity below which the fling is cancelled.
    */
-  bool FlingApplyFrictionOrCancel(const TimeDuration& aDelta);
+  bool FlingApplyFrictionOrCancel(const TimeDuration& aDelta,
+                                  float aFriction,
+                                  float aThreshold);
 
   /**
    * Returns true if the page has room to be scrolled along this axis.
    */
   bool CanScroll() const;
 
   /**
    * Returns true if the page has room to be scrolled along this axis
--- a/gfx/skia/moz.build
+++ b/gfx/skia/moz.build
@@ -304,40 +304,35 @@ EXPORTS.skia += [
     'trunk/include/xml/SkXMLParser.h',
     'trunk/include/xml/SkXMLWriter.h',
     'trunk/src/ports/SkAtomics_android.h',
     'trunk/src/ports/SkAtomics_sync.h',
     'trunk/src/ports/SkAtomics_win.h',
     'trunk/src/ports/SkMutex_pthread.h',
     'trunk/src/ports/SkMutex_win.h',
 ]
-SOURCES += [
+UNIFIED_SOURCES += [
     'trunk/src/core/SkAAClip.cpp',
     'trunk/src/core/SkAdvancedTypefaceMetrics.cpp',
     'trunk/src/core/SkAlphaRuns.cpp',
     'trunk/src/core/SkAnnotation.cpp',
     'trunk/src/core/SkBBoxHierarchyRecord.cpp',
     'trunk/src/core/SkBBoxRecord.cpp',
     'trunk/src/core/SkBitmap.cpp',
     'trunk/src/core/SkBitmap_scroll.cpp',
     'trunk/src/core/SkBitmapDevice.cpp',
     'trunk/src/core/SkBitmapFilter.cpp',
     'trunk/src/core/SkBitmapHeap.cpp',
     'trunk/src/core/SkBitmapProcShader.cpp',
     'trunk/src/core/SkBitmapProcState.cpp',
-    'trunk/src/core/SkBitmapProcState_matrixProcs.cpp',
     'trunk/src/core/SkBitmapScaler.cpp',
     'trunk/src/core/SkBlitMask_D32.cpp',
     'trunk/src/core/SkBlitRow_D16.cpp',
     'trunk/src/core/SkBlitRow_D32.cpp',
     'trunk/src/core/SkBlitter.cpp',
-    'trunk/src/core/SkBlitter_A8.cpp',
-    'trunk/src/core/SkBlitter_ARGB32.cpp',
-    'trunk/src/core/SkBlitter_RGB16.cpp',
-    'trunk/src/core/SkBlitter_Sprite.cpp',
     'trunk/src/core/SkBuffer.cpp',
     'trunk/src/core/SkCanvas.cpp',
     'trunk/src/core/SkChunkAlloc.cpp',
     'trunk/src/core/SkClipStack.cpp',
     'trunk/src/core/SkColor.cpp',
     'trunk/src/core/SkColorFilter.cpp',
     'trunk/src/core/SkColorTable.cpp',
     'trunk/src/core/SkComposeShader.cpp',
@@ -413,17 +408,16 @@ SOURCES += [
     'trunk/src/core/SkRegion.cpp',
     'trunk/src/core/SkRegion_path.cpp',
     'trunk/src/core/SkRRect.cpp',
     'trunk/src/core/SkRTree.cpp',
     'trunk/src/core/SkScalar.cpp',
     'trunk/src/core/SkScaledImageCache.cpp',
     'trunk/src/core/SkScalerContext.cpp',
     'trunk/src/core/SkScan.cpp',
-    'trunk/src/core/SkScan_Antihair.cpp',
     'trunk/src/core/SkScan_AntiPath.cpp',
     'trunk/src/core/SkScan_Hairline.cpp',
     'trunk/src/core/SkScan_Path.cpp',
     'trunk/src/core/SkShader.cpp',
     'trunk/src/core/SkSpriteBlitter_ARGB32.cpp',
     'trunk/src/core/SkSpriteBlitter_RGB16.cpp',
     'trunk/src/core/SkStream.cpp',
     'trunk/src/core/SkString.cpp',
@@ -548,29 +542,27 @@ SOURCES += [
     'trunk/src/gpu/gl/GrGLVertexBuffer.cpp',
     'trunk/src/gpu/gl/GrGpuGL.cpp',
     'trunk/src/gpu/gl/GrGpuGL_program.cpp',
     'trunk/src/gpu/gl/SkGLContextHelper.cpp',
     'trunk/src/gpu/gl/SkNullGLContext.cpp',
     'trunk/src/gpu/GrAAConvexPathRenderer.cpp',
     'trunk/src/gpu/GrAAHairLinePathRenderer.cpp',
     'trunk/src/gpu/GrAARectRenderer.cpp',
-    'trunk/src/gpu/GrAddPathRenderers_default.cpp',
     'trunk/src/gpu/GrAllocPool.cpp',
     'trunk/src/gpu/GrAtlas.cpp',
     'trunk/src/gpu/GrBitmapTextContext.cpp',
     'trunk/src/gpu/GrBlend.cpp',
     'trunk/src/gpu/GrBufferAllocPool.cpp',
     'trunk/src/gpu/GrCacheID.cpp',
     'trunk/src/gpu/GrClipData.cpp',
     'trunk/src/gpu/GrClipMaskCache.cpp',
     'trunk/src/gpu/GrClipMaskManager.cpp',
     'trunk/src/gpu/GrContext.cpp',
     'trunk/src/gpu/GrDefaultPathRenderer.cpp',
-    'trunk/src/gpu/GrDistanceFieldTextContext.cpp',
     'trunk/src/gpu/GrDrawState.cpp',
     'trunk/src/gpu/GrDrawTarget.cpp',
     'trunk/src/gpu/GrEffect.cpp',
     'trunk/src/gpu/GrGpu.cpp',
     'trunk/src/gpu/GrGpuFactory.cpp',
     'trunk/src/gpu/GrInOrderDrawBuffer.cpp',
     'trunk/src/gpu/GrMemoryPool.cpp',
     'trunk/src/gpu/GrOvalRenderer.cpp',
@@ -658,17 +650,16 @@ SOURCES += [
     'trunk/src/sfnt/SkOTTable_name.cpp',
     'trunk/src/sfnt/SkOTUtils.cpp',
     'trunk/src/utils/SkBase64.cpp',
     'trunk/src/utils/SkBitSet.cpp',
     'trunk/src/utils/SkBoundaryPatch.cpp',
     'trunk/src/utils/SkCamera.cpp',
     'trunk/src/utils/SkCanvasStack.cpp',
     'trunk/src/utils/SkCanvasStateUtils.cpp',
-    'trunk/src/utils/SkCondVar.cpp',
     'trunk/src/utils/SkCountdown.cpp',
     'trunk/src/utils/SkCubicInterval.cpp',
     'trunk/src/utils/SkCullPoints.cpp',
     'trunk/src/utils/SkDeferredCanvas.cpp',
     'trunk/src/utils/SkDumpCanvas.cpp',
     'trunk/src/utils/SkEventTracer.cpp',
     'trunk/src/utils/SkFrontBufferedStream.cpp',
     'trunk/src/utils/SkGatherPixelRefsAndRects.cpp',
@@ -676,26 +667,37 @@ SOURCES += [
     'trunk/src/utils/SkLayer.cpp',
     'trunk/src/utils/SkMatrix44.cpp',
     'trunk/src/utils/SkMD5.cpp',
     'trunk/src/utils/SkMeshUtils.cpp',
     'trunk/src/utils/SkNinePatch.cpp',
     'trunk/src/utils/SkNullCanvas.cpp',
     'trunk/src/utils/SkNWayCanvas.cpp',
     'trunk/src/utils/SkOSFile.cpp',
-    'trunk/src/utils/SkParse.cpp',
     'trunk/src/utils/SkParseColor.cpp',
     'trunk/src/utils/SkParsePath.cpp',
     'trunk/src/utils/SkPathUtils.cpp',
     'trunk/src/utils/SkPictureUtils.cpp',
     'trunk/src/utils/SkProxyCanvas.cpp',
     'trunk/src/utils/SkRTConf.cpp',
     'trunk/src/utils/SkSHA1.cpp',
     'trunk/src/utils/SkUnitMappers.cpp',
 ]
+SOURCES += [
+    'trunk/src/core/SkBitmapProcState_matrixProcs.cpp',
+    'trunk/src/core/SkBlitter_A8.cpp',
+    'trunk/src/core/SkBlitter_ARGB32.cpp',
+    'trunk/src/core/SkBlitter_RGB16.cpp',
+    'trunk/src/core/SkBlitter_Sprite.cpp',
+    'trunk/src/core/SkScan_Antihair.cpp',
+    'trunk/src/gpu/GrAddPathRenderers_default.cpp',
+    'trunk/src/gpu/GrDistanceFieldTextContext.cpp',
+    'trunk/src/utils/SkCondVar.cpp',
+    'trunk/src/utils/SkParse.cpp',
+]
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gonk'):
     SOURCES += [
         'trunk/src/images/SkImageRef_ashmem.cpp',
         'trunk/src/ports/SkDebug_android.cpp',
         'trunk/src/ports/SkFontHost_android_old.cpp',
         'trunk/src/ports/SkFontHost_cairo.cpp',
         'trunk/src/ports/SkFontHost_FreeType.cpp',
         'trunk/src/ports/SkFontHost_FreeType_common.cpp',
new file mode 100644
--- /dev/null
+++ b/gfx/skia/patches/0032-Bug-974900-More-missing-include-guards.patch
@@ -0,0 +1,148 @@
+# HG changeset patch
+# Parent c8288d0c7a1544a590a0cac9c39397ac10c8a45b
+Bug 974900 - Add missing include guards to Skia headers - r=gw280
+
+diff --git a/gfx/skia/trunk/include/images/SkImages.h b/gfx/skia/trunk/include/images/SkImages.h
+--- a/gfx/skia/trunk/include/images/SkImages.h
++++ b/gfx/skia/trunk/include/images/SkImages.h
+@@ -1,14 +1,19 @@
+ /*
+  * Copyright 2012 Google Inc.
+  *
+  * Use of this source code is governed by a BSD-style license that can be
+  * found in the LICENSE file.
+  */
+ 
++#ifndef SkImages_DEFINED
++#define SkImages_DEFINED
++
+ class SkImages {
+ public:
+     /**
+      * Initializes flattenables in the images project.
+      */
+     static void InitializeFlattenables();
+ };
++
++#endif
+diff --git a/gfx/skia/trunk/src/core/SkConvolver.h b/gfx/skia/trunk/src/core/SkConvolver.h
+--- a/gfx/skia/trunk/src/core/SkConvolver.h
++++ b/gfx/skia/trunk/src/core/SkConvolver.h
+@@ -8,16 +8,18 @@
+ #include "SkSize.h"
+ #include "SkTypes.h"
+ #include "SkTArray.h"
+ 
+ // avoid confusion with Mac OS X's math library (Carbon)
+ #if defined(__APPLE__)
+ #undef FloatToConvolutionFixed
+ #undef ConvolutionFixedToFloat
++#undef FloatToFixed
++#undef FixedToFloat
+ #endif
+ 
+ // Represents a filter in one dimension. Each output pixel has one entry in this
+ // object for the filter values contributing to it. You build up the filter
+ // list by calling AddFilter for each output pixel (in order).
+ //
+ // We do 2-dimensional convolution by first convolving each row by one
+ // SkConvolutionFilter1D, then convolving each column by another one.
+diff --git a/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h b/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h
+--- a/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h
++++ b/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h
+@@ -3,24 +3,28 @@
+  * Copyright 2012 Google Inc.
+  *
+  * Use of this source code is governed by a BSD-style license that can be
+  * found in the LICENSE file.
+  */
+ 
+ #include "GrPathRenderer.h"
+ 
++#ifndef GrAAConvexPathRenderer_DEFINED
++#define GrAAConvexPathRenderer_DEFINED
+ 
+ class GrAAConvexPathRenderer : public GrPathRenderer {
+ public:
+     GrAAConvexPathRenderer();
+ 
+     virtual bool canDrawPath(const SkPath& path,
+                              const SkStrokeRec& stroke,
+                              const GrDrawTarget* target,
+                              bool antiAlias) const SK_OVERRIDE;
+ 
+ protected:
+     virtual bool onDrawPath(const SkPath& path,
+                             const SkStrokeRec& stroke,
+                             GrDrawTarget* target,
+                             bool antiAlias) SK_OVERRIDE;
+ };
++
++#endif
+diff --git a/gfx/skia/trunk/src/gpu/GrReducedClip.h b/gfx/skia/trunk/src/gpu/GrReducedClip.h
+--- a/gfx/skia/trunk/src/gpu/GrReducedClip.h
++++ b/gfx/skia/trunk/src/gpu/GrReducedClip.h
+@@ -1,16 +1,19 @@
+ 
+ /*
+  * Copyright 2012 Google Inc.
+  *
+  * Use of this source code is governed by a BSD-style license that can be
+  * found in the LICENSE file.
+  */
+ 
++#ifndef GrReducedClip_DEFINED
++#define GrReducedClip_DEFINED
++
+ #include "SkClipStack.h"
+ #include "SkTLList.h"
+ 
+ namespace GrReducedClip {
+ 
+ typedef SkTLList<SkClipStack::Element> ElementList;
+ 
+ enum InitialState {
+@@ -36,8 +39,10 @@ SK_API void ReduceClipStack(const SkClip
+                             const SkIRect& queryBounds,
+                             ElementList* result,
+                             int32_t* resultGenID,
+                             InitialState* initialState,
+                             SkIRect* tighterBounds = NULL,
+                             bool* requiresAA = NULL);
+ 
+ } // namespace GrReducedClip
++
++#endif
+diff --git a/gfx/skia/trunk/src/pathops/SkLineParameters.h b/gfx/skia/trunk/src/pathops/SkLineParameters.h
+--- a/gfx/skia/trunk/src/pathops/SkLineParameters.h
++++ b/gfx/skia/trunk/src/pathops/SkLineParameters.h
+@@ -1,14 +1,18 @@
+ /*
+  * Copyright 2012 Google Inc.
+  *
+  * Use of this source code is governed by a BSD-style license that can be
+  * found in the LICENSE file.
+  */
++
++#ifndef SkLineParameters_DEFINED
++#define SkLineParameters_DEFINED
++
+ #include "SkPathOpsCubic.h"
+ #include "SkPathOpsLine.h"
+ #include "SkPathOpsQuad.h"
+ 
+ // Sources
+ // computer-aided design - volume 22 number 9 november 1990 pp 538 - 549
+ // online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf
+ 
+@@ -164,8 +168,10 @@ public:
+         return -a;
+     }
+ 
+ private:
+     double a;
+     double b;
+     double c;
+ };
++
++#endif
new file mode 100644
--- /dev/null
+++ b/gfx/skia/patches/0033-Bug-974900-undef-interface-windows.patch
@@ -0,0 +1,27 @@
+# HG changeset patch
+# Parent b12f9a408740aa5fd93c296a7d41e1b5f54c1b20
+Bug 974900 - #undef interface defined by windows headers - r=gw280
+
+diff --git a/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h b/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h
+--- a/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h
++++ b/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h
+@@ -9,16 +9,19 @@
+ #ifndef GrGLCaps_DEFINED
+ #define GrGLCaps_DEFINED
+ 
+ #include "GrDrawTargetCaps.h"
+ #include "GrGLStencilBuffer.h"
+ #include "SkTArray.h"
+ #include "SkTDArray.h"
+ 
++// defined in Windows headers
++#undef interface
++
+ class GrGLContextInfo;
+ 
+ /**
+  * Stores some capabilities of a GL context. Most are determined by the GL
+  * version and the extensions string. It also tracks formats that have passed
+  * the FBO completeness test.
+  */
+ class GrGLCaps : public GrDrawTargetCaps {
--- a/gfx/skia/trunk/include/images/SkImages.h
+++ b/gfx/skia/trunk/include/images/SkImages.h
@@ -1,14 +1,19 @@
 /*
  * Copyright 2012 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
 
+#ifndef SkImages_DEFINED
+#define SkImages_DEFINED
+
 class SkImages {
 public:
     /**
      * Initializes flattenables in the images project.
      */
     static void InitializeFlattenables();
 };
+
+#endif
--- a/gfx/skia/trunk/src/core/SkConvolver.h
+++ b/gfx/skia/trunk/src/core/SkConvolver.h
@@ -8,16 +8,18 @@
 #include "SkSize.h"
 #include "SkTypes.h"
 #include "SkTArray.h"
 
 // avoid confusion with Mac OS X's math library (Carbon)
 #if defined(__APPLE__)
 #undef FloatToConvolutionFixed
 #undef ConvolutionFixedToFloat
+#undef FloatToFixed
+#undef FixedToFloat
 #endif
 
 // Represents a filter in one dimension. Each output pixel has one entry in this
 // object for the filter values contributing to it. You build up the filter
 // list by calling AddFilter for each output pixel (in order).
 //
 // We do 2-dimensional convolution by first convolving each row by one
 // SkConvolutionFilter1D, then convolving each column by another one.
--- a/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h
+++ b/gfx/skia/trunk/src/gpu/GrAAConvexPathRenderer.h
@@ -3,24 +3,28 @@
  * Copyright 2012 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
 
 #include "GrPathRenderer.h"
 
+#ifndef GrAAConvexPathRenderer_DEFINED
+#define GrAAConvexPathRenderer_DEFINED
 
 class GrAAConvexPathRenderer : public GrPathRenderer {
 public:
     GrAAConvexPathRenderer();
 
     virtual bool canDrawPath(const SkPath& path,
                              const SkStrokeRec& stroke,
                              const GrDrawTarget* target,
                              bool antiAlias) const SK_OVERRIDE;
 
 protected:
     virtual bool onDrawPath(const SkPath& path,
                             const SkStrokeRec& stroke,
                             GrDrawTarget* target,
                             bool antiAlias) SK_OVERRIDE;
 };
+
+#endif
--- a/gfx/skia/trunk/src/gpu/GrReducedClip.h
+++ b/gfx/skia/trunk/src/gpu/GrReducedClip.h
@@ -1,16 +1,19 @@
 
 /*
  * Copyright 2012 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
 
+#ifndef GrReducedClip_DEFINED
+#define GrReducedClip_DEFINED
+
 #include "SkClipStack.h"
 #include "SkTLList.h"
 
 namespace GrReducedClip {
 
 typedef SkTLList<SkClipStack::Element> ElementList;
 
 enum InitialState {
@@ -36,8 +39,10 @@ SK_API void ReduceClipStack(const SkClip
                             const SkIRect& queryBounds,
                             ElementList* result,
                             int32_t* resultGenID,
                             InitialState* initialState,
                             SkIRect* tighterBounds = NULL,
                             bool* requiresAA = NULL);
 
 } // namespace GrReducedClip
+
+#endif
--- a/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h
+++ b/gfx/skia/trunk/src/gpu/gl/GrGLCaps.h
@@ -9,16 +9,19 @@
 #ifndef GrGLCaps_DEFINED
 #define GrGLCaps_DEFINED
 
 #include "GrDrawTargetCaps.h"
 #include "GrGLStencilBuffer.h"
 #include "SkTArray.h"
 #include "SkTDArray.h"
 
+// defined in Windows headers
+#undef interface
+
 class GrGLContextInfo;
 
 /**
  * Stores some capabilities of a GL context. Most are determined by the GL
  * version and the extensions string. It also tracks formats that have passed
  * the FBO completeness test.
  */
 class GrGLCaps : public GrDrawTargetCaps {
--- a/gfx/skia/trunk/src/pathops/SkLineParameters.h
+++ b/gfx/skia/trunk/src/pathops/SkLineParameters.h
@@ -1,14 +1,18 @@
 /*
  * Copyright 2012 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+
+#ifndef SkLineParameters_DEFINED
+#define SkLineParameters_DEFINED
+
 #include "SkPathOpsCubic.h"
 #include "SkPathOpsLine.h"
 #include "SkPathOpsQuad.h"
 
 // Sources
 // computer-aided design - volume 22 number 9 november 1990 pp 538 - 549
 // online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf
 
@@ -164,8 +168,10 @@ public:
         return -a;
     }
 
 private:
     double a;
     double b;
     double c;
 };
+
+#endif
--- a/gfx/src/nsCoord.h
+++ b/gfx/src/nsCoord.h
@@ -54,26 +54,26 @@ inline void VERIFY_COORD(nscoord aCoord)
 #ifdef NS_COORD_IS_FLOAT
   NS_ASSERTION(floorf(aCoord) == aCoord,
                "Coords cannot have fractions");
 #endif
 }
 
 inline nscoord NSToCoordRound(float aValue)
 {
-#if defined(XP_WIN32) && defined(_M_IX86) && !defined(__GNUC__)
+#if defined(XP_WIN32) && defined(_M_IX86) && !defined(__GNUC__) && !defined(__clang__)
   return NS_lroundup30(aValue);
 #else
   return nscoord(floorf(aValue + 0.5f));
 #endif /* XP_WIN32 && _M_IX86 && !__GNUC__ */
 }
 
 inline nscoord NSToCoordRound(double aValue)
 {
-#if defined(XP_WIN32) && defined(_M_IX86) && !defined(__GNUC__)
+#if defined(XP_WIN32) && defined(_M_IX86) && !defined(__GNUC__) && !defined(__clang__)
   return NS_lroundup30((float)aValue);
 #else
   return nscoord(floor(aValue + 0.5f));
 #endif /* XP_WIN32 && _M_IX86 && !__GNUC__ */
 }
 
 inline nscoord NSToCoordRoundWithClamp(float aValue)
 {
--- a/gfx/thebes/gfxDWriteFonts.cpp
+++ b/gfx/thebes/gfxDWriteFonts.cpp
@@ -2,21 +2,26 @@
  * 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 "gfxDWriteFonts.h"
 
 #include "mozilla/MemoryReporting.h"
 
+#include "gfxDWriteShaper.h"
+#include "gfxHarfBuzzShaper.h"
 #include <algorithm>
+#include "gfxGraphiteShaper.h"
 #include "gfxDWriteFontList.h"
 #include "gfxContext.h"
 #include <dwrite.h>
 
+#include "gfxDWriteTextAnalysis.h"
+
 #include "harfbuzz/hb.h"
 
 // Chosen this as to resemble DWrite's own oblique face style.
 #define OBLIQUE_SKEW_FACTOR 0.3
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
@@ -99,16 +104,24 @@ gfxDWriteFont::gfxDWriteFont(gfxFontEntr
     rv = fe->CreateFontFace(getter_AddRefs(mFontFace), sims);
 
     if (NS_FAILED(rv)) {
         mIsValid = false;
         return;
     }
 
     ComputeMetrics(anAAOption);
+
+    if (FontCanSupportGraphite()) {
+        mGraphiteShaper = new gfxGraphiteShaper(this);
+    }
+
+    if (FontCanSupportHarfBuzz()) {
+        mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
+    }
 }
 
 gfxDWriteFont::~gfxDWriteFont()
 {
     if (mCairoFontFace) {
         cairo_font_face_destroy(mCairoFontFace);
     }
     if (mScaledFont) {
@@ -119,16 +132,22 @@ gfxDWriteFont::~gfxDWriteFont()
 
 gfxFont*
 gfxDWriteFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
 {
     return new gfxDWriteFont(static_cast<gfxDWriteFontEntry*>(mFontEntry.get()),
                              &mStyle, mNeedsBold, anAAOption);
 }
 
+void
+gfxDWriteFont::CreatePlatformShaper()
+{
+    mPlatformShaper = new gfxDWriteShaper(this);
+}
+
 const gfxFont::Metrics&
 gfxDWriteFont::GetMetrics()
 {
     return *mMetrics;
 }
 
 bool
 gfxDWriteFont::GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS *aFontMetrics)
@@ -571,17 +590,17 @@ gfxDWriteFont::Measure(gfxTextRun *aText
         metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit();
         metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 3;
     }
 
     return metrics;
 }
 
 bool
-gfxDWriteFont::ProvidesGlyphWidths() const
+gfxDWriteFont::ProvidesGlyphWidths()
 {
     return !mUseSubpixelPositions ||
            (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD);
 }
 
 int32_t
 gfxDWriteFont::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID)
 {
--- a/gfx/thebes/gfxDWriteFonts.h
+++ b/gfx/thebes/gfxDWriteFonts.h
@@ -48,17 +48,17 @@ public:
 
     /* override Measure to add padding for antialiasing */
     virtual RunMetrics Measure(gfxTextRun *aTextRun,
                                uint32_t aStart, uint32_t aEnd,
                                BoundingBoxType aBoundingBoxType,
                                gfxContext *aContextForTightBoundingBox,
                                Spacing *aSpacing);
 
-    virtual bool ProvidesGlyphWidths() const;
+    virtual bool ProvidesGlyphWidths();
 
     virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID);
 
     virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions> GetGlyphRenderingOptions();
 
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
@@ -66,16 +66,20 @@ public:
 
     virtual FontType GetType() const { return FONT_TYPE_DWRITE; }
 
     virtual mozilla::TemporaryRef<mozilla::gfx::ScaledFont> GetScaledFont(mozilla::gfx::DrawTarget *aTarget);
 
     virtual cairo_scaled_font_t *GetCairoScaledFont();
 
 protected:
+    friend class gfxDWriteShaper;
+
+    virtual void CreatePlatformShaper();
+
     bool GetFakeMetricsForArialBlack(DWRITE_FONT_METRICS *aFontMetrics);
 
     void ComputeMetrics(AntialiasOption anAAOption);
 
     bool HasBitmapStrikeForSize(uint32_t aSize);
 
     cairo_font_face_t *CairoFontFace();
 
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxDWriteShaper.cpp
@@ -0,0 +1,230 @@
+/* -*- 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 "gfxDWriteShaper.h"
+#include "gfxWindowsPlatform.h"
+
+#include <dwrite.h>
+
+#include "gfxDWriteTextAnalysis.h"
+
+#include "nsCRT.h"
+
+bool
+gfxDWriteShaper::ShapeText(gfxContext      *aContext,
+                           const char16_t *aText,
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText)
+{
+    HRESULT hr;
+    // TODO: Handle TEXT_DISABLE_OPTIONAL_LIGATURES
+
+    DWRITE_READING_DIRECTION readingDirection = 
+        aShapedText->IsRightToLeft()
+            ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT
+            : DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
+
+    gfxDWriteFont *font = static_cast<gfxDWriteFont*>(mFont);
+
+    gfxShapedText::CompressedGlyph g;
+
+    IDWriteTextAnalyzer *analyzer =
+        gfxWindowsPlatform::GetPlatform()->GetDWriteAnalyzer();
+    if (!analyzer) {
+        return false;
+    }
+
+    /**
+     * There's an internal 16-bit limit on some things inside the analyzer,
+     * but we never attempt to shape a word longer than 32K characters
+     * in a single call, so we cannot exceed that limit.
+     */
+    UINT32 length = aLength;
+    char16ptr_t text = aText;
+
+    TextAnalysis analysis(text, length, nullptr, readingDirection);
+    TextAnalysis::Run *runHead;
+    hr = analysis.GenerateResults(analyzer, &runHead);
+
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to generate results.");
+        return false;
+    }
+
+    int32_t appUnitsPerDevPixel = aShapedText->GetAppUnitsPerDevUnit();
+
+    UINT32 maxGlyphs = 0;
+trymoreglyphs:
+    if ((UINT32_MAX - 3 * length / 2 + 16) < maxGlyphs) {
+        // This isn't going to work, we're going to cross the UINT32 upper
+        // limit. Give up.
+        NS_WARNING("Shaper needs to generate more than 2^32 glyphs?!");
+        return false;
+    }
+    maxGlyphs += 3 * length / 2 + 16;
+
+    AutoFallibleTArray<UINT16, 400> clusters;
+    AutoFallibleTArray<UINT16, 400> indices;
+    AutoFallibleTArray<DWRITE_SHAPING_TEXT_PROPERTIES, 400> textProperties;
+    AutoFallibleTArray<DWRITE_SHAPING_GLYPH_PROPERTIES, 400> glyphProperties;
+    if (!clusters.SetLength(length) ||
+        !indices.SetLength(maxGlyphs) || 
+        !textProperties.SetLength(maxGlyphs) ||
+        !glyphProperties.SetLength(maxGlyphs)) {
+        NS_WARNING("Shaper failed to allocate memory.");
+        return false;
+    }
+
+    UINT32 actualGlyphs;
+
+    hr = analyzer->GetGlyphs(text, length,
+            font->GetFontFace(), FALSE, 
+            readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
+            &runHead->mScript, nullptr, nullptr, nullptr, nullptr, 0,
+            maxGlyphs, clusters.Elements(), textProperties.Elements(),
+            indices.Elements(), glyphProperties.Elements(), &actualGlyphs);
+
+    if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) {
+        // We increase the amount of glyphs and try again.
+        goto trymoreglyphs;
+    }
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to get glyphs.");
+        return false;
+    }
+
+    WORD gID = indices[0];
+    AutoFallibleTArray<FLOAT, 400> advances;
+    AutoFallibleTArray<DWRITE_GLYPH_OFFSET, 400> glyphOffsets;
+    if (!advances.SetLength(actualGlyphs) || 
+        !glyphOffsets.SetLength(actualGlyphs)) {
+        NS_WARNING("Shaper failed to allocate memory.");
+        return false;
+    }
+
+    if (!static_cast<gfxDWriteFont*>(mFont)->mUseSubpixelPositions) {
+        hr = analyzer->GetGdiCompatibleGlyphPlacements(
+                                          text,
+                                          clusters.Elements(),
+                                          textProperties.Elements(),
+                                          length,
+                                          indices.Elements(),
+                                          glyphProperties.Elements(),
+                                          actualGlyphs,
+                                          font->GetFontFace(),
+                                          font->GetAdjustedSize(),
+                                          1.0,
+                                          nullptr,
+                                          FALSE,
+                                          FALSE,
+                                          FALSE,
+                                          &runHead->mScript,
+                                          nullptr,
+                                          nullptr,
+                                          nullptr,
+                                          0,
+                                          advances.Elements(),
+                                          glyphOffsets.Elements());
+    } else {
+        hr = analyzer->GetGlyphPlacements(text,
+                                          clusters.Elements(),
+                                          textProperties.Elements(),
+                                          length,
+                                          indices.Elements(),
+                                          glyphProperties.Elements(),
+                                          actualGlyphs,
+                                          font->GetFontFace(),
+                                          font->GetAdjustedSize(),
+                                          FALSE,
+                                          FALSE,
+                                          &runHead->mScript,
+                                          nullptr,
+                                          nullptr,
+                                          nullptr,
+                                          0,
+                                          advances.Elements(),
+                                          glyphOffsets.Elements());
+    }
+    if (FAILED(hr)) {
+        NS_WARNING("Analyzer failed to get glyph placements.");
+        return false;
+    }
+
+    nsAutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs;
+    gfxShapedText::CompressedGlyph *charGlyphs =
+        aShapedText->GetCharacterGlyphs();
+
+    for (unsigned int c = 0; c < length; c++) {
+        uint32_t k = clusters[c];
+        uint32_t absC = aOffset + c;
+
+        if (c > 0 && k == clusters[c - 1]) {
+            // This is a cluster continuation. No glyph here.
+            gfxShapedText::CompressedGlyph &g = charGlyphs[absC];
+            NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
+            g.SetComplex(g.IsClusterStart(), false, 0);
+            continue;
+        }
+
+        // Count glyphs for this character
+        uint32_t glyphCount = actualGlyphs - k;
+        uint32_t nextClusterOffset;
+        for (nextClusterOffset = c + 1; 
+            nextClusterOffset < length; ++nextClusterOffset) {
+            if (clusters[nextClusterOffset] > k) {
+                glyphCount = clusters[nextClusterOffset] - k;
+                break;
+            }
+        }
+        int32_t advance = (int32_t)(advances[k] * appUnitsPerDevPixel);
+        if (glyphCount == 1 && advance >= 0 &&
+            glyphOffsets[k].advanceOffset == 0 &&
+            glyphOffsets[k].ascenderOffset == 0 &&
+            charGlyphs[absC].IsClusterStart() &&
+            gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance) &&
+            gfxShapedText::CompressedGlyph::IsSimpleGlyphID(indices[k])) {
+              charGlyphs[absC].SetSimpleGlyph(advance, indices[k]);
+        } else {
+            if (detailedGlyphs.Length() < glyphCount) {
+                if (!detailedGlyphs.AppendElements(
+                    glyphCount - detailedGlyphs.Length())) {
+                    continue;
+                }
+            }
+            float totalAdvance = 0;
+            for (unsigned int z = 0; z < glyphCount; z++) {
+                detailedGlyphs[z].mGlyphID = indices[k + z];
+                detailedGlyphs[z].mAdvance = 
+                    (int32_t)(advances[k + z]
+                       * appUnitsPerDevPixel);
+                if (readingDirection == 
+                    DWRITE_READING_DIRECTION_RIGHT_TO_LEFT) {
+                    detailedGlyphs[z].mXOffset = 
+                        (totalAdvance + 
+                          glyphOffsets[k + z].advanceOffset)
+                        * appUnitsPerDevPixel;
+                } else {
+                    detailedGlyphs[z].mXOffset = 
+                        glyphOffsets[k + z].advanceOffset *
+                        appUnitsPerDevPixel;
+                }
+                detailedGlyphs[z].mYOffset = 
+                    -glyphOffsets[k + z].ascenderOffset *
+                    appUnitsPerDevPixel;
+                totalAdvance += advances[k + z];
+            }
+            aShapedText->SetGlyphs(
+                absC,
+                g.SetComplex(charGlyphs[absC].IsClusterStart(),
+                             true,
+                             glyphCount),
+                detailedGlyphs.Elements());
+        }
+    }
+
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxDWriteShaper.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#ifndef GFX_DWRITESHAPER_H
+#define GFX_DWRITESHAPER_H
+
+#include "gfxDWriteFonts.h"
+
+/**
+ * \brief Class representing a DWrite font shaper.
+ */
+class gfxDWriteShaper : public gfxFontShaper
+{
+public:
+    gfxDWriteShaper(gfxDWriteFont *aFont)
+        : gfxFontShaper(aFont)
+    {
+        MOZ_COUNT_CTOR(gfxDWriteShaper);
+    }
+
+    virtual ~gfxDWriteShaper()
+    {
+        MOZ_COUNT_DTOR(gfxDWriteShaper);
+    }
+
+    virtual bool ShapeText(gfxContext      *aContext,
+                           const char16_t *aText,
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText);
+};
+
+#endif /* GFX_DWRITESHAPER_H */
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxDWriteTextAnalysis.cpp
@@ -0,0 +1,257 @@
+/* -*- 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 "gfxDWriteTextAnalysis.h"
+
+TextAnalysis::TextAnalysis(const wchar_t* text,
+                           UINT32 textLength,
+                           const wchar_t* localeName,
+                           DWRITE_READING_DIRECTION readingDirection)
+  : mText(text)
+  , mTextLength(textLength)
+  , mLocaleName(localeName)
+  , mReadingDirection(readingDirection)
+  , mCurrentRun(nullptr)
+{
+}
+
+TextAnalysis::~TextAnalysis()
+{
+    // delete runs, except mRunHead which is part of the TextAnalysis object
+    for (Run *run = mRunHead.nextRun; run;) {
+        Run *origRun = run;
+        run = run->nextRun;
+        delete origRun;
+    }
+}
+
+STDMETHODIMP 
+TextAnalysis::GenerateResults(IDWriteTextAnalyzer* textAnalyzer,
+                              OUT Run **runHead)
+{
+    // Analyzes the text using the script analyzer and returns
+    // the result as a series of runs.
+
+    HRESULT hr = S_OK;
+
+    // Initially start out with one result that covers the entire range.
+    // This result will be subdivided by the analysis processes.
+    mRunHead.mTextStart = 0;
+    mRunHead.mTextLength = mTextLength;
+    mRunHead.mBidiLevel = 
+        (mReadingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
+    mRunHead.nextRun = nullptr;
+    mCurrentRun = &mRunHead;
+
+    // Call each of the analyzers in sequence, recording their results.
+    if (SUCCEEDED(hr = textAnalyzer->AnalyzeScript(this,
+                                                   0,
+                                                   mTextLength,
+                                                   this))) {
+        *runHead = &mRunHead;
+    }
+
+    return hr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// IDWriteTextAnalysisSource source implementation
+
+IFACEMETHODIMP 
+TextAnalysis::GetTextAtPosition(UINT32 textPosition,
+                                OUT WCHAR const** textString,
+                                OUT UINT32* textLength)
+{
+    if (textPosition >= mTextLength) {
+        // No text at this position, valid query though.
+        *textString = nullptr;
+        *textLength = 0;
+    } else {
+        *textString = mText + textPosition;
+        *textLength = mTextLength - textPosition;
+    }
+    return S_OK;
+}
+
+
+IFACEMETHODIMP 
+TextAnalysis::GetTextBeforePosition(UINT32 textPosition,
+                                    OUT WCHAR const** textString,
+                                    OUT UINT32* textLength)
+{
+    if (textPosition == 0 || textPosition > mTextLength) {
+        // Either there is no text before here (== 0), or this
+        // is an invalid position. The query is considered valid thouh.
+        *textString = nullptr;
+        *textLength = 0;
+    } else {
+        *textString = mText;
+        *textLength = textPosition;
+    }
+    return S_OK;
+}
+
+
+DWRITE_READING_DIRECTION STDMETHODCALLTYPE 
+TextAnalysis::GetParagraphReadingDirection()
+{
+    // We support only a single reading direction.
+    return mReadingDirection;
+}
+
+
+IFACEMETHODIMP 
+TextAnalysis::GetLocaleName(UINT32 textPosition,
+                            OUT UINT32* textLength,
+                            OUT WCHAR const** localeName)
+{
+    // Single locale name is used, valid until the end of the string.
+    *localeName = mLocaleName;
+    *textLength = mTextLength - textPosition;
+
+    return S_OK;
+}
+
+
+IFACEMETHODIMP 
+TextAnalysis::GetNumberSubstitution(UINT32 textPosition,
+                                    OUT UINT32* textLength,
+                                    OUT IDWriteNumberSubstitution** numberSubstitution)
+{
+    // We do not support number substitution.
+    *numberSubstitution = nullptr;
+    *textLength = mTextLength - textPosition;
+
+    return S_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// IDWriteTextAnalysisSink implementation
+
+IFACEMETHODIMP 
+TextAnalysis::SetLineBreakpoints(UINT32 textPosition,
+                                 UINT32 textLength,
+                                 DWRITE_LINE_BREAKPOINT const* lineBreakpoints)
+{
+    // We don't use this for now.
+    return S_OK;
+}
+
+
+IFACEMETHODIMP 
+TextAnalysis::SetScriptAnalysis(UINT32 textPosition,
+                                UINT32 textLength,
+                                DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis)
+{
+    SetCurrentRun(textPosition);
+    SplitCurrentRun(textPosition);
+    while (textLength > 0) {
+        Run *run = FetchNextRun(&textLength);
+        run->mScript = *scriptAnalysis;
+    }
+
+    return S_OK;
+}
+
+
+IFACEMETHODIMP 
+TextAnalysis::SetBidiLevel(UINT32 textPosition,
+                           UINT32 textLength,
+                           UINT8 explicitLevel,
+                           UINT8 resolvedLevel)
+{
+    // We don't use this for now.
+    return S_OK;
+}
+
+
+IFACEMETHODIMP 
+TextAnalysis::SetNumberSubstitution(UINT32 textPosition,
+                                    UINT32 textLength,
+                                    IDWriteNumberSubstitution* numberSubstitution)
+{
+    // We don't use this for now.
+    return S_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Run modification.
+
+TextAnalysis::Run *
+TextAnalysis::FetchNextRun(IN OUT UINT32* textLength)
+{
+    // Used by the sink setters, this returns a reference to the next run.
+    // Position and length are adjusted to now point after the current run
+    // being returned.
+
+    Run *origRun = mCurrentRun;
+    // Split the tail if needed (the length remaining is less than the
+    // current run's size).
+    if (*textLength < mCurrentRun->mTextLength) {
+        SplitCurrentRun(mCurrentRun->mTextStart + *textLength);
+    } else {
+        // Just advance the current run.
+        mCurrentRun = mCurrentRun->nextRun;
+    }
+    *textLength -= origRun->mTextLength;
+
+    // Return a reference to the run that was just current.
+    return origRun;
+}
+
+
+void TextAnalysis::SetCurrentRun(UINT32 textPosition)
+{
+    // Move the current run to the given position.
+    // Since the analyzers generally return results in a forward manner,
+    // this will usually just return early. If not, find the
+    // corresponding run for the text position.
+
+    if (mCurrentRun && mCurrentRun->ContainsTextPosition(textPosition)) {
+        return;
+    }
+
+    for (Run *run = &mRunHead; run; run = run->nextRun) {
+        if (run->ContainsTextPosition(textPosition)) {
+            mCurrentRun = run;
+            return;
+        }
+    }
+    NS_NOTREACHED("We should always be able to find the text position in one \
+        of our runs");
+}
+
+
+void TextAnalysis::SplitCurrentRun(UINT32 splitPosition)
+{
+    if (!mCurrentRun) {
+        NS_ASSERTION(false, "SplitCurrentRun called without current run.");
+        // Shouldn't be calling this when no current run is set!
+        return;
+    }
+    // Split the current run.
+    if (splitPosition <= mCurrentRun->mTextStart) {
+        // No need to split, already the start of a run
+        // or before it. Usually the first.
+        return;
+    }
+    Run *newRun = new Run;
+
+    *newRun = *mCurrentRun;
+
+    // Insert the new run in our linked list.
+    newRun->nextRun = mCurrentRun->nextRun;
+    mCurrentRun->nextRun = newRun;
+
+    // Adjust runs' text positions and lengths.
+    UINT32 splitPoint = splitPosition - mCurrentRun->mTextStart;
+    newRun->mTextStart += splitPoint;
+    newRun->mTextLength -= splitPoint;
+    mCurrentRun->mTextLength = splitPoint;
+    mCurrentRun = newRun;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxDWriteTextAnalysis.h
@@ -0,0 +1,146 @@
+/* -*- 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/. */
+
+#ifndef GFX_DWRITETEXTANALYSIS_H
+#define GFX_DWRITETEXTANALYSIS_H
+
+#include "gfxDWriteCommon.h"
+
+// Helper source/sink class for text analysis.
+class TextAnalysis
+    :   public IDWriteTextAnalysisSource,
+        public IDWriteTextAnalysisSink        
+{
+public:
+
+    // IUnknown interface
+    IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject)
+    {
+        if (iid == __uuidof(IDWriteTextAnalysisSource)) {
+            *ppObject = static_cast<IDWriteTextAnalysisSource*>(this);
+            return S_OK;
+        } else if (iid == __uuidof(IDWriteTextAnalysisSink)) {
+            *ppObject = static_cast<IDWriteTextAnalysisSink*>(this);
+            return S_OK;
+        } else if (iid == __uuidof(IUnknown)) {
+            *ppObject = 
+                static_cast<IUnknown*>(static_cast<IDWriteTextAnalysisSource*>(this));
+            return S_OK;
+        } else {
+            return E_NOINTERFACE;
+        }
+    }
+
+    IFACEMETHOD_(ULONG, AddRef)()
+    {
+        return 1;
+    }
+
+    IFACEMETHOD_(ULONG, Release)()
+    {
+        return 1;
+    }
+
+    // A single contiguous run of characters containing the same analysis 
+    // results.
+    struct Run
+    {
+        UINT32 mTextStart;   // starting text position of this run
+        UINT32 mTextLength;  // number of contiguous code units covered
+        UINT32 mGlyphStart;  // starting glyph in the glyphs array
+        UINT32 mGlyphCount;  // number of glyphs associated with this run of 
+                             // text
+        DWRITE_SCRIPT_ANALYSIS mScript;
+        UINT8 mBidiLevel;
+        bool mIsSideways;
+
+        inline bool ContainsTextPosition(UINT32 aTextPosition) const
+        {
+            return aTextPosition >= mTextStart
+                && aTextPosition <  mTextStart + mTextLength;
+        }
+
+        Run *nextRun;
+    };
+
+public:
+    TextAnalysis(const wchar_t* text,
+                 UINT32 textLength,
+                 const wchar_t* localeName,
+                 DWRITE_READING_DIRECTION readingDirection);
+
+    ~TextAnalysis();
+
+    STDMETHODIMP GenerateResults(IDWriteTextAnalyzer* textAnalyzer,
+                                 Run **runHead);
+
+    // IDWriteTextAnalysisSource implementation
+
+    IFACEMETHODIMP GetTextAtPosition(UINT32 textPosition,
+                                     OUT WCHAR const** textString,
+                                     OUT UINT32* textLength);
+
+    IFACEMETHODIMP GetTextBeforePosition(UINT32 textPosition,
+                                         OUT WCHAR const** textString,
+                                         OUT UINT32* textLength);
+
+    IFACEMETHODIMP_(DWRITE_READING_DIRECTION) 
+        GetParagraphReadingDirection() throw();
+
+    IFACEMETHODIMP GetLocaleName(UINT32 textPosition,
+                                 OUT UINT32* textLength,
+                                 OUT WCHAR const** localeName);
+
+    IFACEMETHODIMP 
+        GetNumberSubstitution(UINT32 textPosition,
+                              OUT UINT32* textLength,
+                              OUT IDWriteNumberSubstitution** numberSubstitution);
+
+    // IDWriteTextAnalysisSink implementation
+
+    IFACEMETHODIMP 
+        SetScriptAnalysis(UINT32 textPosition,
+                          UINT32 textLength,
+                          DWRITE_SCRIPT_ANALYSIS const* scriptAnalysis);
+
+    IFACEMETHODIMP 
+        SetLineBreakpoints(UINT32 textPosition,
+                           UINT32 textLength,
+                           const DWRITE_LINE_BREAKPOINT* lineBreakpoints);
+
+    IFACEMETHODIMP SetBidiLevel(UINT32 textPosition,
+                                UINT32 textLength,
+                                UINT8 explicitLevel,
+                                UINT8 resolvedLevel);
+
+    IFACEMETHODIMP 
+        SetNumberSubstitution(UINT32 textPosition,
+                              UINT32 textLength,
+                              IDWriteNumberSubstitution* numberSubstitution);
+
+protected:
+    Run *FetchNextRun(IN OUT UINT32* textLength);
+
+    void SetCurrentRun(UINT32 textPosition);
+
+    void SplitCurrentRun(UINT32 splitPosition);
+
+protected:
+    // Input
+    // (weak references are fine here, since this class is a transient
+    //  stack-based helper that doesn't need to copy data)
+    UINT32 mTextLength;
+    const wchar_t* mText;
+    const wchar_t* mLocaleName;
+    DWRITE_READING_DIRECTION mReadingDirection;
+
+    // Current processing state.
+    Run *mCurrentRun;
+
+    // Output is a list of runs starting here
+    Run  mRunHead;
+};
+
+#endif /* GFX_DWRITETEXTANALYSIS_H */
--- a/gfx/thebes/gfxFT2FontBase.h
+++ b/gfx/thebes/gfxFT2FontBase.h
@@ -20,17 +20,17 @@ public:
 
     uint32_t GetGlyph(uint32_t aCharCode);
     void GetGlyphExtents(uint32_t aGlyph,
                          cairo_text_extents_t* aExtents);
     virtual const gfxFont::Metrics& GetMetrics();
     virtual uint32_t GetSpaceGlyph();
     virtual bool ProvidesGetGlyph() const { return true; }
     virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector);
-    virtual bool ProvidesGlyphWidths() const { return true; }
+    virtual bool ProvidesGlyphWidths() { return true; }
     virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID);
 
     cairo_scaled_font_t *CairoScaledFont() { return mScaledFont; };
     virtual bool SetupCairoFont(gfxContext *aContext);
 
     virtual FontType GetType() const { return FONT_TYPE_FT2; }
 
     mozilla::gfx::FontOptions* GetFontOptions() { return &mFontOptions; }
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -19,16 +19,18 @@
 #endif
 
 #include "gfxTypes.h"
 #include "gfxFT2Fonts.h"
 #include "gfxFT2FontBase.h"
 #include "gfxFT2Utils.h"
 #include "gfxFT2FontList.h"
 #include <locale.h>
+#include "gfxHarfBuzzShaper.h"
+#include "gfxGraphiteShaper.h"
 #include "nsGkAtoms.h"
 #include "nsTArray.h"
 #include "nsUnicodeRange.h"
 #include "nsCRT.h"
 #include "nsXULAppAPI.h"
 
 #include "prlog.h"
 #include "prinit.h"
@@ -37,30 +39,52 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/gfx/2D.h"
 
 /**
  * gfxFT2Font
  */
 
 bool
-gfxFT2Font::ShapeText(gfxContext     *aContext,
+gfxFT2Font::ShapeText(gfxContext      *aContext,
                       const char16_t *aText,
-                      uint32_t        aOffset,
-                      uint32_t        aLength,
-                      int32_t         aScript,
-                      gfxShapedText  *aShapedText)
+                      uint32_t         aOffset,
+                      uint32_t         aLength,
+                      int32_t          aScript,
+                      gfxShapedText   *aShapedText,
+                      bool             aPreferPlatformShaping)
 {
-    if (!gfxFont::ShapeText(aContext, aText, aOffset, aLength, aScript,
-                            aShapedText)) {
-        // harfbuzz must have failed(?!), just render raw glyphs
+    bool ok = false;
+
+    if (FontCanSupportGraphite()) {
+        if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+            if (!mGraphiteShaper) {
+                mGraphiteShaper = new gfxGraphiteShaper(this);
+            }
+            ok = mGraphiteShaper->ShapeText(aContext, aText,
+                                            aOffset, aLength,
+                                            aScript, aShapedText);
+        }
+    }
+
+    if (!ok && gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript)) {
+        if (!mHarfBuzzShaper) {
+            mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
+        }
+        ok = mHarfBuzzShaper->ShapeText(aContext, aText,
+                                        aOffset, aLength,
+                                        aScript, aShapedText);
+    }
+
+    if (!ok) {
         AddRange(aText, aOffset, aLength, aShapedText);
-        PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
     }
 
+    PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
+
     return true;
 }
 
 void
 gfxFT2Font::AddRange(const char16_t *aText, uint32_t aOffset,
                      uint32_t aLength, gfxShapedText *aShapedText)
 {
     const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
--- a/gfx/thebes/gfxFT2Fonts.h
+++ b/gfx/thebes/gfxFT2Fonts.h
@@ -72,17 +72,18 @@ public: // new functions
 #endif
 
 protected:
     virtual bool ShapeText(gfxContext      *aContext,
                            const char16_t *aText,
                            uint32_t         aOffset,
                            uint32_t         aLength,
                            int32_t          aScript,
-                           gfxShapedText   *aShapedText);
+                           gfxShapedText   *aShapedText,
+                           bool             aPreferPlatformShaping);
 
     void FillGlyphDataForChar(uint32_t ch, CachedGlyphData *gd);
 
     void AddRange(const char16_t *aText,
                   uint32_t         aOffset,
                   uint32_t         aLength,
                   gfxShapedText   *aShapedText);
 
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -18,17 +18,16 @@
 
 #include "gfxFont.h"
 #include "gfxPlatform.h"
 #include "nsGkAtoms.h"
 
 #include "gfxTypes.h"
 #include "gfxContext.h"
 #include "gfxFontMissingGlyphs.h"
-#include "gfxGraphiteShaper.h"
 #include "gfxHarfBuzzShaper.h"
 #include "gfxUserFontSet.h"
 #include "gfxPlatformFontList.h"
 #include "gfxScriptItemizer.h"
 #include "nsSpecialCasingData.h"
 #include "nsTextRunTransformations.h"
 #include "nsUnicodeProperties.h"
 #include "nsMathUtils.h"
@@ -3940,57 +3939,62 @@ gfxFont::CacheHashEntry::KeyEquals(const
 }
 
 bool
 gfxFont::ShapeText(gfxContext    *aContext,
                    const uint8_t *aText,
                    uint32_t       aOffset,
                    uint32_t       aLength,
                    int32_t        aScript,
-                   gfxShapedText *aShapedText)
+                   gfxShapedText *aShapedText,
+                   bool           aPreferPlatformShaping)
 {
     nsDependentCSubstring ascii((const char*)aText, aLength);
     nsAutoString utf16;
     AppendASCIItoUTF16(ascii, utf16);
     if (utf16.Length() != aLength) {
         return false;
     }
     return ShapeText(aContext, utf16.BeginReading(), aOffset, aLength,
-                     aScript, aShapedText);
+                     aScript, aShapedText, aPreferPlatformShaping);
 }
 
 bool
 gfxFont::ShapeText(gfxContext      *aContext,
                    const char16_t *aText,
                    uint32_t         aOffset,
                    uint32_t         aLength,
                    int32_t          aScript,
-                   gfxShapedText   *aShapedText)
+                   gfxShapedText   *aShapedText,
+                   bool             aPreferPlatformShaping)
 {
     bool ok = false;
 
-    if (FontCanSupportGraphite()) {
-        if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
-            if (!mGraphiteShaper) {
-                mGraphiteShaper = new gfxGraphiteShaper(this);
-            }
-            ok = mGraphiteShaper->ShapeText(aContext, aText, aOffset, aLength,
+    if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+        ok = mGraphiteShaper->ShapeText(aContext, aText, aOffset, aLength,
+                                        aScript, aShapedText);
+    }
+
+    if (!ok && mHarfBuzzShaper && !aPreferPlatformShaping) {
+        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript)) {
+            ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
                                             aScript, aShapedText);
         }
     }
 
     if (!ok) {
-        if (!mHarfBuzzShaper) {
-            mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
-        }
-        ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
-                                        aScript, aShapedText);
-    }
-
-    NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
+        if (!mPlatformShaper) {
+            CreatePlatformShaper();
+            NS_ASSERTION(mPlatformShaper, "no platform shaper available!");
+        }
+        if (mPlatformShaper) {
+            ok = mPlatformShaper->ShapeText(aContext, aText, aOffset, aLength,
+                                            aScript, aShapedText);
+        }
+    }
 
     PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
 
     return ok;
 }
 
 void
 gfxFont::PostShapingFixup(gfxContext      *aContext,
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -1466,22 +1466,22 @@ public:
         NS_ASSERTION(aFont, "shaper requires a valid font!");
     }
 
     virtual ~gfxFontShaper() { }
 
     // Shape a piece of text and store the resulting glyph data into
     // aShapedText. Parameters aOffset/aLength indicate the range of
     // aShapedText to be updated; aLength is also the length of aText.
-    virtual bool ShapeText(gfxContext     *aContext,
+    virtual bool ShapeText(gfxContext      *aContext,
                            const char16_t *aText,
-                           uint32_t        aOffset,
-                           uint32_t        aLength,
-                           int32_t         aScript,
-                           gfxShapedText  *aShapedText) = 0;
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText) = 0;
 
     gfxFont *GetFont() const { return mFont; }
 
     // returns true if features exist in output, false otherwise
     static bool
     MergeFontFeatures(const gfxFontStyle *aStyle,
                       const nsTArray<gfxFontFeature>& aFontFeatures,
                       bool aDisableLigatures,
@@ -1947,17 +1947,17 @@ protected:
     // Default implementation relies on gfxFontEntry::CreateFontInstance;
     // backends that don't implement that will need to override this and use
     // an alternative technique. (gfxPangoFonts, I'm looking at you...)
     virtual already_AddRefed<gfxFont> GetSmallCapsFont();
 
     // subclasses may provide (possibly hinted) glyph widths (in font units);
     // if they do not override this, harfbuzz will use unhinted widths
     // derived from the font tables
-    virtual bool ProvidesGlyphWidths() const {
+    virtual bool ProvidesGlyphWidths() {
         return false;
     }
 
     // The return value is interpreted as a horizontal advance in 16.16 fixed
     // point format.
     virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID) {
         return -1;
     }
@@ -1972,26 +1972,28 @@ protected:
     bool SpaceMayParticipateInShaping(int32_t aRunScript);
 
     // For 8-bit text, expand to 16-bit and then call the following method.
     bool ShapeText(gfxContext    *aContext,
                    const uint8_t *aText,
                    uint32_t       aOffset, // dest offset in gfxShapedText
                    uint32_t       aLength,
                    int32_t        aScript,
-                   gfxShapedText *aShapedText); // where to store the result
+                   gfxShapedText *aShapedText, // where to store the result
+                   bool           aPreferPlatformShaping = false);
 
     // Call the appropriate shaper to generate glyphs for aText and store
     // them into aShapedText.
     virtual bool ShapeText(gfxContext      *aContext,
                            const char16_t *aText,
                            uint32_t         aOffset,
                            uint32_t         aLength,
                            int32_t          aScript,
-                           gfxShapedText   *aShapedText);
+                           gfxShapedText   *aShapedText,
+                           bool             aPreferPlatformShaping = false);
 
     // Helper to adjust for synthetic bold and set character-type flags
     // in the shaped text; implementations of ShapeText should call this
     // after glyph shaping has been completed.
     void PostShapingFixup(gfxContext      *aContext,
                           const char16_t *aText,
                           uint32_t         aOffset, // position within aShapedText
                           uint32_t         aLength,
@@ -2140,24 +2142,29 @@ protected:
 
     // the AA setting requested for this font - may affect glyph bounds
     AntialiasOption            mAntialiasOption;
 
     // a copy of the font without antialiasing, if needed for separate
     // measurement by mathml code
     nsAutoPtr<gfxFont>         mNonAAFont;
 
-    // we create either or both of these shapers when needed, depending
-    // whether the font has graphite tables, and whether graphite shaping
-    // is actually enabled
+    // we may switch between these shapers on the fly, based on the script
+    // of the text run being shaped
+    nsAutoPtr<gfxFontShaper>   mPlatformShaper;
     nsAutoPtr<gfxFontShaper>   mHarfBuzzShaper;
     nsAutoPtr<gfxFontShaper>   mGraphiteShaper;
 
     mozilla::RefPtr<mozilla::gfx::ScaledFont> mAzureScaledFont;
 
+    // Create a default platform text shaper for this font.
+    // (TODO: This should become pure virtual once all font backends have
+    // been updated.)
+    virtual void CreatePlatformShaper() { }
+
     // Helper for subclasses that want to initialize standard metrics from the
     // tables of sfnt (TrueType/OpenType) fonts.
     // This will use mFUnitsConvFactor if it is already set, else compute it
     // from mAdjustedSize and the unitsPerEm in the font's 'head' table.
     // Returns TRUE and sets mIsValid=TRUE if successful;
     // Returns TRUE but leaves mIsValid=FALSE if the font seems to be broken.
     // Returns FALSE if the font does not appear to be an sfnt at all,
     // and should be handled (if possible) using other APIs.
--- a/gfx/thebes/gfxGDIFont.cpp
+++ b/gfx/thebes/gfxGDIFont.cpp
@@ -3,17 +3,21 @@
  * 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 "gfxGDIFont.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/WindowsVersion.h"
 
+#include "gfxGDIShaper.h"
+#include "gfxUniscribeShaper.h"
+#include "gfxHarfBuzzShaper.h"
 #include <algorithm>
+#include "gfxGraphiteShaper.h"
 #include "gfxWindowsPlatform.h"
 #include "gfxContext.h"
 #include "mozilla/Preferences.h"
 #include "nsUnicodeProperties.h"
 #include "gfxFontConstants.h"
 
 #include "cairo-win32.h"
 
@@ -44,65 +48,178 @@ gfxGDIFont::gfxGDIFont(GDIFontEntry *aFo
                        AntialiasOption anAAOption)
     : gfxFont(aFontEntry, aFontStyle, anAAOption),
       mFont(nullptr),
       mFontFace(nullptr),
       mMetrics(nullptr),
       mSpaceGlyph(0),
       mNeedsBold(aNeedsBold)
 {
+    if (FontCanSupportGraphite()) {
+        mGraphiteShaper = new gfxGraphiteShaper(this);
+    }
+    if (FontCanSupportHarfBuzz()) {
+        mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
+    }
 }
 
 gfxGDIFont::~gfxGDIFont()
 {
     if (mScaledFont) {
         cairo_scaled_font_destroy(mScaledFont);
     }
     if (mFontFace) {
         cairo_font_face_destroy(mFontFace);
     }
     if (mFont) {
         ::DeleteObject(mFont);
     }
     delete mMetrics;
 }
 
+void
+gfxGDIFont::CreatePlatformShaper()
+{
+    mPlatformShaper = new gfxGDIShaper(this);
+}
+
 gfxFont*
 gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
 {
     return new gfxGDIFont(static_cast<GDIFontEntry*>(mFontEntry.get()),
                           &mStyle, mNeedsBold, anAAOption);
 }
 
+static bool
+UseUniscribe(gfxShapedText *aShapedText,
+             char16ptr_t aText,
+             uint32_t aLength)
+{
+    uint32_t flags = aShapedText->Flags();
+    bool useGDI;
+
+    bool isXP = !IsVistaOrLater();
+
+    // bug 561304 - Uniscribe bug produces bad positioning at certain
+    // font sizes on XP, so default to GDI on XP using logic of 3.6
+
+    useGDI = isXP &&
+             (flags &
+               (gfxTextRunFactory::TEXT_OPTIMIZE_SPEED | 
+                gfxTextRunFactory::TEXT_IS_RTL)
+             ) == gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
+
+    return !useGDI ||
+        ScriptIsComplex(aText, aLength, SIC_COMPLEX) == S_OK;
+}
+
 bool
-gfxGDIFont::ShapeText(gfxContext     *aContext,
+gfxGDIFont::ShapeText(gfxContext      *aContext,
                       const char16_t *aText,
-                      uint32_t        aOffset,
-                      uint32_t        aLength,
-                      int32_t         aScript,
-                      gfxShapedText  *aShapedText)
+                      uint32_t         aOffset,
+                      uint32_t         aLength,
+                      int32_t          aScript,
+                      gfxShapedText   *aShapedText,
+                      bool             aPreferPlatformShaping)
 {
     if (!mMetrics) {
         Initialize();
     }
     if (!mIsValid) {
         NS_WARNING("invalid font! expect incorrect text rendering");
         return false;
     }
 
+    bool ok = false;
+
     // Ensure the cairo font is set up, so there's no risk it'll fall back to
     // creating a "toy" font internally (see bug 544617).
     // We must check that this succeeded, otherwise we risk cairo creating the
     // wrong kind of font internally as a fallback (bug 744480).
     if (!SetupCairoFont(aContext)) {
         return false;
     }
 
-    return gfxFont::ShapeText(aContext, aText, aOffset, aLength, aScript,
-                              aShapedText);
+    if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+        ok = mGraphiteShaper->ShapeText(aContext, aText,
+                                        aOffset, aLength,
+                                        aScript, aShapedText);
+    }
+
+    if (!ok && mHarfBuzzShaper) {
+        if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript) ||
+            (!IsVistaOrLater() &&
+             ScriptShapingType(aScript) == SHAPING_INDIC &&
+             !Preferences::GetBool("gfx.font_rendering.winxp-indic-uniscribe",
+                                   false))) {
+            ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
+                                            aScript, aShapedText);
+        }
+    }
+
+    if (!ok) {
+        GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
+        bool preferUniscribe =
+            (!fe->IsTrueType() || fe->IsSymbolFont()) && !fe->mForceGDI;
+
+        if (preferUniscribe || UseUniscribe(aShapedText, aText, aLength)) {
+            // first try Uniscribe
+            if (!mUniscribeShaper) {
+                mUniscribeShaper = new gfxUniscribeShaper(this);
+            }
+
+            ok = mUniscribeShaper->ShapeText(aContext, aText, aOffset, aLength,
+                                             aScript, aShapedText);
+            if (!ok) {
+                // fallback to GDI shaping
+                if (!mPlatformShaper) {
+                    CreatePlatformShaper();
+                }
+
+                ok = mPlatformShaper->ShapeText(aContext, aText, aOffset,
+                                                aLength, aScript, aShapedText);
+            }
+        } else {
+            // first use GDI
+            if (!mPlatformShaper) {
+                CreatePlatformShaper();
+            }
+
+            ok = mPlatformShaper->ShapeText(aContext, aText, aOffset, aLength,
+                                            aScript, aShapedText);
+            if (!ok) {
+                // try Uniscribe if GDI failed
+                if (!mUniscribeShaper) {
+                    mUniscribeShaper = new gfxUniscribeShaper(this);
+                }
+
+                // use Uniscribe shaping
+                ok = mUniscribeShaper->ShapeText(aContext, aText,
+                                                 aOffset, aLength,
+                                                 aScript, aShapedText);
+            }
+        }
+
+#if DEBUG
+        if (!ok) {
+            NS_ConvertUTF16toUTF8 name(GetName());
+            char msg[256];
+
+            sprintf(msg, 
+                    "text shaping with both uniscribe and GDI failed for"
+                    " font: %s",
+                    name.get());
+            NS_WARNING(msg);
+        }
+#endif
+    }
+
+    PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
+
+    return ok;
 }
 
 const gfxFont::Metrics&
 gfxGDIFont::GetMetrics()
 {
     if (!mMetrics) {
         Initialize();
     }
@@ -415,52 +532,16 @@ gfxGDIFont::FillLogFont(LOGFONTW& aLogFo
                     (mAntialiasOption == kAntialiasSubpixel) ? true : false);
 
     // If GDI synthetic italic is wanted, force the lfItalic field to true
     if (aUseGDIFakeItalic) {
         aLogFont.lfItalic = 1;
     }
 }
 
-uint32_t
-gfxGDIFont::GetGlyph(uint32_t aUnicode, uint32_t aVarSelector)
-{
-    // Callback used only for fonts that lack a 'cmap' table.
-
-    // We don't support variation selector sequences or non-BMP characters
-    // in the legacy bitmap, vector or postscript fonts that might use
-    // this code path.
-    if (aUnicode > 0xffff || aVarSelector) {
-        return 0;
-    }
-
-    if (!mGlyphIDs) {
-        mGlyphIDs = new nsDataHashtable<nsUint32HashKey,uint32_t>(128);
-    }
-
-    uint32_t gid;
-    if (mGlyphIDs->Get(aUnicode, &gid)) {
-        return gid;
-    }
-
-    AutoDC dc;
-    AutoSelectFont fs(dc.GetDC(), GetHFONT());
-
-    wchar_t ch = aUnicode;
-    WORD glyph;
-    DWORD ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph,
-                                 GGI_MARK_NONEXISTING_GLYPHS);
-    if (ret == GDI_ERROR || glyph == 0xFFFF) {
-        return 0;
-    }
-
-    mGlyphIDs->Put(aUnicode, glyph);
-    return glyph;
-}
-
 int32_t
 gfxGDIFont::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID)
 {
     if (!mGlyphWidths) {
         mGlyphWidths = new nsDataHashtable<nsUint32HashKey,int32_t>(200);
     }
 
     int32_t width;
--- a/gfx/thebes/gfxGDIFont.h
+++ b/gfx/thebes/gfxGDIFont.h
@@ -44,60 +44,56 @@ public:
                                uint32_t aStart, uint32_t aEnd,
                                BoundingBoxType aBoundingBoxType,
                                gfxContext *aContextForTightBoundingBox,
                                Spacing *aSpacing);
 
     /* required for MathML to suppress effects of ClearType "padding" */
     virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption);
 
-    // If the font has a cmap table, we handle it purely with harfbuzz;
-    // but if not (e.g. .fon fonts), we'll use a GDI callback to get glyphs.
-    virtual bool ProvidesGetGlyph() const {
-        return !mFontEntry->HasCmapTable();
-    }
-
-    virtual uint32_t GetGlyph(uint32_t aUnicode, uint32_t aVarSelector);
-
-    virtual bool ProvidesGlyphWidths() const { return true; }
+    virtual bool ProvidesGlyphWidths() { return true; }
 
     // get hinted glyph width in pixels as 16.16 fixed-point value
     virtual int32_t GetGlyphWidth(gfxContext *aCtx, uint16_t aGID);
 
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
 
     virtual FontType GetType() const { return FONT_TYPE_GDI; }
 
 protected:
-    /* override to ensure the cairo font is set up properly */
-    virtual bool ShapeText(gfxContext     *aContext,
+    virtual void CreatePlatformShaper();
+
+    /* override to check for uniscribe failure and fall back to GDI */
+    virtual bool ShapeText(gfxContext      *aContext,
                            const char16_t *aText,
-                           uint32_t        aOffset,
-                           uint32_t        aLength,
-                           int32_t         aScript,
-                           gfxShapedText  *aShapedText);
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText,
+                           bool             aPreferPlatformShaping);
 
     void Initialize(); // creates metrics and Cairo fonts
 
     // Fill the given LOGFONT record according to our style, but don't adjust
     // the lfItalic field if we're going to use a cairo transform for fake
     // italics.
     void FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize, bool aUseGDIFakeItalic);
 
+    // mPlatformShaper is used for the GDI shaper, mUniscribeShaper
+    // for the Uniscribe version if needed
+    nsAutoPtr<gfxFontShaper>   mUniscribeShaper;
+
     HFONT                 mFont;
     cairo_font_face_t    *mFontFace;
 
     Metrics              *mMetrics;
     uint32_t              mSpaceGlyph;
 
     bool                  mNeedsBold;
 
-    // cache of glyph IDs (used for non-sfnt fonts only)
-    nsAutoPtr<nsDataHashtable<nsUint32HashKey,uint32_t> > mGlyphIDs;
-
     // cache of glyph widths in 16.16 fixed-point pixels
     nsAutoPtr<nsDataHashtable<nsUint32HashKey,int32_t> > mGlyphWidths;
 };
 
 #endif /* GFX_GDIFONT_H */
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxGDIShaper.cpp
@@ -0,0 +1,96 @@
+/* -*- 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/. */
+
+//#define FORCE_PR_LOG
+
+#include "gfxGDIShaper.h"
+
+/**********************************************************************
+ *
+ * class gfxGDIShaper
+ *
+ **********************************************************************/
+
+bool
+gfxGDIShaper::ShapeText(gfxContext      *aContext,
+                        const char16_t *aText,
+                        uint32_t         aOffset,
+                        uint32_t         aLength,
+                        int32_t          aScript,
+                        gfxShapedText   *aShapedText)
+{
+    DCFromContext dc(aContext);
+    AutoSelectFont selectFont(dc, static_cast<gfxGDIFont*>(mFont)->GetHFONT());
+
+    uint32_t length = aLength;
+    AutoFallibleTArray<WORD,500> glyphArray;
+    if (!glyphArray.SetLength(length)) {
+        return false;
+    }
+    WORD *glyphs = glyphArray.Elements();
+
+    DWORD ret = ::GetGlyphIndicesW(dc, char16ptr_t(aText), length,
+                                   glyphs, GGI_MARK_NONEXISTING_GLYPHS);
+    if (ret == GDI_ERROR) {
+        return false;
+    }
+
+    for (int k = 0; k < length; k++) {
+        if (glyphs[k] == 0xFFFF)
+            return false;
+    }
+ 
+    SIZE size;
+    AutoFallibleTArray<int,500> partialWidthArray;
+    if (!partialWidthArray.SetLength(length)) {
+        return false;
+    }
+
+    BOOL success = ::GetTextExtentExPointI(dc,
+                                           glyphs,
+                                           length,
+                                           INT_MAX,
+                                           nullptr,
+                                           partialWidthArray.Elements(),
+                                           &size);
+    if (!success) {
+        return false;
+    }
+
+    gfxTextRun::CompressedGlyph g;
+    gfxTextRun::CompressedGlyph *charGlyphs =
+        aShapedText->GetCharacterGlyphs();
+    uint32_t i;
+    int32_t lastWidth = 0;
+    int32_t appUnitsPerDevPixel = aShapedText->GetAppUnitsPerDevUnit();
+    for (i = 0; i < length; ++i) {
+        uint32_t offset = aOffset + i;
+        int32_t advancePixels = partialWidthArray[i] - lastWidth;
+        lastWidth = partialWidthArray[i];
+        int32_t advanceAppUnits = advancePixels * appUnitsPerDevPixel;
+        WCHAR glyph = glyphs[i];
+        NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aText[i]),
+                     "Invalid character detected!");
+        bool atClusterStart = charGlyphs[offset].IsClusterStart();
+        if (advanceAppUnits >= 0 &&
+            gfxShapedWord::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
+            gfxShapedWord::CompressedGlyph::IsSimpleGlyphID(glyph) &&
+            atClusterStart)
+        {
+            charGlyphs[offset].SetSimpleGlyph(advanceAppUnits, glyph);
+        } else {
+            gfxShapedText::DetailedGlyph details;
+            details.mGlyphID = glyph;
+            details.mAdvance = advanceAppUnits;
+            details.mXOffset = 0;
+            details.mYOffset = 0;
+            aShapedText->SetGlyphs(offset,
+                                   g.SetComplex(atClusterStart, true, 1),
+                                   &details);
+        }
+    }
+
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxGDIShaper.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+#ifndef GFX_GDISHAPER_H
+#define GFX_GDISHAPER_H
+
+#include "gfxGDIFont.h"
+
+class gfxGDIShaper : public gfxFontShaper
+{
+public:
+    gfxGDIShaper(gfxGDIFont *aFont)
+        : gfxFontShaper(aFont)
+    {
+        MOZ_COUNT_CTOR(gfxGDIShaper);
+    }
+
+    virtual ~gfxGDIShaper()
+    {
+        MOZ_COUNT_DTOR(gfxGDIShaper);
+    }
+
+    virtual bool ShapeText(gfxContext      *aContext,
+                           const char16_t *aText,
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText);
+};
+
+#endif /* GFX_GDISHAPER_H */
--- a/gfx/thebes/gfxMacFont.cpp
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -3,17 +3,19 @@
  * 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 "gfxMacFont.h"
 
 #include "mozilla/MemoryReporting.h"
 
 #include "gfxCoreTextShaper.h"
+#include "gfxHarfBuzzShaper.h"
 #include <algorithm>
+#include "gfxGraphiteShaper.h"
 #include "gfxPlatformMac.h"
 #include "gfxContext.h"
 #include "gfxFontUtils.h"
 #include "gfxMacPlatformFontList.h"
 #include "gfxFontConstants.h"
 
 #include "cairo-quartz.h"
 
@@ -99,54 +101,59 @@ gfxMacFont::gfxMacFont(MacOSFontEntry *a
         mIsValid = false;
 #ifdef DEBUG
         char warnBuf[1024];
         sprintf(warnBuf, "Failed to create scaled font: %s status: %d",
                 NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
         NS_WARNING(warnBuf);
 #endif
     }
+
+    if (FontCanSupportGraphite()) {
+        mGraphiteShaper = new gfxGraphiteShaper(this);
+    }
+    if (FontCanSupportHarfBuzz()) {
+        mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
+    }
 }
 
 gfxMacFont::~gfxMacFont()
 {
     if (mScaledFont) {
         cairo_scaled_font_destroy(mScaledFont);
     }
     if (mFontFace) {
         cairo_font_face_destroy(mFontFace);
     }
 }
 
 bool
-gfxMacFont::ShapeText(gfxContext     *aContext,
+gfxMacFont::ShapeText(gfxContext      *aContext,
                       const char16_t *aText,
-                      uint32_t        aOffset,
-                      uint32_t        aLength,
-                      int32_t         aScript,
-                      gfxShapedText  *aShapedText)
+                      uint32_t         aOffset,
+                      uint32_t         aLength,
+                      int32_t          aScript,
+                      gfxShapedText   *aShapedText,
+                      bool             aPreferPlatformShaping)
 {
     if (!mIsValid) {
         NS_WARNING("invalid font! expect incorrect text rendering");
         return false;
     }
 
-    if (static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout()) {
-        if (!mCoreTextShaper) {
-            mCoreTextShaper = new gfxCoreTextShaper(this);
-        }
-        if (mCoreTextShaper->ShapeText(aContext, aText, aOffset, aLength,
-                                       aScript, aShapedText)) {
-            PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
-            return true;
-        }
-    }
+    bool requiresAAT =
+        static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout();
+    return gfxFont::ShapeText(aContext, aText, aOffset, aLength,
+                              aScript, aShapedText, requiresAAT);
+}
 
-    return gfxFont::ShapeText(aContext, aText, aOffset, aLength, aScript,
-                              aShapedText);
+void
+gfxMacFont::CreatePlatformShaper()
+{
+    mPlatformShaper = new gfxCoreTextShaper(this);
 }
 
 bool
 gfxMacFont::SetupCairoFont(gfxContext *aContext)
 {
     if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
         // Don't cairo_set_scaled_font as that would propagate the error to
         // the cairo_t, precluding any further drawing.
--- a/gfx/thebes/gfxMacFont.h
+++ b/gfx/thebes/gfxMacFont.h
@@ -46,37 +46,38 @@ public:
     virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
     virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                         FontCacheSizes* aSizes) const;
 
     virtual FontType GetType() const { return FONT_TYPE_MAC; }
 
 protected:
+    virtual void CreatePlatformShaper();
+
     // override to prefer CoreText shaping with fonts that depend on AAT
-    virtual bool ShapeText(gfxContext     *aContext,
+    virtual bool ShapeText(gfxContext      *aContext,
                            const char16_t *aText,
-                           uint32_t        aOffset,
-                           uint32_t        aLength,
-                           int32_t         aScript,
-                           gfxShapedText  *aShapedText);
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText,
+                           bool             aPreferPlatformShaping = false);
 
     void InitMetrics();
     void InitMetricsFromPlatform();
 
     // Get width and glyph ID for a character; uses aConvFactor
     // to convert font units as returned by CG to actual dimensions
     gfxFloat GetCharWidth(CFDataRef aCmap, char16_t aUniChar,
                           uint32_t *aGlyphID, gfxFloat aConvFactor);
 
     // a weak reference to the CoreGraphics font: this is owned by the
     // MacOSFontEntry, it is not retained or released by gfxMacFont
     CGFontRef             mCGFont;
 
     cairo_font_face_t    *mFontFace;
 
-    nsAutoPtr<gfxFontShaper> mCoreTextShaper;
-
     Metrics               mMetrics;
     uint32_t              mSpaceGlyph;
 };
 
 #endif /* GFX_MACFONT_H */
--- a/gfx/thebes/gfxPangoFonts.cpp
+++ b/gfx/thebes/gfxPangoFonts.cpp
@@ -15,16 +15,18 @@
 #ifdef MOZ_WIDGET_QT
 #include "gfxQtPlatform.h"
 #endif
 #include "gfxPangoFonts.h"
 #include "gfxFT2FontBase.h"
 #include "gfxFT2Utils.h"
 #include "harfbuzz/hb.h"
 #include "harfbuzz/hb-ot.h"
+#include "gfxHarfBuzzShaper.h"
+#include "gfxGraphiteShaper.h"
 #include "nsUnicodeProperties.h"
 #include "nsUnicodeScriptCodes.h"
 #include "gfxFontconfigUtils.h"
 #include "gfxUserFontSet.h"
 #include "gfxFontConstants.h"
 
 #include <cairo.h>
 #include <cairo-ft.h>
@@ -656,16 +658,24 @@ public:
 
 #ifdef USE_SKIA
     virtual mozilla::TemporaryRef<mozilla::gfx::GlyphRenderingOptions> GetGlyphRenderingOptions();
 #endif
 
 protected:
     virtual already_AddRefed<gfxFont> GetSmallCapsFont();
 
+    virtual bool ShapeText(gfxContext      *aContext,
+                           const char16_t *aText,
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText,
+                           bool             aPreferPlatformShaping);
+
 private:
     gfxFcFont(cairo_scaled_font_t *aCairoFont, gfxFcFontEntry *aFontEntry,
               const gfxFontStyle *aFontStyle);
 
     // key for locating a gfxFcFont corresponding to a cairo_scaled_font
     static cairo_user_data_key_t sGfxFontKey;
 };
 
@@ -1549,16 +1559,52 @@ gfxFcFont::GetSmallCapsFont()
 
     font = new gfxFcFont(smallFont, fe, &style);
     gfxFontCache::GetCache()->AddNew(font);
     cairo_scaled_font_destroy(smallFont);
 
     return font.forget();
 }
 
+bool
+gfxFcFont::ShapeText(gfxContext      *aContext,
+                     const char16_t *aText,
+                     uint32_t         aOffset,
+                     uint32_t         aLength,
+                     int32_t          aScript,
+                     gfxShapedText   *aShapedText,
+                     bool             aPreferPlatformShaping)
+{
+    bool ok = false;
+
+    if (FontCanSupportGraphite()) {
+        if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+            if (!mGraphiteShaper) {
+                mGraphiteShaper = new gfxGraphiteShaper(this);
+            }
+            ok = mGraphiteShaper->ShapeText(aContext, aText, aOffset, aLength,
+                                            aScript, aShapedText);
+        }
+    }
+
+    if (!ok) {
+        if (!mHarfBuzzShaper) {
+            mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
+        }
+        ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
+                                        aScript, aShapedText);
+    }
+
+    NS_WARN_IF_FALSE(ok, "shaper failed, expect scrambled or missing text");
+
+    PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
+
+    return ok;
+}
+
 /* static */ void
 gfxPangoFontGroup::Shutdown()
 {
     // Resetting gFTLibrary in case this is wanted again after a
     // cairo_debug_reset_static_data.
     gFTLibrary = nullptr;
 }
 
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -138,16 +138,18 @@ public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
 };
 
 NS_IMPL_ISUPPORTS(SRGBOverrideObserver, nsIObserver, nsISupportsWeakReference)
 
 #define GFX_DOWNLOADABLE_FONTS_ENABLED "gfx.downloadable_fonts.enabled"
 
+#define GFX_PREF_HARFBUZZ_SCRIPTS "gfx.font_rendering.harfbuzz.scripts"
+#define HARFBUZZ_SCRIPTS_DEFAULT  mozilla::unicode::SHAPING_DEFAULT
 #define GFX_PREF_FALLBACK_USE_CMAPS  "gfx.font_rendering.fallback.always_use_cmaps"
 
 #define GFX_PREF_OPENTYPE_SVG "gfx.font_rendering.opentype_svg.enabled"
 
 #define GFX_PREF_WORD_CACHE_CHARLIMIT "gfx.font_rendering.wordcache.charlimit"
 #define GFX_PREF_WORD_CACHE_MAXENTRIES "gfx.font_rendering.wordcache.maxentries"
 
 #define GFX_PREF_GRAPHITE_SHAPING "gfx.font_rendering.graphite.enabled"
@@ -255,16 +257,17 @@ static const char *gPrefLangNames[] = {
     "x-tibt",
     "x-unicode",
 };
 
 gfxPlatform::gfxPlatform()
   : mAzureCanvasBackendCollector(MOZ_THIS_IN_INITIALIZER_LIST(),
                                  &gfxPlatform::GetAzureBackendInfo)
 {
+    mUseHarfBuzzScripts = UNINITIALIZED_VALUE;
     mAllowDownloadableFonts = UNINITIALIZED_VALUE;
     mFallbackUsesCmaps = UNINITIALIZED_VALUE;
 
     mWordCacheCharLimit = UNINITIALIZED_VALUE;
     mWordCacheMaxEntries = UNINITIALIZED_VALUE;
     mGraphiteShapingEnabled = UNINITIALIZED_VALUE;
     mOpenTypeSVGEnabled = UNINITIALIZED_VALUE;
     mBidiNumeralOption = UNINITIALIZED_VALUE;
@@ -1105,16 +1108,28 @@ gfxPlatform::UseGraphiteShaping()
     if (mGraphiteShapingEnabled == UNINITIALIZED_VALUE) {
         mGraphiteShapingEnabled =
             Preferences::GetBool(GFX_PREF_GRAPHITE_SHAPING, false);
     }
 
     return mGraphiteShapingEnabled;
 }
 
+bool
+gfxPlatform::UseHarfBuzzForScript(int32_t aScriptCode)
+{
+    if (mUseHarfBuzzScripts == UNINITIALIZED_VALUE) {
+        mUseHarfBuzzScripts = Preferences::GetInt(GFX_PREF_HARFBUZZ_SCRIPTS, HARFBUZZ_SCRIPTS_DEFAULT);
+    }
+
+    int32_t shapingType = mozilla::unicode::ScriptShapingType(aScriptCode);
+
+    return (mUseHarfBuzzScripts & shapingType) != 0;
+}
+
 gfxFontEntry*
 gfxPlatform::MakePlatformFont(const gfxProxyFontEntry *aProxyEntry,
                               const uint8_t *aFontData,
                               uint32_t aLength)
 {
     // Default implementation does not handle activating downloaded fonts;
     // just free the data and return.
     // Platforms that support @font-face must override this,
@@ -1841,16 +1856,19 @@ gfxPlatform::FontsPrefsChanged(const cha
         mWordCacheCharLimit = UNINITIALIZED_VALUE;
         FlushFontAndWordCaches();
     } else if (!strcmp(GFX_PREF_WORD_CACHE_MAXENTRIES, aPref)) {
         mWordCacheMaxEntries = UNINITIALIZED_VALUE;
         FlushFontAndWordCaches();
     } else if (!strcmp(GFX_PREF_GRAPHITE_SHAPING, aPref)) {
         mGraphiteShapingEnabled = UNINITIALIZED_VALUE;
         FlushFontAndWordCaches();
+    } else if (!strcmp(GFX_PREF_HARFBUZZ_SCRIPTS, aPref)) {
+        mUseHarfBuzzScripts = UNINITIALIZED_VALUE;
+        FlushFontAndWordCaches();
     } else if (!strcmp(BIDI_NUMERAL_PREF, aPref)) {
         mBidiNumeralOption = UNINITIALIZED_VALUE;
     } else if (!strcmp(GFX_PREF_OPENTYPE_SVG, aPref)) {
         mOpenTypeSVGEnabled = UNINITIALIZED_VALUE;
         gfxFontCache::GetCache()->AgeAllGenerations();
     }
 }
 
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -380,16 +380,23 @@ public:
     uint32_t WordCacheMaxEntries();
 
     /**
      * Whether to use the SIL Graphite rendering engine
      * (for fonts that include Graphite tables)
      */
     bool UseGraphiteShaping();
 
+    /**
+     * Whether to use the harfbuzz shaper (depending on script complexity).
+     *
+     * This allows harfbuzz to be enabled selectively via the preferences.
+     */
+    bool UseHarfBuzzForScript(int32_t aScriptCode);
+
     // check whether format is supported on a platform or not (if unclear, returns true)
     virtual bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) { return false; }
 
     void GetPrefFonts(nsIAtom *aLanguage, nsString& array, bool aAppendUnicode = true);
 
     // in some situations, need to make decisions about ambiguous characters, may need to look at multiple pref langs
     void GetLangPrefs(eFontPrefLang aPrefLangs[], uint32_t &aLen, eFontPrefLang aCharLang, eFontPrefLang aPageLang);
     
@@ -607,16 +614,19 @@ protected:
     int8_t  mOpenTypeSVGEnabled;
 
     int8_t  mBidiNumeralOption;
 
     // whether to always search font cmaps globally 
     // when doing system font fallback
     int8_t  mFallbackUsesCmaps;
 
+    // which scripts should be shaped with harfbuzz
+    int32_t mUseHarfBuzzScripts;
+
     // max character limit for words in word cache
     int32_t mWordCacheCharLimit;
 
     // max number of entries in word cache
     int32_t mWordCacheMaxEntries;
 
 private:
     /**
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -117,18 +117,23 @@ private:
   DECL_GFX_PREF(Once, "apz.fling_friction",                    APZFlingFriction, float, 0.002f);
   DECL_GFX_PREF(Live, "apz.fling_repaint_interval",            APZFlingRepaintInterval, int32_t, 75);
   DECL_GFX_PREF(Once, "apz.fling_stopped_threshold",           APZFlingStoppedThreshold, float, 0.01f);
   DECL_GFX_PREF(Once, "apz.max_velocity_inches_per_ms",        APZMaxVelocity, float, -1.0f);
   DECL_GFX_PREF(Once, "apz.max_velocity_queue_size",           APZMaxVelocityQueueSize, uint32_t, 5);
   DECL_GFX_PREF(Live, "apz.min_skate_speed",                   APZMinSkateSpeed, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.num_paint_duration_samples",        APZNumPaintDurationSamples, int32_t, 3);
   DECL_GFX_PREF(Live, "apz.overscroll.enabled",                APZOverscrollEnabled, bool, false);
-  DECL_GFX_PREF(Once, "apz.overscroll.snap_back_accel",        APZSnapBackAcceleration, float, 0.002f);
-  DECL_GFX_PREF(Live, "apz.overscroll.snap_back_init_vel",     APZSnapBackInitialVelocity, float, 1.0f);
+  DECL_GFX_PREF(Live, "apz.overscroll.fling_friction",         APZOverscrollFlingFriction, float, 0.02f);
+  DECL_GFX_PREF(Live, "apz.overscroll.fling_stopped_threshold", APZOverscrollFlingStoppedThreshold, float, 0.4f);
+  DECL_GFX_PREF(Live, "apz.overscroll.clamping",               APZOverscrollClamping, float, 0.5f);
+  DECL_GFX_PREF(Live, "apz.overscroll.z_effect",               APZOverscrollZEffect, float, 0.2f);
+  DECL_GFX_PREF(Live, "apz.overscroll.snap_back.spring_stiffness", APZOverscrollSnapBackSpringStiffness, float, 0.6f);
+  DECL_GFX_PREF(Live, "apz.overscroll.snap_back.spring_friction", APZOverscrollSnapBackSpringFriction, float, 0.1f);
+  DECL_GFX_PREF(Live, "apz.overscroll.snap_back.mass",         APZOverscrollSnapBackMass, float, 1000.0f);
   DECL_GFX_PREF(Live, "apz.pan_repaint_interval",              APZPanRepaintInterval, int32_t, 250);
   DECL_GFX_PREF(Live, "apz.subframe.enabled",                  APZSubframeEnabled, bool, false);
   DECL_GFX_PREF(Once, "apz.test.logging_enabled",              APZTestLoggingEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.touch_start_tolerance",             APZTouchStartTolerance, float, 1.0f/4.5f);
   DECL_GFX_PREF(Live, "apz.use_paint_duration",                APZUsePaintDuration, bool, true);
   DECL_GFX_PREF(Live, "apz.velocity_bias",                     APZVelocityBias, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.velocity_relevance_time_ms",        APZVelocityRelevanceTime, uint32_t, 150);
   DECL_GFX_PREF(Live, "apz.x_skate_size_multiplier",           APZXSkateSizeMultiplier, float, 1.5f);
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxUniscribeShaper.cpp
@@ -0,0 +1,527 @@
+/* -*- 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 "gfxTypes.h"
+
+#include "gfxContext.h"
+#include "gfxUniscribeShaper.h"
+#include "gfxWindowsPlatform.h"
+
+#include "gfxFontTest.h"
+
+#include "cairo.h"
+#include "cairo-win32.h"
+
+#include <windows.h>
+
+#include "nsTArray.h"
+
+#include "prinit.h"
+
+/**********************************************************************
+ *
+ * class gfxUniscribeShaper
+ *
+ **********************************************************************/
+
+#define ESTIMATE_MAX_GLYPHS(L) (((3 * (L)) >> 1) + 16)
+
+class UniscribeItem
+{
+public:
+    UniscribeItem(gfxContext *aContext, HDC aDC,
+                  gfxUniscribeShaper *aShaper,
+                  const char16_t *aString, uint32_t aLength,
+                  SCRIPT_ITEM *aItem, uint32_t aIVS) :
+        mContext(aContext), mDC(aDC),
+        mShaper(aShaper),
+        mItemString(aString), mItemLength(aLength), 
+        mAlternativeString(nullptr), mScriptItem(aItem),
+        mScript(aItem->a.eScript),
+        mNumGlyphs(0), mMaxGlyphs(ESTIMATE_MAX_GLYPHS(aLength)),
+        mFontSelected(false), mIVS(aIVS)
+    {
+        // See bug 394751 for details.
+        NS_ASSERTION(mMaxGlyphs < 65535,
+                     "UniscribeItem is too big, ScriptShape() will fail!");
+    }
+
+    ~UniscribeItem() {
+        free(mAlternativeString);
+    }
+
+    bool AllocateBuffers() {
+        return (mGlyphs.SetLength(mMaxGlyphs) &&
+                mClusters.SetLength(mItemLength + 1) &&
+                mAttr.SetLength(mMaxGlyphs));
+    }
+
+    /* possible return values:
+     * S_OK - things succeeded
+     * GDI_ERROR - things failed to shape.  Might want to try again after calling DisableShaping()
+     */
+
+    HRESULT Shape() {
+        HRESULT rv;
+        HDC shapeDC = nullptr;
+
+        char16ptr_t str = mAlternativeString ? mAlternativeString : mItemString;
+
+        mScriptItem->a.fLogicalOrder = true; 
+        SCRIPT_ANALYSIS sa = mScriptItem->a;
+
+        while (true) {
+
+            rv = ScriptShape(shapeDC, mShaper->ScriptCache(),
+                             str, mItemLength,
+                             mMaxGlyphs, &sa,
+                             mGlyphs.Elements(), mClusters.Elements(),
+                             mAttr.Elements(), &mNumGlyphs);
+
+            if (rv == E_OUTOFMEMORY) {
+                mMaxGlyphs *= 2;
+                if (!mGlyphs.SetLength(mMaxGlyphs) ||
+                    !mAttr.SetLength(mMaxGlyphs)) {
+                    return E_OUTOFMEMORY;
+                }
+                continue;
+            }
+
+            // Uniscribe can't do shaping with some fonts, so it sets the 
+            // fNoGlyphIndex flag in the SCRIPT_ANALYSIS structure to indicate
+            // this.  This occurs with CFF fonts loaded with 
+            // AddFontMemResourceEx but it's not clear what the other cases
+            // are. We return an error so our caller can try fallback shaping.
+            // see http://msdn.microsoft.com/en-us/library/ms776520(VS.85).aspx
+
+            if (sa.fNoGlyphIndex) {
+                return GDI_ERROR;
+            }
+
+            if (rv == E_PENDING) {
+                if (shapeDC == mDC) {
+                    // we already tried this once, something failed, give up
+                    return E_PENDING;
+                }
+
+                SelectFont();
+
+                shapeDC = mDC;
+                continue;
+            }
+
+            // http://msdn.microsoft.com/en-us/library/dd368564(VS.85).aspx:
+            // Uniscribe will return this if "the font corresponding to the
+            // DC does not support the script required by the run...".
+            // In this case, we'll set the script code to SCRIPT_UNDEFINED
+            // and try again, so that we'll at least get glyphs even though
+            // they won't necessarily have proper shaping.
+            // (We probably shouldn't have selected this font at all,
+            // but it's too late to fix that here.)
+            if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
+                sa.eScript = SCRIPT_UNDEFINED;
+                NS_WARNING("Uniscribe says font does not support script needed");
+                continue;
+            }
+
+            // Prior to Windows 7, Uniscribe didn't support Ideographic Variation
+            // Selectors. Replace the UVS glyph manually.
+            if (mIVS) {
+                uint32_t lastChar = str[mItemLength - 1];
+                if (NS_IS_LOW_SURROGATE(lastChar)
+                    && NS_IS_HIGH_SURROGATE(str[mItemLength - 2])) {
+                    lastChar = SURROGATE_TO_UCS4(str[mItemLength - 2], lastChar);
+                }
+                uint16_t glyphId = mShaper->GetFont()->GetUVSGlyph(lastChar, mIVS);
+                if (glyphId) {
+                    mGlyphs[mNumGlyphs - 1] = glyphId;
+                }
+            }
+
+            return rv;
+        }
+    }
+
+    bool ShapingEnabled() {
+        return (mScriptItem->a.eScript != SCRIPT_UNDEFINED);
+    }
+    void DisableShaping() {
+        mScriptItem->a.eScript = SCRIPT_UNDEFINED;
+        // Note: If we disable the shaping by using SCRIPT_UNDEFINED and
+        // the string has the surrogate pair, ScriptShape API is
+        // *sometimes* crashed. Therefore, we should replace the surrogate
+        // pair to U+FFFD. See bug 341500.
+        GenerateAlternativeString();
+    }
+    void EnableShaping() {
+        mScriptItem->a.eScript = mScript;
+        if (mAlternativeString) {
+            free(mAlternativeString);
+            mAlternativeString = nullptr;
+        }
+    }
+
+    bool IsGlyphMissing(SCRIPT_FONTPROPERTIES *aSFP, uint32_t aGlyphIndex) {
+        return (mGlyphs[aGlyphIndex] == aSFP->wgDefault);
+    }
+
+
+    HRESULT Place() {
+        HRESULT rv;
+        HDC placeDC = nullptr;
+
+        if (!mOffsets.SetLength(mNumGlyphs) ||
+            !mAdvances.SetLength(mNumGlyphs)) {
+            return E_OUTOFMEMORY;
+        }
+
+        SCRIPT_ANALYSIS sa = mScriptItem->a;
+
+        while (true) {
+            rv = ScriptPlace(placeDC, mShaper->ScriptCache(),
+                             mGlyphs.Elements(), mNumGlyphs,
+                             mAttr.Elements(), &sa,
+                             mAdvances.Elements(), mOffsets.Elements(), nullptr);
+
+            if (rv == E_PENDING) {
+                SelectFont();
+                placeDC = mDC;
+                continue;
+            }
+
+            if (rv == USP_E_SCRIPT_NOT_IN_FONT) {
+                sa.eScript = SCRIPT_UNDEFINED;
+                continue;
+            }
+
+            break;
+        }
+
+        return rv;
+    }
+
+    void ScriptFontProperties(SCRIPT_FONTPROPERTIES *sfp) {
+        HRESULT rv;
+
+        memset(sfp, 0, sizeof(SCRIPT_FONTPROPERTIES));
+        sfp->cBytes = sizeof(SCRIPT_FONTPROPERTIES);
+        rv = ScriptGetFontProperties(nullptr, mShaper->ScriptCache(),
+                                     sfp);
+        if (rv == E_PENDING) {
+            SelectFont();
+            rv = ScriptGetFontProperties(mDC, mShaper->ScriptCache(),
+                                         sfp);
+        }
+    }
+
+    void SaveGlyphs(gfxShapedText *aShapedText, uint32_t aOffset) {
+        uint32_t offsetInRun = mScriptItem->iCharPos;
+
+        // XXX We should store this in the item and only fetch it once
+        SCRIPT_FONTPROPERTIES sfp;
+        ScriptFontProperties(&sfp);
+
+        uint32_t offset = 0;
+        nsAutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs;
+        gfxShapedText::CompressedGlyph g;
+        gfxShapedText::CompressedGlyph *charGlyphs =
+            aShapedText->GetCharacterGlyphs();
+        const uint32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
+        while (offset < mItemLength) {
+            uint32_t runOffset = aOffset + offsetInRun + offset;
+            bool atClusterStart = charGlyphs[runOffset].IsClusterStart();
+            if (offset > 0 && mClusters[offset] == mClusters[offset - 1]) {
+                gfxShapedText::CompressedGlyph &g = charGlyphs[runOffset];
+                NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
+                g.SetComplex(atClusterStart, false, 0);
+            } else {
+                // Count glyphs for this character
+                uint32_t k = mClusters[offset];
+                uint32_t glyphCount = mNumGlyphs - k;
+                uint32_t nextClusterOffset;
+                bool missing = IsGlyphMissing(&sfp, k);
+                for (nextClusterOffset = offset + 1; nextClusterOffset < mItemLength; ++nextClusterOffset) {
+                    if (mClusters[nextClusterOffset] > k) {
+                        glyphCount = mClusters[nextClusterOffset] - k;
+                        break;
+                    }
+                }
+                uint32_t j;
+                for (j = 1; j < glyphCount; ++j) {
+                    if (IsGlyphMissing(&sfp, k + j)) {
+                        missing = true;
+                    }
+                }
+                int32_t advance = mAdvances[k]*appUnitsPerDevUnit;
+                WORD glyph = mGlyphs[k];
+                NS_ASSERTION(!gfxFontGroup::IsInvalidChar(mItemString[offset]),
+                             "invalid character detected");
+                if (missing) {
+                    if (NS_IS_HIGH_SURROGATE(mItemString[offset]) &&
+                        offset + 1 < mItemLength &&
+                        NS_IS_LOW_SURROGATE(mItemString[offset + 1])) {
+                        aShapedText->SetMissingGlyph(runOffset,
+                                                     SURROGATE_TO_UCS4(mItemString[offset],
+                                                                       mItemString[offset + 1]),
+                                                     mShaper->GetFont());
+                    } else {
+                        aShapedText->SetMissingGlyph(runOffset, mItemString[offset],
+                                                     mShaper->GetFont());
+                    }
+                } else if (glyphCount == 1 && advance >= 0 &&
+                    mOffsets[k].dv == 0 && mOffsets[k].du == 0 &&
+                    gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance) &&
+                    gfxShapedText::CompressedGlyph::IsSimpleGlyphID(glyph) &&
+                    atClusterStart)
+                {
+                    charGlyphs[runOffset].SetSimpleGlyph(advance, glyph);
+                } else {
+                    if (detailedGlyphs.Length() < glyphCount) {
+                        if (!detailedGlyphs.AppendElements(glyphCount - detailedGlyphs.Length()))
+                            return;
+                    }
+                    uint32_t i;
+                    for (i = 0; i < glyphCount; ++i) {
+                        gfxTextRun::DetailedGlyph *details = &detailedGlyphs[i];
+                        details->mGlyphID = mGlyphs[k + i];
+                        details->mAdvance = mAdvances[k + i] * appUnitsPerDevUnit;
+                        details->mXOffset = float(mOffsets[k + i].du) * appUnitsPerDevUnit *
+                            aShapedText->GetDirection();
+                        details->mYOffset = - float(mOffsets[k + i].dv) * appUnitsPerDevUnit;
+                    }
+                    aShapedText->SetGlyphs(runOffset,
+                                           g.SetComplex(atClusterStart, true,
+                                                        glyphCount),
+                                           detailedGlyphs.Elements());
+                }
+            }
+            ++offset;
+        }
+    }
+
+    void SelectFont() {
+        if (mFontSelected)
+            return;
+
+        cairo_t *cr = mContext->GetCairo();
+
+        cairo_set_font_face(cr, mShaper->GetFont()->CairoFontFace());
+        cairo_set_font_size(cr, mShaper->GetFont()->GetAdjustedSize());
+        cairo_scaled_font_t *scaledFont = mShaper->GetFont()->CairoScaledFont();
+        cairo_win32_scaled_font_select_font(scaledFont, mDC);
+
+        mFontSelected = true;
+    }
+
+private:
+
+    void GenerateAlternativeString() {
+        if (mAlternativeString)
+            free(mAlternativeString);
+        mAlternativeString = (char16_t *)malloc(mItemLength * sizeof(char16_t));
+        if (!mAlternativeString)
+            return;
+        memcpy((void *)mAlternativeString, (const void *)mItemString,
+               mItemLength * sizeof(char16_t));
+        for (uint32_t i = 0; i < mItemLength; i++) {
+            if (NS_IS_HIGH_SURROGATE(mItemString[i]) || NS_IS_LOW_SURROGATE(mItemString[i]))
+                mAlternativeString[i] = char16_t(0xFFFD);
+        }
+    }
+
+private:
+    nsRefPtr<gfxContext> mContext;
+    HDC mDC;
+    gfxUniscribeShaper *mShaper;
+
+    SCRIPT_ITEM *mScriptItem;
+    WORD mScript;
+
+public:
+    // these point to the full string/length of the item
+    const char16_t *mItemString;
+    const uint32_t mItemLength;
+
+private:
+    char16_t *mAlternativeString;
+
+#define AVERAGE_ITEM_LENGTH 40
+
+    AutoFallibleTArray<WORD, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mGlyphs;
+    AutoFallibleTArray<WORD, AVERAGE_ITEM_LENGTH + 1> mClusters;
+    AutoFallibleTArray<SCRIPT_VISATTR, uint32_t(ESTIMATE_MAX_GLYPHS(AVERAGE_ITEM_LENGTH))> mAttr;
+ 
+    AutoFallibleTArray<GOFFSET, 2 * AVERAGE_ITEM_LENGTH> mOffsets;
+    AutoFallibleTArray<int, 2 * AVERAGE_ITEM_LENGTH> mAdvances;
+
+#undef AVERAGE_ITEM_LENGTH
+
+    int mMaxGlyphs;
+    int mNumGlyphs;
+    uint32_t mIVS;
+
+    bool mFontSelected;
+};
+
+class Uniscribe
+{
+public:
+    Uniscribe(const char16_t *aString,
+              gfxShapedText *aShapedText,
+              uint32_t aOffset, uint32_t aLength):
+        mString(aString), mShapedText(aShapedText),
+        mOffset(aOffset), mLength(aLength)
+    {
+    }
+
+    void Init() {
+        memset(&mControl, 0, sizeof(SCRIPT_CONTROL));
+        memset(&mState, 0, sizeof(SCRIPT_STATE));
+        // Lock the direction. Don't allow the itemizer to change directions
+        // based on character type.
+        mState.uBidiLevel = mShapedText->IsRightToLeft() ? 1 : 0;
+        mState.fOverrideDirection = true;
+    }
+
+public:
+    int Itemize() {
+        HRESULT rv;
+
+        int maxItems = 5;
+
+        Init();
+
+        // Allocate space for one more item than expected, to handle a rare
+        // overflow in ScriptItemize (pre XP SP2). See bug 366643.
+        if (!mItems.SetLength(maxItems + 1)) {
+            return 0;
+        }
+        while ((rv = ScriptItemize(mString, mLength,
+                                   maxItems, &mControl, &mState,
+                                   mItems.Elements(), &mNumItems)) == E_OUTOFMEMORY) {
+            maxItems *= 2;
+            if (!mItems.SetLength(maxItems + 1)) {
+                return 0;
+            }
+            Init();
+        }
+
+        return mNumItems;
+    }
+
+    SCRIPT_ITEM *ScriptItem(uint32_t i) {
+        NS_ASSERTION(i <= (uint32_t)mNumItems, "Trying to get out of bounds item");
+        return &mItems[i];
+    }
+
+private:
+    char16ptr_t      mString;
+    gfxShapedText   *mShapedText;
+    uint32_t         mOffset;
+    uint32_t         mLength;
+
+    SCRIPT_CONTROL mControl;
+    SCRIPT_STATE   mState;
+    FallibleTArray<SCRIPT_ITEM> mItems;
+    int mNumItems;
+};
+
+
+bool
+gfxUniscribeShaper::ShapeText(gfxContext      *aContext,
+                              const char16_t *aText,
+                              uint32_t         aOffset,
+                              uint32_t         aLength,
+                              int32_t          aScript,
+                              gfxShapedText   *aShapedText)
+{
+    DCFromContext aDC(aContext);
+ 
+    bool result = true;
+    HRESULT rv;
+
+    Uniscribe us(aText, aShapedText, aOffset, aLength);
+
+    /* itemize the string */
+    int numItems = us.Itemize();
+
+    uint32_t length = aLength;
+    SaveDC(aDC);
+    uint32_t ivs = 0;
+    for (int i = 0; i < numItems; ++i) {
+        int iCharPos = us.ScriptItem(i)->iCharPos;
+        int iCharPosNext = us.ScriptItem(i+1)->iCharPos;
+
+        if (ivs) {
+            iCharPos += 2;
+            if (iCharPos >= iCharPosNext) {
+                ivs = 0;
+                continue;
+            }
+        }
+
+        if (i+1 < numItems && iCharPosNext <= length - 2
+            && aText[iCharPosNext] == H_SURROGATE(kUnicodeVS17)
+            && uint32_t(aText[iCharPosNext + 1]) - L_SURROGATE(kUnicodeVS17)
+            <= L_SURROGATE(kUnicodeVS256) - L_SURROGATE(kUnicodeVS17)) {
+
+            ivs = SURROGATE_TO_UCS4(aText[iCharPosNext],
+                                    aText[iCharPosNext + 1]);
+        } else {
+            ivs = 0;
+        }
+
+        UniscribeItem item(aContext, aDC, this,
+                           aText + iCharPos,
+                           iCharPosNext - iCharPos,
+                           us.ScriptItem(i), ivs);
+        if (!item.AllocateBuffers()) {
+            result = false;
+            break;
+        }
+
+        if (!item.ShapingEnabled()) {
+            item.EnableShaping();
+        }
+
+        rv = item.Shape();
+        if (FAILED(rv)) {
+            // we know we have the glyphs to display this font already
+            // so Uniscribe just doesn't know how to shape the script.
+            // Render the glyphs without shaping.
+            item.DisableShaping();
+            rv = item.Shape();
+        }
+#ifdef DEBUG
+        if (FAILED(rv)) {
+            NS_WARNING("Uniscribe failed to shape with font");
+        }
+#endif
+
+        if (SUCCEEDED(rv)) {
+            rv = item.Place();
+#ifdef DEBUG
+            if (FAILED(rv)) {
+                // crap fonts may fail when placing (e.g. funky free fonts)
+                NS_WARNING("Uniscribe failed to place with font");
+            }
+#endif
+        }
+
+        if (FAILED(rv)) {
+            // Uniscribe doesn't like this font for some reason.
+            // Returning FALSE will make the gfxGDIFont retry with the
+            // "dumb" GDI shaper, unless useUniscribeOnly was set.
+            result = false;
+            break;
+        }
+
+        item.SaveGlyphs(aShapedText, aOffset);
+    }
+
+    RestoreDC(aDC, -1);
+
+    return result;
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxUniscribeShaper.h
@@ -0,0 +1,51 @@
+/* -*- 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/. */
+
+#ifndef GFX_UNISCRIBESHAPER_H
+#define GFX_UNISCRIBESHAPER_H
+
+#include "gfxTypes.h"
+#include "gfxGDIFont.h"
+
+#include <usp10.h>
+#include <cairo-win32.h>
+
+
+class gfxUniscribeShaper : public gfxFontShaper
+{
+public:
+    gfxUniscribeShaper(gfxGDIFont *aFont)
+        : gfxFontShaper(aFont)
+        , mScriptCache(nullptr)
+    {
+        MOZ_COUNT_CTOR(gfxUniscribeShaper);
+    }
+
+    virtual ~gfxUniscribeShaper()
+    {
+        MOZ_COUNT_DTOR(gfxUniscribeShaper);
+    }
+
+    virtual bool ShapeText(gfxContext      *aContext,
+                           const char16_t *aText,
+                           uint32_t         aOffset,
+                           uint32_t         aLength,
+                           int32_t          aScript,
+                           gfxShapedText   *aShapedText);
+
+    SCRIPT_CACHE *ScriptCache() { return &mScriptCache; }
+
+    gfxGDIFont *GetFont() { return static_cast<gfxGDIFont*>(mFont); }
+
+private:
+    SCRIPT_CACHE mScriptCache;
+
+    enum {
+        kUnicodeVS17 = gfxFontUtils::kUnicodeVS17,
+        kUnicodeVS256 = gfxFontUtils::kUnicodeVS256
+    };
+};
+
+#endif /* GFX_UNISCRIBESHAPER_H */
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -166,32 +166,36 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wi
         'gfxGDIFont.h',
         'gfxGDIFontList.h',
         'gfxPDFSurface.h',
         'gfxPlatformFontList.h',
         'gfxWindowsNativeDrawing.h',
         'gfxWindowsPlatform.h',
         'gfxWindowsSurface.h',
     ]
-    # gfxGDIFontList.cpp forces NSPR logging, so it cannot be built in unified mode.
+    # gfxGDIFontList.cpp and gfxGDIShaper.cpp force NSPR logging, so they cannot be built in unified mode.
     SOURCES += [
         'gfxGDIFont.cpp',
         'gfxGDIFontList.cpp',
+        'gfxGDIShaper.cpp',
         'gfxPDFSurface.cpp',
+        'gfxUniscribeShaper.cpp',
         'gfxWindowsNativeDrawing.cpp',
         'gfxWindowsPlatform.cpp',
         'gfxWindowsSurface.cpp',
     ]
     if CONFIG['MOZ_ENABLE_DWRITE_FONT']:
         # gfxDWriteFontList.cpp forces NSPR logging, so it cannot be built in unified mode.
         SOURCES += [
             'gfxD2DSurface.cpp',
             'gfxDWriteCommon.cpp',
             'gfxDWriteFontList.cpp',
             'gfxDWriteFonts.cpp',
+            'gfxDWriteShaper.cpp',
+            'gfxDWriteTextAnalysis.cpp',
         ]
 
 # Are we targeting x86 or x64?  If so, build gfxAlphaRecoverySSE2.cpp.
 if CONFIG['INTEL_ARCHITECTURE']:
     SOURCES += ['gfxAlphaRecoverySSE2.cpp']
     # The file uses SSE2 intrinsics, so it needs special compile flags on some
     # compilers.
     SOURCES['gfxAlphaRecoverySSE2.cpp'].flags += CONFIG['SSE2_FLAGS']
--- a/intl/unicharutil/util/nsUnicodeProperties.cpp
+++ b/intl/unicharutil/util/nsUnicodeProperties.cpp
@@ -244,16 +244,74 @@ bool
 IsClusterExtender(uint32_t aCh, uint8_t aCategory)
 {
     return ((aCategory >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
              aCategory <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) ||
             (aCh >= 0x200c && aCh <= 0x200d) || // ZWJ, ZWNJ
             (aCh >= 0xff9e && aCh <= 0xff9f));  // katakana sound marks
 }
 
+// TODO: replace this with a properties file or similar;
+// expect this to evolve as harfbuzz shaping support matures.
+//
+// The "shaping type" of each script run, as returned by this
+// function, is compared to the bits set in the
+// gfx.font_rendering.harfbuzz.scripts
+// preference to decide whether to use the harfbuzz shaper.
+//
+int32_t
+ScriptShapingType(int32_t aScriptCode)
+{
+    switch (aScriptCode) {
+    default:
+        return SHAPING_DEFAULT; // scripts not explicitly listed here are
+                                // assumed to just use default shaping
+
+    case MOZ_SCRIPT_ARABIC:
+    case MOZ_SCRIPT_SYRIAC:
+    case MOZ_SCRIPT_NKO:
+    case MOZ_SCRIPT_MANDAIC:
+        return SHAPING_ARABIC; // bidi scripts with Arabic-style shaping
+
+    case MOZ_SCRIPT_HEBREW:
+        return SHAPING_HEBREW;
+
+    case MOZ_SCRIPT_HANGUL:
+        return SHAPING_HANGUL;
+
+    case MOZ_SCRIPT_MONGOLIAN: // to be supported by the Arabic shaper?
+        return SHAPING_MONGOLIAN;
+
+    case MOZ_SCRIPT_THAI: // no complex OT features, but MS engines like to do
+                          // sequence checking
+        return SHAPING_THAI;
+
+    case MOZ_SCRIPT_BENGALI:
+    case MOZ_SCRIPT_DEVANAGARI:
+    case MOZ_SCRIPT_GUJARATI:
+    case MOZ_SCRIPT_GURMUKHI:
+    case MOZ_SCRIPT_KANNADA:
+    case MOZ_SCRIPT_MALAYALAM:
+    case MOZ_SCRIPT_ORIYA:
+    case MOZ_SCRIPT_SINHALA:
+    case MOZ_SCRIPT_TAMIL:
+    case MOZ_SCRIPT_TELUGU:
+    case MOZ_SCRIPT_KHMER:
+    case MOZ_SCRIPT_LAO:
+    case MOZ_SCRIPT_TIBETAN:
+    case MOZ_SCRIPT_NEW_TAI_LUE:
+    case MOZ_SCRIPT_TAI_LE:
+    case MOZ_SCRIPT_MYANMAR:
+    case MOZ_SCRIPT_PHAGS_PA:
+    case MOZ_SCRIPT_BATAK:
+    case MOZ_SCRIPT_BRAHMI:
+        return SHAPING_INDIC; // scripts that require Indic or other "special" shaping
+    }
+}
+
 void
 ClusterIterator::Next()
 {
     if (AtEnd()) {
         NS_WARNING("ClusterIterator has already reached the end");
         return;
     }
 
--- a/intl/unicharutil/util/nsUnicodeProperties.h
+++ b/intl/unicharutil/util/nsUnicodeProperties.h
@@ -114,16 +114,28 @@ inline HSType GetHangulSyllableType(uint
 // Case mappings for the full Unicode range;
 // note that it may be worth testing for ASCII chars and taking
 // a separate fast-path before calling these, in perf-critical places
 uint32_t GetUppercase(uint32_t aCh);
 uint32_t GetLowercase(uint32_t aCh);
 uint32_t GetTitlecaseForLower(uint32_t aCh); // maps LC to titlecase, UC unchanged
 uint32_t GetTitlecaseForAll(uint32_t aCh); // maps both UC and LC to titlecase
 
+enum ShapingType {
+  SHAPING_DEFAULT   = 0x0001,
+  SHAPING_ARABIC    = 0x0002,
+  SHAPING_HEBREW    = 0x0004,
+  SHAPING_HANGUL    = 0x0008,
+  SHAPING_MONGOLIAN = 0x0010,
+  SHAPING_INDIC     = 0x0020,
+  SHAPING_THAI      = 0x0040
+};
+
+int32_t ScriptShapingType(int32_t aScriptCode);
+
 // A simple iterator for a string of char16_t codepoints that advances
 // by Unicode grapheme clusters
 class ClusterIterator
 {
 public:
     ClusterIterator(const char16_t* aText, uint32_t aLength)
         : mPos(aText), mLimit(aText + aLength)
 #ifdef DEBUG
--- a/js/public/CharacterEncoding.h
+++ b/js/public/CharacterEncoding.h
@@ -27,16 +27,19 @@ namespace JS {
  */
 class Latin1Chars : public mozilla::Range<Latin1Char>
 {
     typedef mozilla::Range<Latin1Char> Base;
 
   public:
     Latin1Chars() : Base() {}
     Latin1Chars(char *aBytes, size_t aLength) : Base(reinterpret_cast<Latin1Char *>(aBytes), aLength) {}
+    Latin1Chars(const Latin1Char *aBytes, size_t aLength)
+      : Base(const_cast<Latin1Char *>(aBytes), aLength)
+    {}
     Latin1Chars(const char *aBytes, size_t aLength)
       : Base(reinterpret_cast<Latin1Char *>(const_cast<char *>(aBytes)), aLength)
     {}
 };
 
 /*
  * A Latin1Chars, but with \0 termination for C compatibility.
  */
--- a/js/public/OldDebugAPI.h
+++ b/js/public/OldDebugAPI.h
@@ -286,22 +286,16 @@ extern JS_PUBLIC_API(void)
 JS_ReleaseFunctionLocalNameArray(JSContext *cx, void *mark);
 
 extern JS_PUBLIC_API(JSScript *)
 JS_GetFunctionScript(JSContext *cx, JS::HandleFunction fun);
 
 extern JS_PUBLIC_API(JSNative)
 JS_GetFunctionNative(JSContext *cx, JSFunction *fun);
 
-extern JS_PUBLIC_API(JSPrincipals *)
-JS_GetScriptPrincipals(JSScript *script);
-
-extern JS_PUBLIC_API(JSPrincipals *)
-JS_GetScriptOriginPrincipals(JSScript *script);
-
 JS_PUBLIC_API(JSFunction *)
 JS_GetScriptFunction(JSContext *cx, JSScript *script);
 
 extern JS_PUBLIC_API(JSObject *)
 JS_GetParentOrScopeChain(JSContext *cx, JSObject *obj);
 
 /************************************************************************/
 
--- a/js/src/assembler/jit/ExecutableAllocator.h
+++ b/js/src/assembler/jit/ExecutableAllocator.h
@@ -116,18 +116,17 @@ private:
     size_t m_baselineCodeBytes;
     size_t m_regexpCodeBytes;
     size_t m_otherCodeBytes;
 
 public:
     void release(bool willDestroy = false)
     {
         JS_ASSERT(m_refCount != 0);
-        // XXX: disabled, see bug 654820.
-        //JS_ASSERT_IF(willDestroy, m_refCount == 1);
+        JS_ASSERT_IF(willDestroy, m_refCount == 1);
         if (--m_refCount == 0)
             js_delete(this);
     }
     void release(size_t n, CodeKind kind)
     {
         switch (kind) {
           case ION_CODE:
             m_ionCodeBytes -= n;
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -46,17 +46,17 @@ static bool GetX4Lane(JSContext *cx, uns
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              X4TypeDescr::class_.name, laneNames[lane],
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
     TypedObject &typedObj = args.thisv().toObject().as<TypedObject>();
     TypeDescr &descr = typedObj.typeDescr();
-    if (descr.kind() != TypeDescr::X4 || descr.as<X4TypeDescr>().type() != Type32x4::type) {
+    if (descr.kind() != type::X4 || descr.as<X4TypeDescr>().type() != Type32x4::type) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              X4TypeDescr::class_.name, laneNames[lane],
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
     MOZ_ASSERT(!typedObj.owner().isNeutered());
     Elem *data = reinterpret_cast<Elem *>(typedObj.typedMem());
@@ -89,17 +89,17 @@ static bool SignMask(JSContext *cx, unsi
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              X4TypeDescr::class_.name, "signMask",
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
     TypedObject &typedObj = args.thisv().toObject().as<TypedObject>();
     TypeDescr &descr = typedObj.typeDescr();
-    if (descr.kind() != TypeDescr::X4 || descr.as<X4TypeDescr>().type() != Type32x4::type) {
+    if (descr.kind() != type::X4 || descr.as<X4TypeDescr>().type() != Type32x4::type) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              X4TypeDescr::class_.name, "signMask",
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
     MOZ_ASSERT(!typedObj.owner().isNeutered());
     Elem *data = reinterpret_cast<Elem *>(typedObj.typedMem());
@@ -211,17 +211,17 @@ CreateX4Class(JSContext *cx,
 
     // Create type constructor itself and initialize its reserved slots.
 
     Rooted<X4TypeDescr*> x4(cx);
     x4 = NewObjectWithProto<X4TypeDescr>(cx, funcProto, global, TenuredObject);
     if (!x4)
         return nullptr;
 
-    x4->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(TypeDescr::X4));
+    x4->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::X4));
     x4->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
     x4->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(X4TypeDescr::size(type)));
     x4->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(X4TypeDescr::alignment(type)));
     x4->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(false));
     x4->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(T::type));
 
     if (!CreateUserSizeAndAlignmentProperties(cx, x4))
         return nullptr;
@@ -394,17 +394,17 @@ IsVectorObject(HandleValue v)
     if (!v.isObject())
         return false;
 
     JSObject &obj = v.toObject();
     if (!obj.is<TypedObject>())
         return false;
 
     TypeDescr &typeRepr = obj.as<TypedObject>().typeDescr();
-    if (typeRepr.kind() != TypeDescr::X4)
+    if (typeRepr.kind() != type::X4)
         return false;
 
     return typeRepr.as<X4TypeDescr>().type() == V::type;
 }
 
 template<typename Elem>
 static Elem
 TypedObjectMemory(HandleValue v)
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -705,35 +705,35 @@ ArrayMetaTypeDescr::construct(JSContext 
 
     // Construct a canonical string `new ArrayType(<elementType>)`:
     StringBuffer contents(cx);
     contents.append("new ArrayType(");
     contents.append(&elementType->stringRepr());
     contents.append(")");
     RootedAtom stringRepr(cx, contents.finishAtom());
     if (!stringRepr)
-        return nullptr;
+        return false;
 
     // Extract ArrayType.prototype
     RootedObject arrayTypePrototype(cx, GetPrototype(cx, arrayTypeGlobal));
     if (!arrayTypePrototype)
-        return nullptr;
+        return false;
 
     // Create the instance of ArrayType
     Rooted<UnsizedArrayTypeDescr *> obj(cx);
     obj = create<UnsizedArrayTypeDescr>(cx, arrayTypePrototype, elementType,
                                         stringRepr, 0);
     if (!obj)
         return false;
 
     // Add `length` property, which is undefined for an unsized array.
     if (!JSObject::defineProperty(cx, obj, cx->names().length,
                                   UndefinedHandleValue, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
-        return nullptr;
+        return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 /*static*/ bool
 UnsizedArrayTypeDescr::dimension(JSContext *cx, unsigned int argc, jsval *vp)
 {
@@ -757,30 +757,30 @@ UnsizedArrayTypeDescr::dimension(JSConte
     int32_t length = args[0].toInt32();
     Rooted<SizedTypeDescr*> elementType(cx, &unsizedTypeDescr->elementType());
 
     // Compute the size.
     CheckedInt32 size = CheckedInt32(elementType->size()) * length;
     if (!size.isValid()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                              JSMSG_TYPEDOBJECT_TOO_BIG);
-        return nullptr;
+        return false;
     }
 
     // Construct a canonical string `new ArrayType(<elementType>).dimension(N)`:
     StringBuffer contents(cx);
     contents.append("new ArrayType(");
     contents.append(&elementType->stringRepr());
     contents.append(").dimension(");
     if (!NumberValueToStringBuffer(cx, NumberValue(length), contents))
         return false;
     contents.append(")");
     RootedAtom stringRepr(cx, contents.finishAtom());
     if (!stringRepr)
-        return nullptr;
+        return false;
 
     // Create the sized type object.
     Rooted<SizedArrayTypeDescr*> obj(cx);
     obj = ArrayMetaTypeDescr::create<SizedArrayTypeDescr>(cx, unsizedTypeDescr,
                                                           elementType,
                                                           stringRepr, size.value());
     if (!obj)
         return false;
@@ -788,25 +788,25 @@ UnsizedArrayTypeDescr::dimension(JSConte
     obj->initReservedSlot(JS_DESCR_SLOT_SIZED_ARRAY_LENGTH,
                           Int32Value(length));
 
     // Add `length` property.
     RootedValue lengthVal(cx, Int32Value(length));
     if (!JSObject::defineProperty(cx, obj, cx->names().length,
                                   lengthVal, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
-        return nullptr;
+        return false;
 
     // Add `unsized` property, which is a link from the sized
     // array to the unsized array.
     RootedValue unsizedTypeDescrValue(cx, ObjectValue(*unsizedTypeDescr));
     if (!JSObject::defineProperty(cx, obj, cx->names().unsized,
                                   unsizedTypeDescrValue, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
-        return nullptr;
+        return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
 js::IsTypedObjectArray(JSObject &obj)
 {
@@ -817,18 +817,17 @@ js::IsTypedObjectArray(JSObject &obj)
 }
 
 /*********************************
  * StructType class
  */
 
 const Class StructTypeDescr::class_ = {
     "StructType",
-    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) |
-    JSCLASS_HAS_PRIVATE, // used to store FieldList
+    JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS),
     JS_PropertyStub,
     JS_DeletePropertyStub,
     JS_PropertyStub,
     JS_StrictPropertyStub,
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr, /* finalize */
@@ -1011,17 +1010,17 @@ StructMetaTypeDescr::create(JSContext *c
         return nullptr;
 
     Rooted<StructTypeDescr*> descr(cx);
     descr = NewObjectWithProto<StructTypeDescr>(cx, structTypePrototype, nullptr,
                                                 TenuredObject);
     if (!descr)
         return nullptr;
 
-    descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(TypeDescr::Struct));
+    descr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Struct));
     descr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
     descr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(alignment));
     descr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(totalSize.value()));
     descr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(opaque));
 
     // Construct for internal use an array with the name for each field.
     {
         RootedObject fieldNamesVec(cx);
@@ -1116,54 +1115,54 @@ StructMetaTypeDescr::construct(JSContext
     }
 
     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                          JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS);
     return false;
 }
 
 size_t
-StructTypeDescr::fieldCount()
+StructTypeDescr::fieldCount() const
 {
     return getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).toObject().getDenseInitializedLength();
 }
 
 bool
-StructTypeDescr::fieldIndex(jsid id, size_t *out)
+StructTypeDescr::fieldIndex(jsid id, size_t *out) const
 {
     JSObject &fieldNames = getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).toObject();
     size_t l = fieldNames.getDenseInitializedLength();
     for (size_t i = 0; i < l; i++) {
         JSAtom &a = fieldNames.getDenseElement(i).toString()->asAtom();
         if (JSID_IS_ATOM(id, &a)) {
             *out = i;
             return true;
         }
     }
     return false;
 }
 
 JSAtom &
-StructTypeDescr::fieldName(size_t index)
+StructTypeDescr::fieldName(size_t index) const
 {
     JSObject &fieldNames = getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_NAMES).toObject();
     return fieldNames.getDenseElement(index).toString()->asAtom();
 }
 
 int32_t
-StructTypeDescr::fieldOffset(size_t index)
+StructTypeDescr::fieldOffset(size_t index) const
 {
     JSObject &fieldOffsets =
         getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_OFFSETS).toObject();
     JS_ASSERT(index < fieldOffsets.getDenseInitializedLength());
     return fieldOffsets.getDenseElement(index).toInt32();
 }
 
 SizedTypeDescr&
-StructTypeDescr::fieldDescr(size_t index)
+StructTypeDescr::fieldDescr(size_t index) const
 {
     JSObject &fieldDescrs =
         getReservedSlot(JS_DESCR_SLOT_STRUCT_FIELD_TYPES).toObject();
     JS_ASSERT(index < fieldDescrs.getDenseInitializedLength());
     return fieldDescrs.getDenseElement(index).toObject().as<SizedTypeDescr>();
 }
 
 /******************************************************************************
@@ -1248,17 +1247,17 @@ DefineSimpleTypeDescr(JSContext *cx,
     if (!JS_DefineFunctions(cx, descr, T::typeObjectMethods))
         return false;
 
     // Create the typed prototype for the scalar type. This winds up
     // not being user accessible, but we still create one for consistency.
     Rooted<TypedProto*> proto(cx);
     proto = NewObjectWithProto<TypedProto>(cx, objProto, nullptr, TenuredObject);
     if (!proto)
-        return nullptr;
+        return false;
     proto->initTypeDescrSlot(*descr);
     descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto));
 
     RootedValue descrValue(cx, ObjectValue(*descr));
     if (!JSObject::defineProperty(cx, module, className,
                                   descrValue, nullptr, nullptr, 0))
     {
         return false;
@@ -1353,66 +1352,66 @@ GlobalObject::initTypedObjectModule(JSCo
     if (!JS_DefineFunctions(cx, module, TypedObjectMethods))
         return false;
 
     // uint8, uint16, any, etc
 
 #define BINARYDATA_SCALAR_DEFINE(constant_, type_, name_)                       \
     if (!DefineSimpleTypeDescr<ScalarTypeDescr>(cx, global, module, constant_,      \
                                             cx->names().name_))                 \
-        return nullptr;
+        return false;
     JS_FOR_EACH_SCALAR_TYPE_REPR(BINARYDATA_SCALAR_DEFINE)
 #undef BINARYDATA_SCALAR_DEFINE
 
 #define BINARYDATA_REFERENCE_DEFINE(constant_, type_, name_)                    \
     if (!DefineSimpleTypeDescr<ReferenceTypeDescr>(cx, global, module, constant_,   \
                                                cx->names().name_))              \
-        return nullptr;
+        return false;
     JS_FOR_EACH_REFERENCE_TYPE_REPR(BINARYDATA_REFERENCE_DEFINE)
 #undef BINARYDATA_REFERENCE_DEFINE
 
     // ArrayType.
 
     RootedObject arrayType(cx);
     arrayType = DefineMetaTypeDescr<ArrayMetaTypeDescr>(
         cx, global, module, TypedObjectModuleObject::ArrayTypePrototype);
     if (!arrayType)
-        return nullptr;
+        return false;
 
     RootedValue arrayTypeValue(cx, ObjectValue(*arrayType));
     if (!JSObject::defineProperty(cx, module, cx->names().ArrayType,
                                   arrayTypeValue,
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
-        return nullptr;
+        return false;
 
     // StructType.
 
     RootedObject structType(cx);
     structType = DefineMetaTypeDescr<StructMetaTypeDescr>(
         cx, global, module, TypedObjectModuleObject::StructTypePrototype);
     if (!structType)
-        return nullptr;
+        return false;
 
     RootedValue structTypeValue(cx, ObjectValue(*structType));
     if (!JSObject::defineProperty(cx, module, cx->names().StructType,
                                   structTypeValue,
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
-        return nullptr;
+        return false;
 
     // Everything is setup, install module on the global object:
     RootedValue moduleValue(cx, ObjectValue(*module));
     global->setConstructor(JSProto_TypedObject, moduleValue);
     if (!JSObject::defineProperty(cx, global, cx->names().TypedObject,
                                   moduleValue,
                                   nullptr, nullptr,
                                   0))
     {
-        return nullptr;
+        return false;
     }
 
     return module;
 }
 
 JSObject *
 js_InitTypedObjectModuleObject(JSContext *cx, HandleObject obj)
 {
@@ -1478,27 +1477,16 @@ TypedObject::createUnattachedWithClass(J
     if (!obj)
         return nullptr;
 
     obj->initPrivate(nullptr);
     obj->initReservedSlot(JS_BUFVIEW_SLOT_BYTEOFFSET, Int32Value(0));
     obj->initReservedSlot(JS_BUFVIEW_SLOT_LENGTH, Int32Value(length));
     obj->initReservedSlot(JS_BUFVIEW_SLOT_OWNER, NullValue());
     obj->initReservedSlot(JS_BUFVIEW_SLOT_NEXT_VIEW, PrivateValue(nullptr));
-    obj->initReservedSlot(JS_TYPEDOBJ_SLOT_TYPE_DESCR, ObjectValue(*type));
-
-    // Tag the type object for this instance with the type
-    // representation, if that has not been done already.
-    if (!type->is<SimpleTypeDescr>()) { // FIXME Bug 929651
-        RootedTypeObject typeObj(cx, obj->getType(cx));
-        if (typeObj) {
-            if (!typeObj->addTypedObjectAddendum(cx, type))
-                return nullptr;
-        }
-    }
 
     return static_cast<TypedObject*>(&*obj);
 }
 
 void
 TypedObject::attach(ArrayBufferObject &buffer, int32_t offset)
 {
     JS_ASSERT(offset >= 0);
@@ -1520,26 +1508,26 @@ TypedObject::attach(TypedObject &typedOb
 }
 
 // Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of
 // the type `type`. `type` must not be an unsized array.
 static int32_t
 TypedObjLengthFromType(TypeDescr &descr)
 {
     switch (descr.kind()) {
-      case TypeDescr::Scalar:
-      case TypeDescr::Reference:
-      case TypeDescr::Struct:
-      case TypeDescr::X4:
+      case type::Scalar:
+      case type::Reference:
+      case type::Struct:
+      case type::X4:
         return 0;
 
-      case TypeDescr::SizedArray:
+      case type::SizedArray:
         return descr.as<SizedArrayTypeDescr>().length();
 
-      case TypeDescr::UnsizedArray:
+      case type::UnsizedArray:
         MOZ_ASSUME_UNREACHABLE("TypedObjLengthFromType() invoked on unsized type");
     }
     MOZ_ASSUME_UNREACHABLE("Invalid kind");
 }
 
 /*static*/ TypedObject *
 TypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type,
                            HandleTypedObject typedObj, int32_t offset)
@@ -1569,33 +1557,33 @@ TypedObject::createZeroed(JSContext *cx,
     // Create unattached wrapper object.
     Rooted<TypedObject*> obj(cx, createUnattached(cx, descr, length));
     if (!obj)
         return nullptr;
 
     // Allocate and initialize the memory for this instance.
     // Also initialize the JS_TYPEDOBJ_SLOT_LENGTH slot.
     switch (descr->kind()) {
-      case TypeDescr::Scalar:
-      case TypeDescr::Reference:
-      case TypeDescr::Struct:
-      case TypeDescr::X4:
-      case TypeDescr::SizedArray:
+      case type::Scalar:
+      case type::Reference:
+      case type::Struct:
+      case type::X4:
+      case type::SizedArray:
       {
         size_t totalSize = descr->as<SizedTypeDescr>().size();
         Rooted<ArrayBufferObject*> buffer(cx);
         buffer = ArrayBufferObject::create(cx, totalSize);
         if (!buffer)
             return nullptr;
         descr->as<SizedTypeDescr>().initInstances(cx->runtime(), buffer->dataPointer(), 1);
         obj->attach(*buffer, 0);
         return obj;
       }
 
-      case TypeDescr::UnsizedArray:
+      case type::UnsizedArray:
       {
         Rooted<SizedTypeDescr*> elementTypeRepr(cx);
         elementTypeRepr = &descr->as<UnsizedArrayTypeDescr>().elementType();
 
         CheckedInt32 totalSize = CheckedInt32(elementTypeRepr->size()) * length;
         if (!totalSize.isValid()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                  JSMSG_TYPEDOBJECT_TOO_BIG);
@@ -1632,77 +1620,74 @@ ReportTypedObjTypeError(JSContext *cx,
 
     JS_free(cx, (void *) typeReprStr);
     return false;
 }
 
 /*static*/ void
 TypedObject::obj_trace(JSTracer *trace, JSObject *object)
 {
-    gc::MarkSlot(trace, &object->getReservedSlotRef(JS_TYPEDOBJ_SLOT_TYPE_DESCR),
-                 "TypedObjectTypeDescr");
-
     ArrayBufferViewObject::trace(trace, object);
 
     JS_ASSERT(object->is<TypedObject>());
     TypedObject &typedObj = object->as<TypedObject>();
     TypeDescr &descr = typedObj.typeDescr();
     if (descr.opaque()) {
         uint8_t *mem = typedObj.typedMem();
         if (!mem)
             return; // partially constructed
 
         if (typedObj.owner().isNeutered())
             return;
 
         switch (descr.kind()) {
-          case TypeDescr::Scalar:
-          case TypeDescr::Reference:
-          case TypeDescr::Struct:
-          case TypeDescr::SizedArray:
-          case TypeDescr::X4:
+          case type::Scalar:
+          case type::Reference:
+          case type::Struct:
+          case type::SizedArray:
+          case type::X4:
             descr.as<SizedTypeDescr>().traceInstances(trace, mem, 1);
             break;
 
-          case TypeDescr::UnsizedArray:
+          case type::UnsizedArray:
             descr.as<UnsizedArrayTypeDescr>().elementType().traceInstances(trace, mem, typedObj.length());
             break;
         }
     }
 }
 
 bool
 TypedObject::obj_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id,
                               MutableHandleObject objp, MutableHandleShape propp)
 {
     JS_ASSERT(obj->is<TypedObject>());
 
     Rooted<TypeDescr*> descr(cx, &obj->as<TypedObject>().typeDescr());
     switch (descr->kind()) {
-      case TypeDescr::Scalar:
-      case TypeDescr::Reference:
-      case TypeDescr::X4:
+      case type::Scalar:
+      case type::Reference:
+      case type::X4:
         break;
 
-      case TypeDescr::SizedArray:
-      case TypeDescr::UnsizedArray:
+      case type::SizedArray:
+      case type::UnsizedArray:
       {
         uint32_t index;
         if (js_IdIsIndex(id, &index))
             return obj_lookupElement(cx, obj, index, objp, propp);
 
         if (JSID_IS_ATOM(id, cx->names().length)) {
             MarkNonNativePropertyFound(propp);
             objp.set(obj);
             return true;
         }
         break;
       }
 
-      case TypeDescr::Struct:
+      case type::Struct:
       {
         StructTypeDescr &structDescr = descr->as<StructTypeDescr>();
         size_t index;
         if (structDescr.fieldIndex(id, &index)) {
             MarkNonNativePropertyFound(propp);
             objp.set(obj);
             return true;
         }
@@ -1798,39 +1783,39 @@ TypedObject::obj_getGeneric(JSContext *c
     // Dispatch elements to obj_getElement:
     uint32_t index;
     if (js_IdIsIndex(id, &index))
         return obj_getElement(cx, obj, receiver, index, vp);
 
     // Handle everything else here:
 
     switch (typedObj->typeDescr().kind()) {
-      case TypeDescr::Scalar:
-      case TypeDescr::Reference:
+      case type::Scalar:
+      case type::Reference:
         break;
 
-      case TypeDescr::X4:
+      case type::X4:
         break;
 
-      case TypeDescr::SizedArray:
-      case TypeDescr::UnsizedArray:
+      case type::SizedArray:
+      case type::UnsizedArray:
         if (JSID_IS_ATOM(id, cx->names().length)) {
             if (typedObj->owner().isNeutered() || !typedObj->typedMem()) { // unattached
                 JS_ReportErrorNumber(
                     cx, js_GetErrorMessage,
                     nullptr, JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
                 return false;
             }
 
             vp.setInt32(typedObj->length());
             return true;
         }
         break;
 
-      case TypeDescr::Struct: {
+      case type::Struct: {
         Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());
 
         size_t fieldIndex;
         if (!descr->fieldIndex(id, &fieldIndex))
             break;
 
         size_t offset = descr->fieldOffset(fieldIndex);
         Rooted<SizedTypeDescr*> fieldType(cx, &descr->fieldDescr(fieldIndex));
@@ -1859,27 +1844,27 @@ bool
 TypedObject::obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver,
                              uint32_t index, MutableHandleValue vp)
 {
     JS_ASSERT(obj->is<TypedObject>());
     Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
     Rooted<TypeDescr *> descr(cx, &typedObj->typeDescr());
 
     switch (descr->kind()) {
-      case TypeDescr::Scalar:
-      case TypeDescr::Reference:
-      case TypeDescr::X4:
-      case TypeDescr::Struct:
+      case type::Scalar:
+      case type::Reference:
+      case type::X4:
+      case type::Struct:
         break;
 
-      case TypeDescr::SizedArray:
+      case type::SizedArray:
         return obj_getArrayElement<SizedArrayTypeDescr>(cx, typedObj, descr,
                                                         index, vp);
 
-      case TypeDescr::UnsizedArray:
+      case type::UnsizedArray:
         return obj_getArrayElement<UnsizedArrayTypeDescr>(cx, typedObj, descr,
                                                           index, vp);
     }
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         vp.setUndefined();
         return true;
@@ -1915,33 +1900,33 @@ TypedObject::obj_setGeneric(JSContext *c
     JS_ASSERT(obj->is<TypedObject>());
     Rooted<TypedObject *> typedObj(cx, &obj->as<TypedObject>());
 
     uint32_t index;
     if (js_IdIsIndex(id, &index))
         return obj_setElement(cx, obj, index, vp, strict);
 
     switch (typedObj->typeDescr().kind()) {
-      case ScalarTypeDescr::Scalar:
-      case TypeDescr::Reference:
+      case type::Scalar:
+      case type::Reference:
         break;
 
-      case ScalarTypeDescr::X4:
+      case type::X4:
         break;
 
-      case ScalarTypeDescr::SizedArray:
-      case ScalarTypeDescr::UnsizedArray:
+      case type::SizedArray:
+      case type::UnsizedArray:
         if (JSID_IS_ATOM(id, cx->names().length)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage,
                                  nullptr, JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
             return false;
         }
         break;
 
-      case ScalarTypeDescr::Struct: {
+      case type::Struct: {
         Rooted<StructTypeDescr*> descr(cx, &typedObj->typeDescr().as<StructTypeDescr>());
 
         size_t fieldIndex;
         if (!descr->fieldIndex(id, &fieldIndex))
             break;
 
         size_t offset = descr->fieldOffset(fieldIndex);