Merge m-c to fx-team. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 07 Oct 2016 09:46:31 -0400
changeset 422564 6ec07eafa41f4eb5d8a3fc9ed8a165b46603b216
parent 422219 0e67f06bcfcc83f4317a968914232bb6c82965ac (current diff)
parent 422188 ea8624a9b11e89b831b830cef70c47ae67ccead1 (diff)
child 422565 fec388445e7cfed221984024c18067f44b29fd3c
push id31744
push usermwein@mozilla.com
push dateFri, 07 Oct 2016 21:14:49 +0000
reviewersmerge
milestone52.0a1
Merge m-c to fx-team. a=merge
testing/web-platform/meta/encrypted-media/Google/encrypted-media-keystatuses.html.ini
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -149,16 +149,22 @@
     <!-- for url bar autocomplete -->
     <panel type="autocomplete-richlistbox"
            id="PopupAutoCompleteRichResult"
            noautofocus="true"
            hidden="true"
            flip="none"
            level="parent"/>
 
+    <panel id="DateTimePickerPanel"
+           hidden="true"
+           noautofocus="true"
+           consumeoutsideclicks="false"
+           level="parent"/>
+
     <!-- for select dropdowns. The menupopup is what shows the list of options,
          and the popuponly menulist makes things like the menuactive attributes
          work correctly on the menupopup. ContentSelectDropdown expects the
          popuponly menulist to be its immediate parent. -->
     <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
       <menupopup rolluponmousewheel="true"
                  activateontab="true" position="after_start"
 #ifdef XP_WIN
@@ -1051,17 +1057,18 @@
       <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
       <vbox id="appcontent" flex="1">
         <notificationbox id="high-priority-global-notificationbox" notificationside="top"/>
         <tabbrowser id="content"
                     flex="1" contenttooltip="aHTMLTooltip"
                     tabcontainer="tabbrowser-tabs"
                     contentcontextmenu="contentAreaContextMenu"
                     autocompletepopup="PopupAutoComplete"
-                    selectmenulist="ContentSelectDropdown"/>
+                    selectmenulist="ContentSelectDropdown"
+                    datetimepicker="DateTimePickerPanel"/>
       </vbox>
       <vbox id="browser-border-end" hidden="true" layer="true"/>
     </hbox>
 #include ../../components/customizableui/content/customizeMode.inc.xul
   </deck>
 
   <html:div id="fullscreen-warning" class="pointerlockfswarning" hidden="true">
     <html:div class="pointerlockfswarning-domain-text">
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -925,12 +925,18 @@ var UserContextIdNotifier = {
 UserContextIdNotifier.init();
 
 ExtensionContent.init(this);
 addEventListener("unload", () => {
   ExtensionContent.uninit(this);
   RefreshBlocker.uninit();
 });
 
+addMessageListener("AllowScriptsToClose", () => {
+  content.QueryInterface(Ci.nsIInterfaceRequestor)
+         .getInterface(Ci.nsIDOMWindowUtils)
+         .allowScriptsToClose();
+});
+
 addEventListener("MozAfterPaint", function onFirstPaint() {
   removeEventListener("MozAfterPaint", onFirstPaint);
   sendAsyncMessage("Browser:FirstPaint");
 });
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -20,17 +20,17 @@
                   flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
                   onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
         <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
           <xul:notificationbox flex="1" notificationside="top">
             <xul:hbox flex="1" class="browserSidebarContainer">
               <xul:vbox flex="1" class="browserContainer">
                 <xul:stack flex="1" class="browserStack" anonid="browserStack">
                   <xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
-                               xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist"/>
+                               xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist,datetimepicker"/>
                 </xul:stack>
               </xul:vbox>
             </xul:hbox>
           </xul:notificationbox>
         </xul:tabpanels>
       </xul:tabbox>
       <children/>
     </content>
@@ -1881,16 +1881,20 @@
 
             if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
             }
 
             if (this.hasAttribute("selectmenulist"))
               b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
 
+            if (this.hasAttribute("datetimepicker")) {
+              b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
+            }
+
             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
 
             if (aParams.relatedBrowser) {
               b.relatedBrowser = aParams.relatedBrowser;
             }
 
             // Create the browserStack container
             var stack = document.createElementNS(NS_XUL, "stack");
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -8,18 +8,23 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
+  promiseObserved,
 } = ExtensionUtils;
 
+function onXULFrameLoaderCreated({target}) {
+  target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
+}
+
 extensions.registerSchemaAPI("windows", "addon_parent", context => {
   let {extension} = context;
   return {
     windows: {
       onCreated:
       new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
         fire(WindowManager.convert(extension, window));
       }).api(),
@@ -89,16 +94,20 @@ extensions.registerSchemaAPI("windows", 
 
         let args = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
 
         if (createData.tabId !== null) {
           if (createData.url !== null) {
             return Promise.reject({message: "`tabId` may not be used in conjunction with `url`"});
           }
 
+          if (createData.allowScriptsToClose) {
+            return Promise.reject({message: "`tabId` may not be used in conjunction with `allowScriptsToClose`"});
+          }
+
           let tab = TabManager.getTab(createData.tabId, context);
 
           // Private browsing tabs can only be moved to private browsing
           // windows.
           let incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
           if (createData.incognito !== null && createData.incognito != incognito) {
             return Promise.reject({message: "`incognito` property must match the incognito state of tab"});
           }
@@ -131,48 +140,52 @@ extensions.registerSchemaAPI("windows", 
         if (createData.incognito !== null) {
           if (createData.incognito) {
             features.push("private");
           } else {
             features.push("non-private");
           }
         }
 
+        let {allowScriptsToClose, url} = createData;
+        if (allowScriptsToClose === null) {
+          allowScriptsToClose = typeof url === "string" && url.startsWith("moz-extension://");
+        }
+
         let window = Services.ww.openWindow(null, "chrome://browser/content/browser.xul", "_blank",
                                             features.join(","), args);
 
         WindowManager.updateGeometry(window, createData);
 
         // TODO: focused, type
 
         return new Promise(resolve => {
           window.addEventListener("load", function listener() {
             window.removeEventListener("load", listener);
 
             if (createData.state == "maximized" || createData.state == "normal" ||
                 (createData.state == "fullscreen" && AppConstants.platform != "macosx")) {
               window.document.documentElement.setAttribute("sizemode", createData.state);
             } else if (createData.state !== null) {
-              // window.minimize() has no useful effect until the window has
-              // been shown.
-
-              let obs = doc => {
-                if (doc === window.document) {
-                  Services.obs.removeObserver(obs, "document-shown");
-                  WindowManager.setState(window, createData.state);
-                  resolve();
-                }
-              };
-              Services.obs.addObserver(obs, "document-shown", false);
-              return;
+              // window.minimize() has no effect until the window has been shown.
+              return promiseObserved("document-shown", doc => doc == window.document).then(() => {
+                WindowManager.setState(window, createData.state);
+                resolve();
+              });
             }
-
             resolve();
           });
         }).then(() => {
+          if (allowScriptsToClose) {
+            for (let {linkedBrowser} of window.gBrowser.tabs) {
+              onXULFrameLoaderCreated({target: linkedBrowser});
+              linkedBrowser.addEventListener( // eslint-disable-line mozilla/balanced-listeners
+                                             "XULFrameLoaderCreated", onXULFrameLoaderCreated);
+            }
+          }
           return WindowManager.convert(extension, window);
         });
       },
 
       update: function(windowId, updateInfo) {
         if (updateInfo.state !== null && updateInfo.state != "normal") {
           if (updateInfo.left !== null || updateInfo.top !== null ||
               updateInfo.width !== null || updateInfo.height !== null) {
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -323,16 +323,21 @@
                 "$ref": "CreateType",
                 "optional": true,
                 "description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set."
               },
               "state": {
                 "$ref": "WindowState",
                 "optional": true,
                 "description": "The initial state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
+              },
+              "allowScriptsToClose": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Allow scripts to close the window."
               }
             },
             "optional": true
           },
           {
             "type": "function",
             "name": "callback",
             "optional": true,
--- a/browser/components/extensions/test/browser/browser_ext_windows_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create.js
@@ -160,8 +160,65 @@ add_task(function* testWindowCreateParam
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("window-create-params");
   yield extension.unload();
 });
 
+// Tests allowScriptsToClose option
+add_task(function* test_allowScriptsToClose() {
+  const files = {
+    "dummy.html": "<meta charset=utf-8><script src=close.js></script>",
+    "close.js": function() {
+      window.close();
+      if (!window.closed) {
+        browser.test.sendMessage("close-failed");
+      }
+    },
+  };
+
+  function background() {
+    browser.test.onMessage.addListener((msg, options) => {
+      function listener(_, {status}, {url}) {
+        if (status == "complete" && url == options.url) {
+          browser.tabs.onUpdated.removeListener(listener);
+          browser.tabs.executeScript({file: "close.js"});
+        }
+      }
+      options.url = browser.runtime.getURL(options.url);
+      browser.windows.create(options);
+      if (msg === "create+execute") {
+        browser.tabs.onUpdated.addListener(listener);
+      }
+    });
+    browser.test.notifyPass();
+  }
+
+  const example = "http://example.com/";
+  const manifest = {permissions: ["tabs", example]};
+
+  const extension = ExtensionTestUtils.loadExtension({files, background, manifest});
+  yield SpecialPowers.pushPrefEnv({set: [["dom.allow_scripts_to_close_windows", false]]});
+
+  yield extension.startup();
+  yield extension.awaitFinish();
+
+  extension.sendMessage("create", {url: "dummy.html"});
+  let win = yield BrowserTestUtils.waitForNewWindow();
+  yield BrowserTestUtils.windowClosed(win);
+  info("script allowed to close the window");
+
+  extension.sendMessage("create+execute", {url: example});
+  win = yield BrowserTestUtils.waitForNewWindow();
+  yield extension.awaitMessage("close-failed");
+  info("script prevented from closing the window");
+  win.close();
+
+  extension.sendMessage("create+execute", {url: example, allowScriptsToClose: true});
+  win = yield BrowserTestUtils.waitForNewWindow();
+  yield BrowserTestUtils.windowClosed(win);
+  info("script allowed to close the window");
+
+  yield SpecialPowers.popPrefEnv();
+  yield extension.unload();
+});
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -28,16 +28,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
   ["BookmarkHTMLUtils", "resource://gre/modules/BookmarkHTMLUtils.jsm"],
   ["BookmarkJSONUtils", "resource://gre/modules/BookmarkJSONUtils.jsm"],
   ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
   ["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
   ["CaptivePortalWatcher", "resource:///modules/CaptivePortalWatcher.jsm"],
   ["ContentClick", "resource:///modules/ContentClick.jsm"],
   ["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
   ["ContentSearch", "resource:///modules/ContentSearch.jsm"],
+  ["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
   ["DirectoryLinksProvider", "resource:///modules/DirectoryLinksProvider.jsm"],
   ["Feeds", "resource:///modules/Feeds.jsm"],
   ["FileUtils", "resource://gre/modules/FileUtils.jsm"],
   ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
   ["Integration", "resource://gre/modules/Integration.jsm"],
   ["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
   ["LoginHelper", "resource://gre/modules/LoginHelper.jsm"],
   ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
@@ -1017,16 +1018,17 @@ BrowserGlue.prototype = {
       }
     }
 
     this._checkForOldBuildUpdates();
 
     CaptivePortalWatcher.init();
 
     AutoCompletePopup.init();
+    DateTimePickerHelper.init();
 
     this._firstWindowTelemetry(aWindow);
     this._firstWindowLoaded();
   },
 
   /**
    * Application shutdown handler.
    */
@@ -1048,16 +1050,17 @@ BrowserGlue.prototype = {
     BrowserUsageTelemetry.uninit();
     SelfSupportBackend.uninit();
     NewTabMessages.uninit();
     CaptivePortalWatcher.uninit();
     AboutNewTab.uninit();
     webrtcUI.uninit();
     FormValidationHandler.uninit();
     AutoCompletePopup.uninit();
+    DateTimePickerHelper.uninit();
     if (AppConstants.NIGHTLY_BUILD) {
       AddonWatcher.uninit();
     }
   },
 
   _initServiceDiscovery: function () {
     if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
       return;
--- a/browser/components/originattributes/test/browser/browser.ini
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -24,8 +24,9 @@ support-files =
   worker_blobify.js
   worker_deblobify.js
 
 [browser_firstPartyIsolation.js]
 [browser_localStorageIsolation.js]
 [browser_blobURLIsolation.js]
 [browser_imageCacheIsolation.js]
 [browser_sharedworker.js]
+[browser_httpauth.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_httpauth.js
@@ -0,0 +1,54 @@
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+let server = new HttpServer();
+server.registerPathHandler('/file.html', fileHandler);
+server.start(-1);
+
+let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
+let FILE_URI = BASE_URI + '/file.html';
+
+let credentialQueue = [];
+
+// Ask the user agent for authorization.
+function fileHandler(metadata, response) {
+  if (!metadata.hasHeader("Authorization")) {
+    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+    response.setHeader("WWW-Authenticate", "Basic realm=\"User Visible Realm\"");
+    return;
+  }
+
+  // This will be "account:password" encoded in base64.
+  credentialQueue.push(metadata.getHeader("Authorization"));
+
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "text/html", false);
+  let body = "<html><body></body></html>";
+  response.bodyOutputStream.write(body, body.length);
+}
+
+function onCommonDialogLoaded(subject) {
+  // Submit random account and password
+  let dialog = subject.Dialog;
+  dialog.ui.loginTextbox.setAttribute("value", Math.random());
+  dialog.ui.password1Textbox.setAttribute("value", Math.random());
+  dialog.ui.button0.click();
+}
+
+Services.obs.addObserver(onCommonDialogLoaded, "common-dialog-loaded", false);
+
+registerCleanupFunction(() => {
+  Services.obs.removeObserver(onCommonDialogLoaded, "common-dialog-loaded");
+  server.stop(() => {
+    server = null;
+  });
+});
+
+function getResult() {
+  // If two targets are isolated, they should get different credentials.
+  // Otherwise, the credentials will be cached and therefore the same.
+  return credentialQueue.shift();
+}
+
+IsolationTestTools.runTests(FILE_URI, getResult);
+
--- a/dom/animation/test/chrome/test_running_on_compositor.html
+++ b/dom/animation/test/chrome/test_running_on_compositor.html
@@ -726,17 +726,17 @@ promise_test(function(t) {
   });
 }, 'Transitions override important rules');
 
 promise_test(function(t) {
   var div = addDiv(t, { style: 'transition: opacity 100s; ' +
                                'opacity: 0 !important' });
   getComputedStyle(div).opacity;
 
-  div.animate({ opacity: [ 0, 1 ] }, 10 * MS_PER_SEC);
+  div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
 
   div.style.setProperty('opacity', '1', 'important');
   getComputedStyle(div).opacity;
 
   var [transition, animation] = div.getAnimations();
 
   return Promise.all([transition.ready, animation.ready]).then(function() {
     assert_animation_is_not_running_on_compositor(transition,
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -267,16 +267,17 @@ GK_ATOM(current, "current")
 GK_ATOM(cutoutregion, "cutoutregion")
 GK_ATOM(cycler, "cycler")
 GK_ATOM(data, "data")
 GK_ATOM(datalist, "datalist")
 GK_ATOM(dataType, "data-type")
 GK_ATOM(dateTime, "date-time")
 GK_ATOM(datasources, "datasources")
 GK_ATOM(datetime, "datetime")
+GK_ATOM(datetimebox, "datetimebox")
 GK_ATOM(dblclick, "dblclick")
 GK_ATOM(dd, "dd")
 GK_ATOM(debug, "debug")
 GK_ATOM(decimalFormat, "decimal-format")
 GK_ATOM(decimalSeparator, "decimal-separator")
 GK_ATOM(deck, "deck")
 GK_ATOM(declare, "declare")
 GK_ATOM(decoderDoctor, "decoder-doctor")
@@ -1980,16 +1981,17 @@ GK_ATOM(bcTableCellFrame, "BCTableCellFr
 GK_ATOM(blockFrame, "BlockFrame")
 GK_ATOM(boxFrame, "BoxFrame")
 GK_ATOM(brFrame, "BRFrame")
 GK_ATOM(bulletFrame, "BulletFrame")
 GK_ATOM(colorControlFrame, "colorControlFrame")
 GK_ATOM(columnSetFrame, "ColumnSetFrame")
 GK_ATOM(comboboxControlFrame, "ComboboxControlFrame")
 GK_ATOM(comboboxDisplayFrame, "ComboboxDisplayFrame")
+GK_ATOM(dateTimeControlFrame, "DateTimeControlFrame")
 GK_ATOM(deckFrame, "DeckFrame")
 GK_ATOM(detailsFrame, "DetailsFrame")
 GK_ATOM(fieldSetFrame, "FieldSetFrame")
 GK_ATOM(flexContainerFrame, "FlexContainerFrame")
 GK_ATOM(formControlFrame, "FormControlFrame") // radio or checkbox
 GK_ATOM(frameSetFrame, "FrameSetFrame")
 GK_ATOM(gfxButtonControlFrame, "gfxButtonControlFrame")
 GK_ATOM(gridContainerFrame, "GridContainerFrame")
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/mozCurrentTransform-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<canvas id="canvas" width="150" height="150"></canvas>
+
+<script>
+
+var canvas = document.getElementById('canvas');
+var ctx = canvas.getContext('2d');
+ctx.transform(1,0.5,-0.5,1,30,10);
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 100);
+
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/mozCurrentTransform.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<canvas id="canvas" width="150" height="150"></canvas>
+
+<script>
+
+var canvas = document.getElementById('canvas');
+var ctx = canvas.getContext('2d');
+ctx.transform(1,0.5,-0.5,1,30,10);
+setTimeout(function() {
+  var canvas = document.getElementById('canvas');
+  var ctx = canvas.getContext('2d');
+  var transform = ctx.mozCurrentTransform;
+  ctx.mozCurrentTransform = transform;
+  ctx.fillStyle = '#f00';
+  ctx.fillRect(0, 0, 100, 100);
+  document.documentElement.removeAttribute("class");
+}, 10)
+
+</script>
+</body></html>
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/reftest/mozCurrentTransformInverse.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<canvas id="canvas" width="150" height="150"></canvas>
+
+<script>
+
+var canvas = document.getElementById('canvas');
+var ctx = canvas.getContext('2d');
+ctx.transform(1,0.5,-0.5,1,30,10);
+setTimeout(function() {
+  var canvas = document.getElementById('canvas');
+  var ctx = canvas.getContext('2d');
+  var transform = ctx.mozCurrentTransformInverse;
+  ctx.mozCurrentTransformInverse = transform;
+  ctx.fillStyle = '#f00';
+  ctx.fillRect(0, 0, 100, 100);
+  document.documentElement.removeAttribute("class");
+}, 10)
+
+</script>
+</body></html>
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -161,8 +161,12 @@ pref(canvas.customfocusring.enabled,true
 
 # Check that captureStream() displays in a local video element
 == capturestream.html wrapper.html?green.png
 
 fuzzy-if(azureSkia,16,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
 
 # Canvas Filter Reftests
 include filters/reftest.list
+
+# Bug 1305963
+== mozCurrentTransform.html mozCurrentTransform-ref.html
+== mozCurrentTransformInverse.html mozCurrentTransform-ref.html
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -644,16 +644,18 @@ HTMLImageElement::UnbindFromTree(bool aD
     nsIDocument* doc = GetOurOwnerDoc();
     MOZ_ASSERT(doc);
     if (doc) {
       doc->RemoveResponsiveContent(this);
       mInDocResponsiveContent = false;
     }
   }
 
+  mLastSelectedSource = nullptr;
+
   nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 void
 HTMLImageElement::UpdateFormOwner()
 {
   if (!mForm) {
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -48,16 +48,17 @@
 #include "nsIFrame.h"
 #include "nsRangeFrame.h"
 #include "nsIServiceManager.h"
 #include "nsError.h"
 #include "nsIEditor.h"
 #include "nsIIOService.h"
 #include "nsDocument.h"
 #include "nsAttrValueOrString.h"
+#include "nsDateTimeControlFrame.h"
 
 #include "nsPresState.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMHTMLCollection.h"
 #include "nsLinebreakConverter.h" //to strip out carriage returns
 #include "nsReadableUtils.h"
 #include "nsUnicharUtils.h"
@@ -2702,16 +2703,92 @@ HTMLInputElement::MozSetDirectory(const 
 
   nsTArray<OwningFileOrDirectory> array;
   OwningFileOrDirectory* element = array.AppendElement();
   element->SetAsDirectory() = directory;
 
   SetFilesOrDirectories(array, true);
 }
 
+void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
+    return;
+  }
+
+  aValue = *mDateTimeInputBoxValue;
+}
+
+void
+HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+    return;
+  }
+
+  nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+  if (frame) {
+    frame->SetValueFromPicker(aValue);
+  }
+}
+
+void
+HTMLInputElement::SetDateTimePickerState(bool aOpen)
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+    return;
+  }
+
+  nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+  if (frame) {
+    frame->SetPickerState(aOpen);
+  }
+}
+
+void
+HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue)
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+    return;
+  }
+
+  mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
+  nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+                                      static_cast<nsIDOMHTMLInputElement*>(this),
+                                      NS_LITERAL_STRING("MozOpenDateTimePicker"),
+                                      true, true);
+}
+
+void
+HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue)
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+    return;
+  }
+
+  mDateTimeInputBoxValue = new DateTimeValue(aValue);
+  nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+                                      static_cast<nsIDOMHTMLInputElement*>(this),
+                                      NS_LITERAL_STRING("MozUpdateDateTimePicker"),
+                                      true, true);
+}
+
+void
+HTMLInputElement::CloseDateTimePicker()
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+    return;
+  }
+
+  nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+                                      static_cast<nsIDOMHTMLInputElement*>(this),
+                                      NS_LITERAL_STRING("MozCloseDateTimePicker"),
+                                      true, true);
+}
+
 bool
 HTMLInputElement::MozIsTextField(bool aExcludePassword)
 {
   // TODO: temporary until bug 888320 is fixed.
   if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
     return false;
   }
 
@@ -2728,23 +2805,58 @@ HTMLInputElement::GetOwnerNumberControl(
       HTMLInputElement::FromContentOrNull(GetParent()->GetParent());
     if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
       return grandparent;
     }
   }
   return nullptr;
 }
 
+HTMLInputElement*
+HTMLInputElement::GetOwnerDateTimeControl()
+{
+  if (IsInNativeAnonymousSubtree() &&
+      mType == NS_FORM_INPUT_TEXT &&
+      GetParent() &&
+      GetParent()->GetParent() &&
+      GetParent()->GetParent()->GetParent() &&
+      GetParent()->GetParent()->GetParent()->GetParent()) {
+    // Yes, this is very very deep.
+    HTMLInputElement* ownerDateTimeControl =
+      HTMLInputElement::FromContentOrNull(
+        GetParent()->GetParent()->GetParent()->GetParent());
+    if (ownerDateTimeControl &&
+        ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
+      return ownerDateTimeControl;
+    }
+  }
+  return nullptr;
+}
+
+
 NS_IMETHODIMP
 HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
 {
   *aResult = MozIsTextField(aExcludePassword);
   return NS_OK;
 }
 
+void
+HTMLInputElement::SetUserInput(const nsAString& aInput,
+                               const mozilla::Maybe<nsIPrincipal*>& aPrincipal) {
+  MOZ_ASSERT(aPrincipal.isSome());
+
+  if (mType == NS_FORM_INPUT_FILE &&
+      !nsContentUtils::IsSystemPrincipal(aPrincipal.value())) {
+    return;
+  }
+
+  SetUserInput(aInput);
+}
+
 NS_IMETHODIMP
 HTMLInputElement::SetUserInput(const nsAString& aValue)
 {
   if (mType == NS_FORM_INPUT_FILE)
   {
     Sequence<nsString> list;
     if (!list.AppendElement(aValue, fallible)) {
       return NS_ERROR_OUT_OF_MEMORY;
@@ -3159,16 +3271,22 @@ HTMLInputElement::SetValueInternal(const
           if (numberControlFrame) {
             numberControlFrame->SetValueOfAnonTextControl(value);
           }
         } else if (mType == NS_FORM_INPUT_RANGE) {
           nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
           if (frame) {
             frame->UpdateForValueChange();
           }
+        } else if (mType == NS_FORM_INPUT_TIME &&
+                   !IsExperimentalMobileType(mType)) {
+          nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+          if (frame) {
+            frame->UpdateInputBoxValue();
+          }
         }
         if (!mParserCreating) {
           OnValueChanged(/* aNotify = */ true,
                          /* aWasInteractiveUserChange = */ false);
         }
         // else DoneCreatingElement calls us again once mParserCreating is false
       }
 
@@ -3461,16 +3579,25 @@ HTMLInputElement::Blur(ErrorResult& aErr
     if (numberControlFrame) {
       HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
       if (textControl) {
         textControl->Blur(aError);
         return;
       }
     }
   }
+
+  if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+    nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+    if (frame) {
+      frame->HandleBlurEvent();
+      return;
+    }
+  }
+
   nsGenericHTMLElement::Blur(aError);
 }
 
 void
 HTMLInputElement::Focus(ErrorResult& aError)
 {
   if (mType == NS_FORM_INPUT_NUMBER) {
     // Focus our anonymous text control, if we have one.
@@ -3480,16 +3607,24 @@ HTMLInputElement::Focus(ErrorResult& aEr
       HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
       if (textControl) {
         textControl->Focus(aError);
         return;
       }
     }
   }
 
+  if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+    nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+    if (frame) {
+      frame->HandleFocusEvent();
+      return;
+    }
+  }
+
   if (mType != NS_FORM_INPUT_FILE) {
     nsGenericHTMLElement::Focus(aError);
     return;
   }
 
   // For file inputs, focus the first button instead. In the case of there
   // being two buttons (when the picker is a directory picker) the user can
   // tab to the next one.
@@ -3783,17 +3918,17 @@ HTMLInputElement::PreHandleEvent(EventCh
     GetValue(mFocusedValue);
   }
 
   // Fire onchange (if necessary), before we do the blur, bug 357684.
   if (aVisitor.mEvent->mMessage == eBlur) {
     // Experimental mobile types rely on the system UI to prevent users to not
     // set invalid values but we have to be extra-careful. Especially if the
     // option has been enabled on desktop.
-    if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
+    if (IsExperimentalMobileType(mType)) {
       nsAutoString aValue;
       GetValueInternal(aValue);
       nsresult rv =
         SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     FireChangeEventIfNeeded();
   }
@@ -3804,16 +3939,28 @@ HTMLInputElement::PreHandleEvent(EventCh
     // Just as nsGenericHTMLFormElementWithState::PreHandleEvent calls
     // nsIFormControlFrame::SetFocus, we handle focus here.
     nsIFrame* frame = GetPrimaryFrame();
     if (frame) {
       frame->InvalidateFrameSubtree();
     }
   }
 
+  if (mType == NS_FORM_INPUT_TIME &&
+      !IsExperimentalMobileType(mType) &&
+      aVisitor.mEvent->mMessage == eFocus &&
+      aVisitor.mEvent->mOriginalTarget == this) {
+    // If original target is this and not the anonymous text control, we should
+    // pass the focus to the anonymous text control.
+    nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+    if (frame) {
+      frame->HandleFocusEvent();
+    }
+  }
+
   if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
     if (mNumberControlSpinnerIsSpinning) {
       // If the timer is running the user has depressed the mouse on one of the
       // spin buttons. If the mouse exits the button we either want to reverse
       // the direction of spin if it has moved over the other button, or else
       // we want to end the spin. We do this here (rather than in
       // PostHandleEvent) because we don't want to let content preventDefault()
       // the end of the spin.
@@ -6677,32 +6824,42 @@ HTMLInputElement::AddStates(EventStates 
 {
   if (mType == NS_FORM_INPUT_TEXT) {
     EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
                                        NS_EVENT_STATE_FOCUSRING));
     if (!focusStates.IsEmpty()) {
       HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
       if (ownerNumberControl) {
         ownerNumberControl->AddStates(focusStates);
+      } else {
+        HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
+        if (ownerDateTimeControl) {
+          ownerDateTimeControl->AddStates(focusStates);
+        }
       }
     }
   }
   nsGenericHTMLFormElementWithState::AddStates(aStates);
 }
 
 void
 HTMLInputElement::RemoveStates(EventStates aStates)
 {
   if (mType == NS_FORM_INPUT_TEXT) {
     EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
                                        NS_EVENT_STATE_FOCUSRING));
     if (!focusStates.IsEmpty()) {
       HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
       if (ownerNumberControl) {
         ownerNumberControl->RemoveStates(focusStates);
+      } else {
+        HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
+        if (ownerDateTimeControl) {
+          ownerDateTimeControl->RemoveStates(focusStates);
+        }
       }
     }
   }
   nsGenericHTMLFormElementWithState::RemoveStates(aStates);
 }
 
 bool
 HTMLInputElement::RestoreState(nsPresState* aState)
@@ -6870,22 +7027,24 @@ HTMLInputElement::IsHTMLFocusable(bool a
 
 #ifdef XP_MACOSX
   const bool defaultFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
 #else
   const bool defaultFocusable = true;
 #endif
 
   if (mType == NS_FORM_INPUT_FILE ||
-      mType == NS_FORM_INPUT_NUMBER) {
+      mType == NS_FORM_INPUT_NUMBER ||
+      mType == NS_FORM_INPUT_TIME) {
     if (aTabIndex) {
       // We only want our native anonymous child to be tabable to, not ourself.
       *aTabIndex = -1;
     }
-    if (mType == NS_FORM_INPUT_NUMBER) {
+    if (mType == NS_FORM_INPUT_NUMBER ||
+        mType == NS_FORM_INPUT_TIME) {
       *aIsFocusable = true;
     } else {
       *aIsFocusable = defaultFocusable;
     }
     return true;
   }
 
   if (mType == NS_FORM_INPUT_HIDDEN) {
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -767,17 +767,34 @@ public:
   int32_t GetTextLength(ErrorResult& aRv);
 
   void MozGetFileNameArray(nsTArray<nsString>& aFileNames, ErrorResult& aRv);
 
   void MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv);
   void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
   void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv);
 
+  /*
+   * The following functions are called from datetime picker to let input box
+   * know the current state of the picker or to update the input box on changes.
+   */
+  void GetDateTimeInputBoxValue(DateTimeValue& aValue);
+  void UpdateDateTimeInputBox(const DateTimeValue& aValue);
+  void SetDateTimePickerState(bool aOpen);
+
+  /*
+   * The following functions are called from datetime input box XBL to control
+   * and update the picker.
+   */
+  void OpenDateTimePicker(const DateTimeValue& aInitialValue);
+  void UpdateDateTimePicker(const DateTimeValue& aValue);
+  void CloseDateTimePicker();
+
   HTMLInputElement* GetOwnerNumberControl();
+  HTMLInputElement* GetOwnerDateTimeControl();
 
   void StartNumberControlSpinnerSpin();
   enum SpinnerStopState {
     eAllowDispatchingEvents,
     eDisallowDispatchingEvents
   };
   void StopNumberControlSpinnerSpin(SpinnerStopState aState =
                                       eAllowDispatchingEvents);
@@ -798,17 +815,18 @@ public:
   {
     return mNumberControlSpinnerIsSpinning && !mNumberControlSpinnerSpinsUp;
   }
 
   bool MozIsTextField(bool aExcludePassword);
 
   nsIEditor* GetEditor();
 
-  // XPCOM SetUserInput() is OK
+  void SetUserInput(const nsAString& aInput,
+                    const mozilla::Maybe<nsIPrincipal*>& aPrincipal);
 
   // XPCOM GetPhonetic() is OK
 
   /**
    * If aValue contains a valid floating-point number in the format specified
    * by the HTML 5 spec:
    *
    *   http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
@@ -1467,16 +1485,22 @@ protected:
   /**
    * If mIsDraggingRange is true, this is the value that the input had before
    * the drag started. Used to reset the input to its old value if the drag is
    * canceled.
    */
   Decimal mRangeThumbDragStartValue;
 
   /**
+   * Current value in the input box, in DateTimeValue dictionary format, see
+   * HTMLInputElement.webidl for details.
+   */
+  nsAutoPtr<DateTimeValue> mDateTimeInputBoxValue;
+
+  /**
    * The selection properties cache for number controls.  This is needed because
    * the number controls don't recycle their text field, so the normal cache in
    * nsTextEditorState cannot do its job.
    */
   nsTextEditorState::SelectionProperties mSelectionProperties;
 
   // Step scale factor values, for input types that have one.
   static const Decimal kStepScaleFactorDate;
@@ -1556,17 +1580,18 @@ private:
     return mType == NS_FORM_INPUT_TEXT || mType == NS_FORM_INPUT_SEARCH ||
            mType == NS_FORM_INPUT_URL || mType == NS_FORM_INPUT_TEL ||
            mType == NS_FORM_INPUT_PASSWORD;
   }
 
   static bool MayFireChangeOnBlur(uint8_t aType) {
     return IsSingleLineTextControl(false, aType) ||
            aType == NS_FORM_INPUT_RANGE ||
-           aType == NS_FORM_INPUT_NUMBER;
+           aType == NS_FORM_INPUT_NUMBER ||
+           aType == NS_FORM_INPUT_TIME;
   }
 
   struct nsFilePickerFilter {
     nsFilePickerFilter()
       : mFilterMask(0) {}
 
     explicit nsFilePickerFilter(int32_t aFilterMask)
       : mFilterMask(aFilterMask) {}
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -5705,90 +5705,90 @@ NS_IMETHODIMP HTMLMediaElement::SetMozPr
 }
 
 ImageContainer* HTMLMediaElement::GetImageContainer()
 {
   VideoFrameContainer* container = GetVideoFrameContainer();
   return container ? container->GetImageContainer() : nullptr;
 }
 
-bool
-HTMLMediaElement::MaybeCreateAudioChannelAgent()
-{
-  if (!mAudioChannelAgent) {
-    nsresult rv;
-    mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return false;
-    }
-    MOZ_ASSERT(mAudioChannelAgent);
-    mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
-                                             static_cast<int32_t>(mAudioChannel),
-                                             this);
-  }
-  return true;
+void
+HTMLMediaElement::CreateAudioChannelAgent()
+{
+  if (mAudioChannelAgent) {
+    return;
+  }
+
+  mAudioChannelAgent = new AudioChannelAgent();
+  mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
+                                           static_cast<int32_t>(mAudioChannel),
+                                           this);
 }
 
 bool
 HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
 {
+  // If we have an error, we are not playing.
+  if (mError) {
+    return false;
+  }
+
   // It might be resumed from remote, we should keep the audio channel agent.
   if (IsSuspendedByAudioChannel()) {
     return true;
   }
 
   // Are we paused
   if (mPaused) {
     return false;
   }
 
-  // If we have an error, we are not playing.
-  if (mError) {
-    return false;
-  }
-
   // We should consider any bfcached page or inactive document as non-playing.
   if (!IsActive()) {
     return false;
   }
 
   // A loop always is playing
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
     return true;
   }
 
+  // If we are actually playing...
+  if (IsCurrentlyPlaying()) {
+    return true;
+  }
+
   // If we are seeking, we consider it as playing
   if (mPlayingThroughTheAudioChannelBeforeSeek) {
     return true;
   }
 
   // If we are playing an external stream.
   if (mSrcAttrStream) {
     return true;
   }
 
-  return true;
+  return false;
 }
 
 void
 HTMLMediaElement::UpdateAudioChannelPlayingState()
 {
   bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();
 
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
 
     // If we are not playing, we don't need to create a new audioChannelAgent.
     if (!mAudioChannelAgent && !mPlayingThroughTheAudioChannel) {
        return;
     }
 
-    if (MaybeCreateAudioChannelAgent()) {
-      NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
-    }
+    CreateAudioChannelAgent();
+    NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
   }
 }
 
 void
 HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying)
 {
   // This is needed to pass nsContentUtils::IsCallerChrome().
   // AudioChannel API should not called from content but it can happen that
@@ -6376,30 +6376,18 @@ HTMLMediaElement::ComputedSuspended() co
   return mAudioChannelSuspended;
 }
 
 bool
 HTMLMediaElement::IsCurrentlyPlaying() const
 {
   // We have playable data, but we still need to check whether data is "real"
   // current data.
-  if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
-      !IsPlaybackEnded()) {
-
-    // Restart the video after ended, it needs to seek to the new position.
-    // In b2g, the cache is not large enough to store whole video data, so we
-    // need to download data again. In this case, although the ready state is
-    // "HAVE_CURRENT_DATA", it is the previous old data. Actually we are not
-    // yet have enough currently data.
-    if (mDecoder && mDecoder->IsSeeking() && !mPlayingBeforeSeek) {
-      return false;
-    }
-    return true;
-  }
-  return false;
+  return mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
+         !IsPlaybackEnded();
 }
 
 void
 HTMLMediaElement::SetAudibleState(bool aAudible)
 {
   if (mIsAudioTrackAudible != aAudible) {
     mIsAudioTrackAudible = aAudible;
     NotifyAudioPlaybackChanged(
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -43,16 +43,17 @@ typedef uint32_t AudibleChangedReasons;
 namespace mozilla {
 class DecoderDoctorDiagnostics;
 class DOMMediaStream;
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
 namespace dom {
+class AudioChannelAgent;
 class MediaKeys;
 class TextTrack;
 class TimeRanges;
 class WakeLock;
 class MediaTrack;
 class MediaStreamTrack;
 class VideoStreamTrack;
 } // namespace dom
@@ -1216,19 +1217,18 @@ protected:
   TextTrackManager* GetOrCreateTextTrackManager();
 
   // Recomputes ready state and fires events as necessary based on current state.
   void UpdateReadyStateInternal();
 
   // Notifies the audio channel agent when the element starts or stops playing.
   void NotifyAudioChannelAgent(bool aPlaying);
 
-  // Creates the audio channel agent if needed.  Returns true if the audio
-  // channel agent is ready to be used.
-  bool MaybeCreateAudioChannelAgent();
+  // Creates the audio channel agent.
+  void CreateAudioChannelAgent();
 
   // Determine if the element should be paused because of suspend conditions.
   bool ShouldElementBePaused();
 
   // Create or destroy the captured stream depend on mAudioCapturedByWindow.
   void AudioCaptureStreamChangeIfNeeded();
 
   /**
@@ -1613,17 +1613,17 @@ protected:
   bool mPlayingThroughTheAudioChannel;
 
   // Disable the video playback by track selection. This flag might not be
   // enough if we ever expand the ability of supporting multi-tracks video
   // playback.
   bool mDisableVideo;
 
   // An agent used to join audio channel service.
-  nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
+  RefPtr<AudioChannelAgent> mAudioChannelAgent;
 
   RefPtr<TextTrackManager> mTextTrackManager;
 
   RefPtr<AudioTrackList> mAudioTrackList;
 
   RefPtr<VideoTrackList> mVideoTrackList;
 
   nsAutoPtr<MediaStreamTrackListener> mMediaStreamTrackListener;
--- a/dom/html/moz.build
+++ b/dom/html/moz.build
@@ -13,16 +13,17 @@ MOCHITEST_MANIFESTS += [
 MOCHITEST_CHROME_MANIFESTS += [
     'test/chrome.ini',
     'test/forms/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 XPIDL_SOURCES += [
+    'nsIDateTimeInputArea.idl',
     'nsIFormSubmitObserver.idl',
     'nsIHTMLMenu.idl',
     'nsIImageDocument.idl',
     'nsIMenuBuilder.idl',
     'nsIPhonetic.idl',
 ]
 
 XPIDL_MODULE = 'content_html'
new file mode 100644
--- /dev/null
+++ b/dom/html/nsIDateTimeInputArea.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(465c0cc3-24cb-48ce-af1a-b18402326b05)]
+interface nsIDateTimeInputArea : nsISupports
+{
+  /**
+   * Called from DOM/Layout when input element value has changed.
+   */
+  void notifyInputElementValueChanged();
+
+  /**
+   * Called when date/time picker value has changed.
+   */
+  void setValueFromPicker(in jsval value);
+
+  /**
+   * Called from DOM/Layout to set focus on inner text box.
+   */
+  void focusInnerTextBox();
+
+  /**
+   * Called from DOM/Layout to blur inner text box.
+   */
+  void blurInnerTextBox();
+
+  /**
+   * Set the current state of the picker, true if it's opened, false otherwise.
+   */
+  void setPickerState(in boolean isOpen);
+};
--- a/dom/html/nsIFormControl.h
+++ b/dom/html/nsIFormControl.h
@@ -261,18 +261,21 @@ bool
 nsIFormControl::IsSingleLineTextControl(bool aExcludePassword, uint32_t aType)
 {
   return aType == NS_FORM_INPUT_TEXT ||
          aType == NS_FORM_INPUT_EMAIL ||
          aType == NS_FORM_INPUT_SEARCH ||
          aType == NS_FORM_INPUT_TEL ||
          aType == NS_FORM_INPUT_URL ||
          // TODO: those are temporary until bug 773205 is fixed.
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+         // On Android/B2G, date/time input appears as a normal text box.
+         aType == NS_FORM_INPUT_TIME ||
+#endif
          aType == NS_FORM_INPUT_DATE ||
-         aType == NS_FORM_INPUT_TIME ||
          aType == NS_FORM_INPUT_MONTH ||
          aType == NS_FORM_INPUT_WEEK ||
          (!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
 }
 
 bool
 nsIFormControl::IsSubmittableControl() const
 {
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-time-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- In this case we're using reftest-wait to make sure the test doesn't
+       get snapshotted before it's been focused. We're not testing
+       invalidation so we don't need to listen for MozReftestInvalidate.
+  -->
+  <head>
+    <script>
+      function focusHandler() {
+        setTimeout(function() {
+          document.documentElement.removeAttribute("class");
+        }, 0);
+      }
+    </script>
+  </head>
+  <body onload="document.getElementById('t').focus();">
+    <input type="time" id="t" onfocus="focusHandler();"
+           style="-moz-appearance: none;">
+  </body>
+</html>
+
+
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/autofocus/input-time.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- In this case we're using reftest-wait to make sure the test doesn't
+       get snapshotted before it's been focused. We're not testing
+       invalidation so we don't need to listen for MozReftestInvalidate.
+  -->
+  <head>
+    <script>
+      function focusHandler() {
+        setTimeout(function() {
+          document.documentElement.removeAttribute("class");
+        }, 0);
+      }
+    </script>
+  </head>
+  <body>
+    <input type="time" autofocus onfocus="focusHandler();"
+           style="-moz-appearance: none;">
+  </body>
+</html>
+
+
--- a/dom/html/reftests/autofocus/reftest.list
+++ b/dom/html/reftests/autofocus/reftest.list
@@ -1,12 +1,13 @@
-default-preferences pref(dom.forms.number,true)
+default-preferences pref(dom.forms.number,true) pref(dom.forms.datetime,true)
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-load.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-create.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-number.html input-number-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
+skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-time.html input-time-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-load.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-create.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == textarea-load.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == textarea-create.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,9,6) needs-focus == select-load.html select-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 skip-if(B2G||Mulet) fuzzy-if(skiaContent,2,4) needs-focus == select-create.html select-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
 needs-focus == autofocus-after-load.html autofocus-after-load-ref.html
 fails-if(B2G||Mulet) fuzzy-if(skiaContent,2,5) needs-focus == autofocus-leaves-iframe.html autofocus-leaves-iframe-ref.html # B2G focus difference between test and reference # Initial mulet triage: parity with B2G/B2G Desktop
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/image-load-shortcircuit-ref.html
@@ -0,0 +1,1 @@
+<div><img src=pass.png></div>
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/image-load-shortcircuit.html
@@ -0,0 +1,8 @@
+<html>
+<div></div>
+<script>
+  var d = (new DOMParser()).parseFromString("<img src=pass.png>", "text/html");
+  var n = d.adoptNode(d.querySelector('img'));
+  document.querySelector('div').appendChild(n);
+</script>
+</html>
--- a/dom/html/reftests/reftest.list
+++ b/dom/html/reftests/reftest.list
@@ -30,16 +30,17 @@ skip-if(Android||B2G) == 649134-2.html 6
 
 == bug448564-1_malformed.html bug448564-1_well-formed.html
 == bug448564-1_malformed.html bug448564-1_ideal.html
 
 == bug448564-4a.html          bug448564-4b.html
 == bug502168-1_malformed.html bug502168-1_well-formed.html
 
 == responsive-image-load-shortcircuit.html responsive-image-load-shortcircuit-ref.html
+== image-load-shortcircuit.html image-load-shortcircuit-ref.html
 
 # Test that image documents taken into account CSS properties like
 # image-orientation when determining the size of the image.
 # (Fuzzy necessary due to pixel-wise comparison of different JPEGs.
 # The vast majority of the fuzziness comes from Linux and WinXP.)
 fuzzy(1,149) == bug917595-iframe-1.html    bug917595-1-ref.html
 skip-if(B2G||Mulet) fuzzy-if((!B2G&&!Mulet),3,640) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg # bug 1060869 # Bug 1150490 disabling on Mulet as on B2G
 
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -30,16 +30,20 @@ skip-if = buildapp == 'mulet'
 [test_input_color_input_change_events.html]
 skip-if = buildapp == 'mulet'
 [test_input_color_picker_initial.html]
 skip-if = buildapp == 'mulet'
 [test_input_color_picker_popup.html]
 skip-if = android_version == '18' # Android, bug 1147974
 [test_input_color_picker_update.html]
 skip-if = android_version == '18' # Android, bug 1147974
+[test_input_datetime_focus_blur.html]
+skip-if = os == "android" || appname == "b2g"
+[test_input_datetime_tabindex.html]
+skip-if = os == "android" || appname == "b2g"
 [test_input_defaultValue.html]
 [test_input_email.html]
 [test_input_event.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || android_version == '18' #Bug 931116, b2g desktop and mulet specific, initial triage; on Android, bug 1147974
 [test_input_file_picker.html]
 skip-if = buildapp == 'b2g' # b2g(5 failures out of 139 and timing out, bug 901581) b2g-debug(5 failures out of 139 and timing out, bug 901581) b2g-desktop(5 failures out of 139 and timing out, bug 901581)
 [test_input_list_attribute.html]
 [test_input_number_l10n.html]
@@ -59,16 +63,18 @@ skip-if = os == "android" || appname == 
 [test_input_range_key_events.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_input_range_mouse_and_touch_events.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926546
 [test_input_range_rounding.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_input_sanitization.html]
 [test_input_textarea_set_value_no_scroll.html]
+[test_input_time_key_events.html]
+skip-if = os == "android" || appname == "b2g"
 [test_input_types_pref.html]
 [test_input_typing_sanitization.html]
 skip-if = buildapp == 'mulet'
 [test_input_untrusted_key_events.html]
 [test_input_url.html]
 [test_interactive_content_in_label.html]
 [test_label_control_attribute.html]
 [test_label_input_controls.html]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_focus_blur.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
+-->
+<head>
+  <title>Test focus/blur behaviour for &lt;input type='time'&gt;</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288591">Mozilla Bug 1288591</a>
+<p id="display"></p>
+<div id="content">
+  <input id="input" type="time">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1288591.
+ * This test checks whether date/time input types' .focus()/.blur() works
+ * correctly. This test also checks when focusing on an date/time input element,
+ * the focus is redirected to the anonymous text control, but the
+ * document.activeElement still returns date/time input element.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  test();
+  SimpleTest.finish();
+});
+
+function test() {
+  let time = document.getElementById("input");
+  time.focus();
+
+  // The active element returns the input type=time.
+  let activeElement = document.activeElement;
+  is(activeElement, time, "activeElement should be the time element");
+  is(activeElement.localName, "input", "activeElement should be an input element");
+  is(activeElement.type, "time", "activeElement should be of type time");
+
+  // Use FocusManager to check that the actual focus is on the anonymous
+  // text control.
+  let fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]
+                        .getService(SpecialPowers.Ci.nsIFocusManager);
+  let focusedElement = fm.focusedElement;
+  is(focusedElement.localName, "input", "focusedElement should be an input element");
+  is(focusedElement.type, "text", "focusedElement should be of type text");
+
+  time.blur();
+  isnot(document.activeElement, time, "activeElement should no longer be the time element");
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_tabindex.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
+-->
+<head>
+  <title>Test tabindex attribute for &lt;input type='time'&gt;</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288591">Mozilla Bug 1288591</a>
+<p id="display"></p>
+<div id="content">
+  <input id="time1" type="time" tabindex="0">
+  <input id="time2" type="time" tabindex="-1">
+  <input id="time3" type="time" tabindex="0">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 1288591.
+ * This test checks whether date/time input types' tabindex attribute works
+ * correctly.
+ **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  test();
+  SimpleTest.finish();
+});
+
+function test() {
+  let time1 = document.getElementById("time1");
+  let time2 = document.getElementById("time2");
+  let time3 = document.getElementById("time3");
+
+  time1.focus();
+  is(document.activeElement, time1,
+     "input element with tabindex=0 is focusable");
+
+  // Advance to time1 minute field
+  synthesizeKey("VK_TAB", {});
+  is(document.activeElement, time1,
+     "input element with tabindex=0 is tabbable");
+
+  // Advance to time1 AM/PM field
+  synthesizeKey("VK_TAB", {});
+  is(document.activeElement, time1,
+     "input element with tabindex=0 is tabbable");
+
+  // Advance to next element
+  synthesizeKey("VK_TAB", {});
+  is(document.activeElement, time3,
+     "input element with tabindex=-1 is not tabbable");
+
+  time2.focus();
+  is(document.activeElement, time2,
+     "input element with tabindex=-1 is still focusable");
+
+  // Changing the tabindex attribute dynamically.
+  time3.setAttribute("tabindex", "-1");
+  synthesizeKey("VK_TAB", {}); // need only one TAB since time2 is not tabbable
+  isnot(document.activeElement, time3,
+        "element with tabindex changed to -1 should not be tabbable");
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/html/test/forms/test_input_time_key_events.html
@@ -0,0 +1,197 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
+-->
+<head>
+  <title>Test key events for time control</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <meta charset="UTF-8">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288591">Mozilla Bug 1288591</a>
+<p id="display"></p>
+<div id="content">
+  <input id="input" type="time">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+// Turn off Spatial Navigation because it hijacks arrow keydown events:
+SimpleTest.waitForFocus(function() {
+  SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() {
+    test();
+    SimpleTest.finish();
+  });
+});
+
+var testData = [
+  /**
+   * keys: keys to send to the input element.
+   * initialVal: initial value set to the input element.
+   * expectedVal: expected value of the input element after sending the keys.
+   */
+  {
+    // Type 1030 and select AM.
+    keys: ["1030", "VK_DOWN"],
+    initialVal: "",
+    expectedVal: "10:30"
+  },
+  {
+    // Type 3 in the hour field will automatically advance to the minute field.
+    keys: ["330", "VK_DOWN"],
+    initialVal: "",
+    expectedVal: "03:30"
+  },
+  {
+    // Type 5 in the hour field will automatically advance to the minute field.
+    // Type 7 in the minute field will automatically advance to the AM/PM field.
+    keys: ["57", "VK_DOWN"],
+    initialVal: "",
+    expectedVal: "05:07"
+  },
+  {
+    // Advance to AM/PM field and change to PM.
+    keys: ["VK_TAB", "VK_TAB", "VK_DOWN"],
+    initialVal: "10:30",
+    expectedVal: "22:30"
+  },
+  {
+    // Right key should do the same thing as TAB key.
+    keys: ["VK_RIGHT", "VK_RIGHT", "VK_DOWN"],
+    initialVal: "10:30",
+    expectedVal: "22:30"
+  },
+  {
+    // Advance to minute field then back to hour field and decrement.
+    keys: ["VK_RIGHT", "VK_LEFT", "VK_DOWN"],
+    initialVal: "10:30",
+    expectedVal: "09:30"
+  },
+  {
+    // Focus starts on the first field, hour in this case, and increment.
+    keys: ["VK_UP"],
+    initialVal: "16:00",
+    expectedVal: "17:00"
+  },
+  {
+    // Advance to minute field and decrement.
+    keys: ["VK_TAB", "VK_DOWN"],
+    initialVal: "16:00",
+    expectedVal: "16:59"
+  },
+  {
+    // Advance to minute field and increment.
+    keys: ["VK_TAB", "VK_UP"],
+    initialVal: "16:59",
+    expectedVal: "16:00"
+  },
+  {
+    // PageUp on hour field increments hour by 3.
+    keys: ["VK_PAGE_UP"],
+    initialVal: "05:00",
+    expectedVal: "08:00"
+  },
+  {
+    // PageDown on hour field decrements hour by 3.
+    keys: ["VK_PAGE_DOWN"],
+    initialVal: "05:00",
+    expectedVal: "02:00"
+  },
+  {
+    // PageUp on minute field increments minute by 10.
+    keys: ["VK_TAB", "VK_PAGE_UP"],
+    initialVal: "14:00",
+    expectedVal: "14:10"
+  },
+  {
+    // PageDown on minute field decrements minute by 10.
+    keys: ["VK_TAB", "VK_PAGE_DOWN"],
+    initialVal: "14:00",
+    expectedVal: "14:50"
+  },
+  {
+    // Home key on hour field sets it to the minimum hour, which is 1 in 12-hour
+    // clock.
+    keys: ["VK_HOME"],
+    initialVal: "03:10",
+    expectedVal: "01:10"
+  },
+  {
+    // End key on hour field sets it to the maximum hour, which is 12 in 12-hour
+    // clock.
+    keys: ["VK_END"],
+    initialVal: "03:10",
+    expectedVal: "00:10"
+  },
+  {
+    // Home key on minute field sets it to the minimum minute, which is 0.
+    keys: ["VK_TAB", "VK_HOME"],
+    initialVal: "19:30",
+    expectedVal: "19:00"
+  },
+  {
+    // End key on minute field sets it to the minimum minute, which is 59.
+    keys: ["VK_TAB", "VK_END"],
+    initialVal: "19:30",
+    expectedVal: "19:59"
+  },
+  // Second field will show up when needed.
+  {
+    // PageUp on second field increments second by 10.
+    keys: ["VK_TAB", "VK_TAB", "VK_PAGE_UP"],
+    initialVal: "08:10:10",
+    expectedVal: "08:10:20"
+  },
+  {
+    // PageDown on second field increments second by 10.
+    keys: ["VK_TAB", "VK_TAB", "VK_PAGE_DOWN"],
+    initialVal: "08:10:10",
+    expectedVal: "08:10:00"
+  },
+  {
+    // Home key on second field sets it to the minimum second, which is 0.
+    keys: ["VK_TAB", "VK_TAB", "VK_HOME"],
+    initialVal: "16:00:30",
+    expectedVal: "16:00:00"
+  },
+  {
+    // End key on second field sets it to the minimum second, which is 59.
+    keys: ["VK_TAB", "VK_TAB", "VK_END"],
+    initialVal: "16:00:30",
+    expectedVal: "16:00:59"
+  },
+];
+
+function sendKeys(aKeys) {
+  for (let i = 0; i < aKeys.length; i++) {
+    let key = aKeys[i];
+    if (key.startsWith("VK")) {
+      synthesizeKey(key, {});
+    } else {
+      sendString(key);
+    }
+  }
+}
+
+function test() {
+  var elem = document.getElementById("input");
+
+  for (let { keys, initialVal, expectedVal } of testData) {
+    elem.focus();
+    elem.value = initialVal;
+    sendKeys(keys);
+    elem.blur();
+    is(elem.value, expectedVal,
+       "Test with " + keys + ", result should be " + expectedVal);
+    elem.value = "";
+  }
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/html/test/forms/test_input_typing_sanitization.html
+++ b/dom/html/test/forms/test_input_typing_sanitization.html
@@ -156,37 +156,16 @@ function runTest()
         '-',
         '2012-01',
         '2013-01-1',
         '1011-23-21',
         '1000-12-99',
       ]
     },
     {
-      type: 'time',
-      validData: [
-        '00:00',
-        '09:09:00',
-        '08:23:23.1',
-        '21:43:56.12',
-        '23:12:45.100',
-      ],
-      invalidData: [
-        '00:',
-        '00:00:',
-        '25:00',
-        '-00:00',
-        '00:00:00.',
-        '00:60',
-        '10:58:99',
-        ':19:10',
-        '23:08:09.1012',
-      ]
-    },
-    {
       type: 'month',
       validData: [
         '0001-01',
         '2012-12',
         '100000-01',
       ],
       invalidData: [
         '1-01',
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -242,18 +242,60 @@ ManifestInvalidType=Expected the %1$S’s %2$S member to be a %3$S.
 ManifestInvalidCSSColor=%1$S: %2$S is not a valid CSS color.
 PatternAttributeCompileFailure=Unable to check <input pattern='%S'> because the pattern is not a valid regexp: %S
 # LOCALIZATION NOTE: Do not translate "postMessage" or DOMWindow. %S values are origins, like https://domain.com:port
 TargetPrincipalDoesNotMatch=Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘%S’) does not match the recipient window’s origin (‘%S’).
 # LOCALIZATION NOTE: Do not translate 'YouTube'. %S values are origins, like https://domain.com:port
 RewriteYouTubeEmbed=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible.
 # LOCALIZATION NOTE: Do not translate 'YouTube'. %S values are origins, like https://domain.com:port
 RewriteYouTubeEmbedPathParams=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Params were unsupported by iframe embeds and converted. Please update page to use iframe instead of embed/object, if possible.
-# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string.
-PushMessageDecryptionFailure=The ServiceWorker for scope ‘%1$S’ encountered an error decrypting a push message: ‘%2$S’. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
+# LOCALIZATION NOTE: This error is reported when the "Encryption" header for an
+# incoming push message is missing or invalid. Do not translate "ServiceWorker",
+# "Encryption", and "salt". %1$S is the ServiceWorker scope URL.
+PushMessageBadEncryptionHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption’ header must include a unique ‘salt‘ parameter for each message.
+# LOCALIZATION NOTE: This error is reported when the "Crypto-Key" header for an
+# incoming push message is missing or invalid. Do not translate "ServiceWorker",
+# "Crypto-Key", and "dh". %1$S is the ServiceWorker scope URL.
+PushMessageBadCryptoKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Crypto-Key‘ header must include a ‘dh‘ parameter containing the app server’s public key.
+# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt because the deprecated
+# "Encryption-Key" header for an incoming push message is missing or invalid.
+# Do not translate "ServiceWorker", "Encryption-Key", "dh", "Crypto-Key", and
+# "Content-Encoding: aesgcm". %1$S is the ServiceWorker scope URL.
+PushMessageBadEncryptionKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption-Key’ header must include a ‘dh‘ parameter. This header is deprecated and will soon be removed. Please use ‘Crypto-Key‘ with ‘Content-Encoding: aesgcm‘ instead.
+# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
+# because the "Content-Encoding" header is missing or contains an
+# unsupported encoding. Do not translate "ServiceWorker", "Content-Encoding",
+# "aesgcm", and "aesgcm128". %1$S is the ServiceWorker scope URL.
+PushMessageBadEncodingHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Content-Encoding‘ header must be ‘aesgcm‘. ‘aesgcm128‘ is allowed, but deprecated and will soon be removed.
+# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
+# because the "dh" parameter is not valid base64url. Do not translate
+# "ServiceWorker", "dh", "Crypto-Key", and "base64url". %1$S is the
+# ServiceWorker scope URL.
+PushMessageBadSenderKey=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘dh‘ parameter in the ‘Crypto-Key‘ header must be the app server’s Diffie-Hellman public key, base64url-encoded (RFC 7515, Appendix C) and in “uncompressed” or “raw” form (65 bytes before encoding).
+# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
+# because the "salt" parameter is not valid base64url. Do not translate
+# "ServiceWorker", "salt", "Encryption", and "base64url". %1$S is the
+# ServiceWorker scope URL.
+PushMessageBadSalt=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘salt‘ parameter in the ‘Encryption‘ header must be base64url-encoded (RFC 7515, Appendix C), and be at least 16 bytes before encoding.
+# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
+# because the "rs" parameter is not a number, or is less than the pad size.
+# Do not translate "ServiceWorker", "rs", or "Encryption". %1$S is the
+# ServiceWorker scope URL. %2$S is the minimum value (1 for aesgcm128, 2 for
+# aesgcm).
+PushMessageBadRecordSize=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘rs‘ parameter of the ‘Encryption‘ header must be between %2$S and 2^36-31, or omitted entirely.
+# LOCALIZATION NOTE: This error is reported when a push message fails to decrypt
+# because an encrypted record is shorter than the pad size, the pad is larger
+# than the record, or any of the padding bytes are non-zero. Do not translate
+# "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is the pad size
+# (1 for aesgcm128, 2 for aesgcm).
+PushMessageBadPaddingError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. Each record in the encrypted message must be padded with a %2$S byte big-endian unsigned integer, followed by that number of zero-valued bytes.
+# LOCALIZATION NOTE: This error is reported when push message decryption fails
+# and no specific error info is available. Do not translate "ServiceWorker".
+# %1$S is the ServiceWorker scope URL.
+PushMessageBadCryptoError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
 # LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec.
 PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’.
 FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead.
 ChromeScriptedDOMParserWithoutPrincipal=Creating DOMParser without a principal is deprecated.
 IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches.
 BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches.
 # LOCALIZATION NOTE: %1$S is the unanimatable paced property.
 UnanimatablePacedProperty=Paced property ‘%1$S’ is not an animatable property.
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -29,16 +29,43 @@ struct DecryptResult {
   DecryptResult(DecryptStatus aStatus, MediaRawData* aSample)
     : mStatus(aStatus)
     , mSample(aSample)
   {}
   DecryptStatus mStatus;
   RefPtr<MediaRawData> mSample;
 };
 
+class CDMKeyInfo {
+public:
+  explicit CDMKeyInfo(const nsTArray<uint8_t>& aKeyId)
+    : mKeyId(aKeyId)
+    , mStatus()
+  {}
+
+  CDMKeyInfo(const nsTArray<uint8_t>& aKeyId,
+             const dom::Optional<dom::MediaKeyStatus>& aStatus)
+    : mKeyId(aKeyId)
+    , mStatus(aStatus.Value())
+  {}
+
+  // The copy-ctor and copy-assignment operator for Optional<T> are declared as
+  // delete, so override CDMKeyInfo copy-ctor for nsTArray operations.
+  CDMKeyInfo(const CDMKeyInfo& aKeyInfo)
+  {
+    mKeyId = aKeyInfo.mKeyId;
+    if (aKeyInfo.mStatus.WasPassed()) {
+      mStatus.Construct(aKeyInfo.mStatus.Value());
+    }
+  }
+
+  nsTArray<uint8_t> mKeyId;
+  dom::Optional<dom::MediaKeyStatus> mStatus;
+};
+
 typedef int64_t UnixTime;
 
 // Proxies calls CDM, and proxies calls back.
 // 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 {
 protected:
--- a/dom/media/eme/DecryptorProxyCallback.h
+++ b/dom/media/eme/DecryptorProxyCallback.h
@@ -36,21 +36,17 @@ public:
 
   virtual void SessionClosed(const nsCString& aSessionId) = 0;
 
   virtual void SessionError(const nsCString& aSessionId,
                             nsresult aException,
                             uint32_t aSystemCode,
                             const nsCString& aMessage) = 0;
 
-  virtual void KeyStatusChanged(const nsCString& aSessionId,
-                                const nsTArray<uint8_t>& aKeyId,
-                                mozilla::dom::MediaKeyStatus aStatus) = 0;
-
-  virtual void ForgetKeyStatus(const nsCString& aSessionId,
-                               const nsTArray<uint8_t>& aKeyId) = 0;
-
   virtual void Decrypted(uint32_t aId,
                          mozilla::DecryptStatus aResult,
                          const nsTArray<uint8_t>& aDecryptedData) = 0;
+
+  virtual void BatchedKeyStatusChanged(const nsCString& aSessionId,
+                                       const nsTArray<mozilla::CDMKeyInfo>& aKeyInfos) = 0;
 };
 
-#endif
\ No newline at end of file
+#endif
--- a/dom/media/gmp/GMPCDMCallbackProxy.cpp
+++ b/dom/media/gmp/GMPCDMCallbackProxy.cpp
@@ -275,49 +275,36 @@ GMPCDMCallbackProxy::SessionError(const 
                               aSessionId,
                               aException,
                               aSystemCode,
                               aMessage);
   NS_DispatchToMainThread(task);
 }
 
 void
-GMPCDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId,
-                                      const nsTArray<uint8_t>& aKeyId,
-                                      dom::MediaKeyStatus aStatus)
+GMPCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
+                                             const nsTArray<CDMKeyInfo>& aKeyInfos)
 {
   MOZ_ASSERT(mProxy->IsOnOwnerThread());
-
-  KeyStatusChangedInternal(aSessionId,
-                           aKeyId,
-                           dom::Optional<dom::MediaKeyStatus>(aStatus));
+  BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
 }
 
 void
-GMPCDMCallbackProxy::ForgetKeyStatus(const nsCString& aSessionId,
-                                     const nsTArray<uint8_t>& aKeyId)
-{
-  MOZ_ASSERT(mProxy->IsOnOwnerThread());
-
-  KeyStatusChangedInternal(aSessionId,
-                           aKeyId,
-                           dom::Optional<dom::MediaKeyStatus>());
-}
-
-void
-GMPCDMCallbackProxy::KeyStatusChangedInternal(const nsCString& aSessionId,
-                                              const nsTArray<uint8_t>& aKeyId,
-                                              const dom::Optional<dom::MediaKeyStatus>& aStatus)
+GMPCDMCallbackProxy::BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+                                                     const nsTArray<CDMKeyInfo>& aKeyInfos)
 {
   bool keyStatusesChange = false;
   {
     CDMCaps::AutoLock caps(mProxy->Capabilites());
-    keyStatusesChange = caps.SetKeyStatus(aKeyId,
-                                          NS_ConvertUTF8toUTF16(aSessionId),
-                                          aStatus);
+    for (size_t i = 0; i < aKeyInfos.Length(); i++) {
+      keyStatusesChange |=
+        caps.SetKeyStatus(aKeyInfos[i].mKeyId,
+                          NS_ConvertUTF8toUTF16(aSessionId),
+                          aKeyInfos[i].mStatus);
+    }
   }
   if (keyStatusesChange) {
     nsCOMPtr<nsIRunnable> task;
     task = NewRunnableMethod<nsString>(mProxy,
                                        &CDMProxy::OnKeyStatusesChange,
                                        NS_ConvertUTF8toUTF16(aSessionId));
     NS_DispatchToMainThread(task);
   }
--- a/dom/media/gmp/GMPCDMCallbackProxy.h
+++ b/dom/media/gmp/GMPCDMCallbackProxy.h
@@ -38,37 +38,32 @@ public:
 
   void SessionClosed(const nsCString& aSessionId) override;
 
   void SessionError(const nsCString& aSessionId,
                     nsresult aException,
                     uint32_t aSystemCode,
                     const nsCString& aMessage) override;
 
-  void KeyStatusChanged(const nsCString& aSessionId,
-                        const nsTArray<uint8_t>& aKeyId,
-                        dom::MediaKeyStatus aStatus) override;
-
-  void ForgetKeyStatus(const nsCString& aSessionId,
-                       const nsTArray<uint8_t>& aKeyId) override;
-
   void Decrypted(uint32_t aId,
                  DecryptStatus aResult,
                  const nsTArray<uint8_t>& aDecryptedData) override;
 
+  void BatchedKeyStatusChanged(const nsCString& aSessionId,
+                               const nsTArray<CDMKeyInfo>& aKeyInfos) override;
+
   void Terminated() override;
 
   ~GMPCDMCallbackProxy() {}
 
 private:
   friend class GMPCDMProxy;
   explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
-  void KeyStatusChangedInternal(const nsCString& aSessionId,
-                                const nsTArray<uint8_t>& aKeyId,
-                                const dom::Optional<dom::MediaKeyStatus>& aStatus);
 
+  void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+                                       const nsTArray<CDMKeyInfo>& aKeyInfos);
   // Warning: Weak ref.
   CDMProxy* mProxy;
 };
 
 } // namespace mozilla
 
 #endif // GMPCDMCallbackProxy_h_
--- a/dom/media/gmp/GMPDecryptorChild.cpp
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -156,26 +156,39 @@ void
 GMPDecryptorChild::KeyStatusChanged(const char* aSessionId,
                                     uint32_t aSessionIdLength,
                                     const uint8_t* aKeyId,
                                     uint32_t aKeyIdLength,
                                     GMPMediaKeyStatus aStatus)
 {
   AutoTArray<uint8_t, 16> kid;
   kid.AppendElements(aKeyId, aKeyIdLength);
-  if (aStatus == kGMPUnknown) {
-    CALL_ON_GMP_THREAD(SendForgetKeyStatus,
-                       nsCString(aSessionId, aSessionIdLength),
-                       kid);
-  } else {
-    CALL_ON_GMP_THREAD(SendKeyStatusChanged,
-                       nsCString(aSessionId, aSessionIdLength),
-                       kid,
-                       aStatus);
+
+  nsTArray<GMPKeyInformation> keyInfos;
+  keyInfos.AppendElement(GMPKeyInformation(kid, aStatus));
+  CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
+                     nsCString(aSessionId, aSessionIdLength),
+                     keyInfos);
+}
+
+void
+GMPDecryptorChild::BatchedKeyStatusChanged(const char* aSessionId,
+                                           uint32_t aSessionIdLength,
+                                           const GMPMediaKeyInfo* aKeyInfos,
+                                           uint32_t aKeyInfosLength)
+{
+  nsTArray<GMPKeyInformation> keyInfos;
+  for (uint32_t i = 0; i < aKeyInfosLength; i++) {
+    nsTArray<uint8_t> keyId;
+    keyId.AppendElements(aKeyInfos[i].keyid, aKeyInfos[i].keyid_size);
+    keyInfos.AppendElement(GMPKeyInformation(keyId, aKeyInfos[i].status));
   }
+  CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
+                     nsCString(aSessionId, aSessionIdLength),
+                     keyInfos);
 }
 
 void
 GMPDecryptorChild::Decrypted(GMPBuffer* aBuffer, GMPErr aResult)
 {
   if (!ON_GMP_THREAD()) {
     // We should run this whole method on the GMP thread since the buffer needs
     // to be deleted after the SendDecrypted call.
--- a/dom/media/gmp/GMPDecryptorChild.h
+++ b/dom/media/gmp/GMPDecryptorChild.h
@@ -68,16 +68,21 @@ public:
                         const uint8_t* aKeyId,
                         uint32_t aKeyIdLength,
                         GMPMediaKeyStatus aStatus) override;
 
   void SetCapabilities(uint64_t aCaps) override;
 
   void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) override;
 
+  void BatchedKeyStatusChanged(const char* aSessionId,
+                               uint32_t aSessionIdLength,
+                               const GMPMediaKeyInfo* aKeyInfos,
+                               uint32_t aKeyInfosLength) override;
+
   // GMPDecryptorHost
   void GetSandboxVoucher(const uint8_t** aVoucher,
                          uint32_t* aVoucherLength) override;
 
   void GetPluginVoucher(const uint8_t** aVoucher,
                         uint32_t* aVoucherLength) override;
 private:
   ~GMPDecryptorChild();
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -353,38 +353,37 @@ ToMediaKeyStatus(GMPMediaKeyStatus aStat
     case kGMPInternalError: return dom::MediaKeyStatus::Internal_error;
     case kGMPReleased: return dom::MediaKeyStatus::Released;
     case kGMPStatusPending: return dom::MediaKeyStatus::Status_pending;
     default: return dom::MediaKeyStatus::Internal_error;
   }
 }
 
 bool
-GMPDecryptorParent::RecvKeyStatusChanged(const nsCString& aSessionId,
-                                         InfallibleTArray<uint8_t>&& aKeyId,
-                                         const GMPMediaKeyStatus& aStatus)
+GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+                                                InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
 {
-  LOGD(("GMPDecryptorParent[%p]::RecvKeyStatusChanged(sessionId='%s', keyId=%s, status=%d)",
-        this, aSessionId.get(), ToBase64(aKeyId).get(), aStatus));
+  LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(sessionId='%s', KeyInfos len='%d')",
+        this, aSessionId.get(), aKeyInfos.Length()));
 
   if (mIsOpen) {
-    mCallback->KeyStatusChanged(aSessionId, aKeyId, ToMediaKeyStatus(aStatus));
-  }
-  return true;
-}
-
-bool
-GMPDecryptorParent::RecvForgetKeyStatus(const nsCString& aSessionId,
-                                        InfallibleTArray<uint8_t>&& aKeyId)
-{
-  LOGD(("GMPDecryptorParent[%p]::RecvForgetKeyStatus(sessionId='%s', keyId=%s)",
-        this, aSessionId.get(), ToBase64(aKeyId).get()));
-
-  if (mIsOpen) {
-    mCallback->ForgetKeyStatus(aSessionId, aKeyId);
+    nsTArray<CDMKeyInfo> cdmKeyInfos(aKeyInfos.Length());
+    for (uint32_t i = 0; i < aKeyInfos.Length(); i++) {
+      LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(keyId=%s, gmp-status=%d)",
+            this, ToBase64(aKeyInfos[i].keyId()).get(), aKeyInfos[i].status()));
+      // If the status is kGMPUnknown, we're going to forget(remove) that key info.
+      if (aKeyInfos[i].status() != kGMPUnknown) {
+        auto status = ToMediaKeyStatus(aKeyInfos[i].status());
+        cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId(),
+                                             dom::Optional<dom::MediaKeyStatus>(status)));
+      } else {
+        cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId()));
+      }
+    }
+    mCallback->BatchedKeyStatusChanged(aSessionId, cdmKeyInfos);
   }
   return true;
 }
 
 DecryptStatus
 ToDecryptStatus(GMPErr aError)
 {
   switch (aError) {
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -93,27 +93,23 @@ private:
 
   bool RecvSessionClosed(const nsCString& aSessionId) override;
 
   bool RecvSessionError(const nsCString& aSessionId,
                         const GMPDOMException& aException,
                         const uint32_t& aSystemCode,
                         const nsCString& aMessage) override;
 
-  bool RecvKeyStatusChanged(const nsCString& aSessionId,
-                            InfallibleTArray<uint8_t>&& aKeyId,
-                            const GMPMediaKeyStatus& aStatus) override;
-
-  bool RecvForgetKeyStatus(const nsCString& aSessionId,
-                           InfallibleTArray<uint8_t>&& aKeyId) override;
-
   bool RecvDecrypted(const uint32_t& aId,
                      const GMPErr& aErr,
                      InfallibleTArray<uint8_t>&& aBuffer) override;
 
+  bool RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+                                   InfallibleTArray<GMPKeyInformation>&& aKeyInfos) override;
+
   bool RecvShutdown() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
   bool Recv__delete__() override;
 
   bool mIsOpen;
   bool mShuttingDown;
   bool mActorDestroyed;
--- a/dom/media/gmp/GMPTypes.ipdlh
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 using GMPBufferType from "gmp-video-codec.h";
 using GMPAudioCodecType from "gmp-audio-codec.h";
+using GMPMediaKeyStatus from "gmp-decryption.h";
 
 namespace mozilla {
 namespace gmp {
 
 struct GMPDecryptionData {
   uint8_t[] mKeyId;
   uint8_t[] mIV;
   uint16_t[] mClearBytes;
@@ -71,10 +72,15 @@ struct GMPAudioEncodedSampleData
 struct GMPAudioDecodedSampleData
 {
   int16_t[] mData;
   uint64_t mTimeStamp; // microseconds.
   uint32_t mChannelCount;
   uint32_t mSamplesPerSecond;
 };
 
+struct GMPKeyInformation {
+  uint8_t[] keyId;
+  GMPMediaKeyStatus status;
+};
+
 }
 }
--- a/dom/media/gmp/PGMPDecryptor.ipdl
+++ b/dom/media/gmp/PGMPDecryptor.ipdl
@@ -2,17 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PGMPContent;
 include GMPTypes;
 
 using GMPSessionMessageType from  "gmp-decryption.h";
-using GMPMediaKeyStatus from  "gmp-decryption.h";
 using GMPSessionType from  "gmp-decryption.h";
 using GMPDOMException from "gmp-decryption.h";
 using GMPErr from "gmp-errors.h";
 
 namespace mozilla {
 namespace gmp {
 
 async protocol PGMPDecryptor
@@ -74,20 +73,18 @@ parent:
 
   async SessionClosed(nsCString aSessionId);
 
   async SessionError(nsCString aSessionId,
                      GMPDOMException aDOMExceptionCode,
                      uint32_t aSystemCode,
                      nsCString aMessage);
 
-  async KeyStatusChanged(nsCString aSessionId, uint8_t[] aKey,
-                         GMPMediaKeyStatus aStatus);
-
-  async ForgetKeyStatus(nsCString aSessionId, uint8_t[] aKey);
-
   async Decrypted(uint32_t aId, GMPErr aResult, uint8_t[] aBuffer);
 
   async Shutdown();
+
+  async BatchedKeyStatusChanged(nsCString aSessionId,
+                                GMPKeyInformation[] aKeyInfos);
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/gmp-api/gmp-decryption.h
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -97,16 +97,30 @@ enum GMPMediaKeyStatus {
   kGMPOutputRestricted = 3,
   kGMPInternalError = 4,
   kGMPUnknown = 5, // Removes key from MediaKeyStatusMap
   kGMPReleased = 6,
   kGMPStatusPending = 7,
   kGMPMediaKeyStatusInvalid = 8 // Must always be last.
 };
 
+struct GMPMediaKeyInfo {
+  GMPMediaKeyInfo() {}
+  GMPMediaKeyInfo(const uint8_t* aKeyId,
+                  uint32_t aKeyIdSize,
+                  GMPMediaKeyStatus aStatus)
+    : keyid(aKeyId)
+    , keyid_size(aKeyIdSize)
+    , status(aStatus)
+  {}
+  const uint8_t* keyid;
+  uint32_t keyid_size;
+  GMPMediaKeyStatus status;
+};
+
 // Time in milliseconds, as offset from epoch, 1 Jan 1970.
 typedef int64_t GMPTimestamp;
 
 // Callbacks to be called from the CDM. Threadsafe.
 class GMPDecryptorCallback {
 public:
 
   // The GMPDecryptor should call this in response to a call to
@@ -187,16 +201,22 @@ public:
                                 GMPMediaKeyStatus aStatus) = 0;
 
   // DEPRECATED; this function has no affect.
   virtual void SetCapabilities(uint64_t aCaps) = 0;
 
   // Returns decrypted buffer to Gecko, or reports failure.
   virtual void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) = 0;
 
+  // To aggregate KeyStatusChanged into single callback per session id.
+  virtual void BatchedKeyStatusChanged(const char* aSessionId,
+                                       uint32_t aSessionIdLength,
+                                       const GMPMediaKeyInfo* aKeyInfos,
+                                       uint32_t aKeyInfosLength) = 0;
+
   virtual ~GMPDecryptorCallback() {}
 };
 
 // Host interface, passed to GetAPIFunc(), with "decrypt".
 class GMPDecryptorHost {
 public:
   virtual void GetSandboxVoucher(const uint8_t** aVoucher,
                                  uint32_t* aVoucherLength) = 0;
--- a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -404,23 +404,25 @@ WidevineDecryptor::OnSessionKeysChange(c
                                        const KeyInformation* aKeysInfo,
                                        uint32_t aKeysInfoCount)
 {
   if (!mCallback) {
     Log("Decryptor::OnSessionKeysChange() FAIL; !mCallback");
     return;
   }
   Log("Decryptor::OnSessionKeysChange()");
+
+  nsTArray<GMPMediaKeyInfo> key_infos;
   for (uint32_t i = 0; i < aKeysInfoCount; i++) {
-    mCallback->KeyStatusChanged(aSessionId,
-                                aSessionIdSize,
-                                aKeysInfo[i].key_id,
-                                aKeysInfo[i].key_id_size,
-                                ToGMPKeyStatus(aKeysInfo[i].status));
+    key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id,
+                                            aKeysInfo[i].key_id_size,
+                                            ToGMPKeyStatus(aKeysInfo[i].status)));
   }
+  mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize,
+                                     key_infos.Elements(), key_infos.Length());
 }
 
 static GMPTimestamp
 ToGMPTime(Time aCDMTime)
 {
   return static_cast<GMPTimestamp>(aCDMTime * 1000);
 }
 
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -1383,26 +1383,23 @@ class GMPStorageTest : public GMPDecrypt
                      const nsCString& aSessionId) override { }
   void ExpirationChange(const nsCString& aSessionId,
                         UnixTime aExpiryTime) override {}
   void SessionClosed(const nsCString& aSessionId) override {}
   void SessionError(const nsCString& aSessionId,
                     nsresult aException,
                     uint32_t aSystemCode,
                     const nsCString& aMessage) override {}
-  void KeyStatusChanged(const nsCString& aSessionId,
-                        const nsTArray<uint8_t>& aKeyId,
-                        mozilla::dom::MediaKeyStatus aStatus) override { }
-
-  void ForgetKeyStatus(const nsCString& aSessionId,
-                       const nsTArray<uint8_t>& aKeyId) override { }
-
   void Decrypted(uint32_t aId,
                  mozilla::DecryptStatus aResult,
                  const nsTArray<uint8_t>& aDecryptedData) override { }
+
+  void BatchedKeyStatusChanged(const nsCString& aSessionId,
+                               const nsTArray<CDMKeyInfo>& aKeyInfos) override { }
+
   void Terminated() override {
     if (mDecryptor) {
       mDecryptor->Close();
       mDecryptor = nullptr;
     }
   }
 
 private:
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -1753,26 +1753,36 @@ TrackBuffersManager::RemoveFrames(const 
   // and part of the current coded frame group. This is allows to handle step
   // 14 of the coded frame processing algorithm without having to check the value
   // of highest end timestamp:
   // "Remove existing coded frames in track buffer:
   //  If highest end timestamp for track buffer is not set:
   //   Remove all coded frames from track buffer that have a presentation timestamp greater than or equal to presentation timestamp and less than frame end timestamp.
   //  If highest end timestamp for track buffer is set and less than or equal to presentation timestamp:
   //   Remove all coded frames from track buffer that have a presentation timestamp greater than or equal to highest end timestamp and less than frame end timestamp"
+  TimeUnit intervalsEnd = aIntervals.GetEnd();
+  bool mayBreakLoop = false;
   for (uint32_t i = aStartIndex; i < data.Length(); i++) {
     const RefPtr<MediaRawData> sample = data[i];
     TimeInterval sampleInterval =
       TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
                    TimeUnit::FromMicroseconds(sample->GetEndTime()));
     if (aIntervals.Contains(sampleInterval)) {
       if (firstRemovedIndex.isNothing()) {
         firstRemovedIndex = Some(i);
       }
       lastRemovedIndex = i;
+      mayBreakLoop = false;
+      continue;
+    }
+    if (sample->mKeyframe && mayBreakLoop) {
+      break;
+    }
+    if (sampleInterval.mStart > intervalsEnd) {
+      mayBreakLoop = true;
     }
   }
 
   if (firstRemovedIndex.isNothing()) {
     return 0;
   }
 
   // Remove decoding dependencies of the coded frames removed in the previous step:
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -293,16 +293,20 @@ MediaEngineWebRTCMicrophoneSource::Updat
   switch (mState) {
     case kReleased:
       MOZ_ASSERT(aHandle);
       if (sChannelsOpen == 0) {
         if (!InitEngine()) {
           LOG(("Audio engine is not initalized"));
           return NS_ERROR_FAILURE;
         }
+      } else {
+        // Until we fix (or wallpaper) support for multiple mic input
+        // (Bug 1238038) fail allocation for a second device
+        return NS_ERROR_FAILURE;
       }
       if (!AllocChannel()) {
         LOG(("Audio device is not initalized"));
         return NS_ERROR_FAILURE;
       }
       if (mAudioInput->SetRecordingDevice(mCapIndex)) {
         FreeChannel();
         return NS_ERROR_FAILURE;
--- a/dom/push/PushCrypto.jsm
+++ b/dom/push/PushCrypto.jsm
@@ -2,20 +2,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
 const Cu = Components.utils;
 
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyGetter(this, 'gDOMBundle', () =>
+  Services.strings.createBundle('chrome://global/locale/dom/dom.properties'));
+
 Cu.importGlobalProperties(['crypto']);
 
-this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray',
-                         'getCryptoParams'];
+this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray'];
 
 var UTF8 = new TextEncoder('utf-8');
 
 // Legacy encryption scheme (draft-thomson-http-encryption-02).
 var AESGCM128_ENCODING = 'aesgcm128';
 var AESGCM128_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128');
 
 // New encryption scheme (draft-ietf-httpbis-encryption-encoding-01).
@@ -25,16 +30,69 @@ var AESGCM_ENCRYPT_INFO = UTF8.encode('C
 var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');
 var AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
 var P256DH_INFO = UTF8.encode('P-256\0');
 var ECDH_KEY = { name: 'ECDH', namedCurve: 'P-256' };
 var ECDSA_KEY =  { name: 'ECDSA', namedCurve: 'P-256' };
 // A default keyid with a name that won't conflict with a real keyid.
 var DEFAULT_KEYID = '';
 
+/** Localized error property names. */
+
+// `Encryption` header missing or malformed.
+const BAD_ENCRYPTION_HEADER = 'PushMessageBadEncryptionHeader';
+// `Crypto-Key` or legacy `Encryption-Key` header missing.
+const BAD_CRYPTO_KEY_HEADER = 'PushMessageBadCryptoKeyHeader';
+const BAD_ENCRYPTION_KEY_HEADER = 'PushMessageBadEncryptionKeyHeader';
+// `Content-Encoding` header missing or contains unsupported encoding.
+const BAD_ENCODING_HEADER = 'PushMessageBadEncodingHeader';
+// `dh` parameter of `Crypto-Key` header missing or not base64url-encoded.
+const BAD_DH_PARAM = 'PushMessageBadSenderKey';
+// `salt` parameter of `Encryption` header missing or not base64url-encoded.
+const BAD_SALT_PARAM = 'PushMessageBadSalt';
+// `rs` parameter of `Encryption` header not a number or less than pad size.
+const BAD_RS_PARAM = 'PushMessageBadRecordSize';
+// Invalid or insufficient padding for encrypted chunk.
+const BAD_PADDING = 'PushMessageBadPaddingError';
+// Generic crypto error.
+const BAD_CRYPTO = 'PushMessageBadCryptoError';
+
+class CryptoError extends Error {
+  /**
+   * Creates an error object indicating an incoming push message could not be
+   * decrypted.
+   *
+   * @param {String} message A human-readable error message. This is only for
+   * internal module logging, and doesn't need to be localized.
+   * @param {String} property The localized property name from `dom.properties`.
+   * @param {String...} params Substitutions to insert into the localized
+   *  string.
+   */
+  constructor(message, property, ...params) {
+    super(message);
+    this.isCryptoError = true;
+    this.property = property;
+    this.params = params;
+  }
+
+  /**
+   * Formats a localized string for reporting decryption errors to the Web
+   * Console.
+   *
+   * @param {String} scope The scope of the service worker receiving the
+   *  message, prepended to any other substitutions in the string.
+   * @returns {String} The localized string.
+   */
+  format(scope) {
+    let params = [scope, ...this.params].map(String);
+    return gDOMBundle.formatStringFromName(this.property, params,
+                                           params.length);
+  }
+}
+
 function getEncryptionKeyParams(encryptKeyField) {
   if (!encryptKeyField) {
     return null;
   }
   var params = encryptKeyField.split(',');
   return params.reduce((m, p) => {
     var pmap = p.split(';').reduce(parseHeaderFieldParams, {});
     if (pmap.keyid && pmap.dh) {
@@ -43,60 +101,94 @@ function getEncryptionKeyParams(encryptK
     if (!m[DEFAULT_KEYID] && pmap.dh) {
       m[DEFAULT_KEYID] = pmap.dh;
     }
     return m;
   }, {});
 }
 
 function getEncryptionParams(encryptField) {
+  if (!encryptField) {
+    throw new CryptoError('Missing encryption header',
+                          BAD_ENCRYPTION_HEADER);
+  }
   var p = encryptField.split(',', 1)[0];
   if (!p) {
-    return null;
+    throw new CryptoError('Encryption header missing params',
+                          BAD_ENCRYPTION_HEADER);
   }
   return p.split(';').reduce(parseHeaderFieldParams, {});
 }
 
-this.getCryptoParams = function(headers) {
+function getCryptoParams(headers) {
   if (!headers) {
     return null;
   }
 
   var keymap;
   var padSize;
+  if (!headers.encoding) {
+    throw new CryptoError('Missing Content-Encoding header',
+                          BAD_ENCODING_HEADER);
+  }
   if (headers.encoding == AESGCM_ENCODING) {
     // aesgcm uses the Crypto-Key header, 2 bytes for the pad length, and an
     // authentication secret.
     // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-01
     keymap = getEncryptionKeyParams(headers.crypto_key);
+    if (!keymap) {
+      throw new CryptoError('Missing Crypto-Key header',
+                            BAD_CRYPTO_KEY_HEADER);
+    }
     padSize = 2;
   } else if (headers.encoding == AESGCM128_ENCODING) {
     // aesgcm128 uses Encryption-Key, 1 byte for the pad length, and no secret.
     // https://tools.ietf.org/html/draft-thomson-http-encryption-02
     keymap = getEncryptionKeyParams(headers.encryption_key);
+    if (!keymap) {
+      throw new CryptoError('Missing Encryption-Key header',
+                            BAD_ENCRYPTION_KEY_HEADER);
+    }
     padSize = 1;
-  }
-  if (!keymap) {
-    return null;
+  } else {
+    throw new CryptoError('Unsupported Content-Encoding: ' + headers.encoding,
+                          BAD_ENCODING_HEADER);
   }
 
   var enc = getEncryptionParams(headers.encryption);
-  if (!enc) {
-    return null;
+  var dh = keymap[enc.keyid || DEFAULT_KEYID];
+  if (!dh) {
+    throw new CryptoError('Missing dh parameter', BAD_DH_PARAM);
   }
-  var dh = keymap[enc.keyid || DEFAULT_KEYID];
   var salt = enc.salt;
-  var rs = (enc.rs)? parseInt(enc.rs, 10) : 4096;
-
-  if (!dh || !salt || isNaN(rs) || (rs <= padSize)) {
-    return null;
+  if (!salt) {
+    throw new CryptoError('Missing salt parameter', BAD_SALT_PARAM);
+  }
+  var rs = enc.rs ? parseInt(enc.rs, 10) : 4096;
+  if (isNaN(rs)) {
+    throw new CryptoError('rs parameter must be a number', BAD_RS_PARAM);
+  }
+  if (rs <= padSize) {
+    throw new CryptoError('rs parameter must be at least ' + padSize,
+                          BAD_RS_PARAM, padSize);
   }
   return {dh, salt, rs, padSize};
 }
 
+// Decodes an unpadded, base64url-encoded string.
+function base64URLDecode(string) {
+  try {
+    return ChromeUtils.base64URLDecode(string, {
+      // draft-ietf-httpbis-encryption-encoding-01 prohibits padding.
+      padding: 'reject',
+    });
+  } catch (ex) {}
+  return null;
+}
+
 var parseHeaderFieldParams = (m, v) => {
   var i = v.indexOf('=');
   if (i >= 0) {
     // A quoted string with internal quotes is invalid for all the possible
     // values of this header field.
     m[v.substring(0, i).trim()] = v.substring(i + 1).trim()
                                    .replace(/^"(.*)"$/, '$1');
   }
@@ -145,26 +237,26 @@ function hkdf(salt, ikm) {
 }
 
 hkdf.prototype.extract = function(info, len) {
   var input = concatArray([info, new Uint8Array([1])]);
   return this.prkhPromise
     .then(prkh => prkh.hash(input))
     .then(h => {
       if (h.byteLength < len) {
-        throw new Error('Length is too long');
+        throw new CryptoError('HKDF length is too long', BAD_CRYPTO);
       }
       return h.slice(0, len);
     });
 };
 
 /* generate a 96-bit nonce for use in GCM, 48-bits of which are populated */
 function generateNonce(base, index) {
   if (index >= Math.pow(2, 48)) {
-    throw new Error('Error generating nonce - index is too large.');
+    throw new CryptoError('Nonce index is too large', BAD_CRYPTO);
   }
   var nonce = base.slice(0, 12);
   nonce = new Uint8Array(nonce);
   for (var i = 0; i < 6; ++i) {
     nonce[nonce.byteLength - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
   }
   return nonce;
 }
@@ -185,48 +277,89 @@ this.PushCrypto = {
     return crypto.subtle.generateKey(ECDH_KEY, true, ['deriveBits'])
       .then(cryptoKey =>
          Promise.all([
            crypto.subtle.exportKey('raw', cryptoKey.publicKey),
            crypto.subtle.exportKey('jwk', cryptoKey.privateKey)
          ]));
   },
 
-  decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey, aSalt, aRs,
-            aAuthenticationSecret, aPadSize) {
+  /**
+   * Decrypts a push message.
+   *
+   * @param {JsonWebKey} privateKey The ECDH private key of the subscription
+   *  receiving the message, in JWK form.
+   * @param {BufferSource} publicKey The ECDH public key of the subscription
+   *  receiving the message, in raw form.
+   * @param {BufferSource} authenticationSecret The 16-byte shared
+   *  authentication secret of the subscription receiving the message.
+   * @param {Object} headers The encryption headers passed to `getCryptoParams`.
+   * @param {BufferSource} ciphertext The encrypted message data.
+   * @returns {Promise} Resolves with a `Uint8Array` containing the decrypted
+   *  message data. Rejects with a `CryptoError` if decryption fails.
+   */
+  decrypt(privateKey, publicKey, authenticationSecret, headers, ciphertext) {
+    return Promise.resolve().then(_ => {
+      let cryptoParams = getCryptoParams(headers);
+      if (!cryptoParams) {
+        return null;
+      }
+      return this._decodeMsg(ciphertext, privateKey, publicKey,
+                             cryptoParams.dh, cryptoParams.salt,
+                             cryptoParams.rs, authenticationSecret,
+                             cryptoParams.padSize);
+    }).catch(error => {
+      if (error.isCryptoError) {
+        throw error;
+      }
+      // Web Crypto returns an unhelpful "operation failed for an
+      // operation-specific reason" error if decryption fails. We don't have
+      // context about what went wrong, so we throw a generic error instead.
+      throw new CryptoError('Bad encryption', BAD_CRYPTO);
+    });
+  },
+
+  _decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey, aSalt, aRs,
+             aAuthenticationSecret, aPadSize) {
 
     if (aData.byteLength === 0) {
       // Zero length messages will be passed as null.
-      return Promise.resolve(null);
+      return null;
     }
 
     // The last chunk of data must be less than aRs, if it is not return an
     // error.
     if (aData.byteLength % (aRs + 16) === 0) {
-      return Promise.reject(new Error('Data truncated'));
+      throw new CryptoError('Encrypted data truncated', BAD_CRYPTO);
     }
 
-    let senderKey = ChromeUtils.base64URLDecode(aSenderPublicKey, {
-      // draft-ietf-httpbis-encryption-encoding-01 prohibits padding.
-      padding: "reject",
-    });
+    let senderKey = base64URLDecode(aSenderPublicKey);
+    if (!senderKey) {
+      throw new CryptoError('dh parameter is not base64url-encoded',
+                            BAD_DH_PARAM);
+    }
+
+    let salt = base64URLDecode(aSalt);
+    if (!salt) {
+      throw new CryptoError('salt parameter is not base64url-encoded',
+                            BAD_SALT_PARAM);
+    }
 
     return Promise.all([
       crypto.subtle.importKey('raw', senderKey, ECDH_KEY,
                               false, ['deriveBits']),
       crypto.subtle.importKey('jwk', aPrivateKey, ECDH_KEY,
                               false, ['deriveBits'])
     ])
     .then(([appServerKey, subscriptionPrivateKey]) =>
           crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey },
                                    subscriptionPrivateKey, 256))
     .then(ikm => this._deriveKeyAndNonce(aPadSize,
                                          new Uint8Array(ikm),
-                                         ChromeUtils.base64URLDecode(aSalt,
-                                                    { padding: "reject" }),
+                                         salt,
                                          aPublicKey,
                                          senderKey,
                                          aAuthenticationSecret))
     .then(r =>
       // AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
       Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
         this._decodeChunk(aPadSize, slice, index, r[1], r[0]))))
     .then(r => concatArray(r));
@@ -293,29 +426,30 @@ this.PushCrypto = {
    * @param {Number} padSize The size of the padding length prepended to each
    *  chunk. For aesgcm, the padding length is expressed as a 16-bit unsigned
    *  big endian integer. For aesgcm128, the padding is an 8-bit integer.
    * @param {Uint8Array} decoded The decrypted, padded chunk.
    * @returns {Uint8Array} The chunk with padding removed.
    */
   _unpadChunk(padSize, decoded) {
     if (padSize < 1 || padSize > 2) {
-      throw new Error('Unsupported pad size');
+      throw new CryptoError('Unsupported pad size', BAD_CRYPTO);
     }
     if (decoded.length < padSize) {
-      throw new Error('Decoded array is too short!');
+      throw new CryptoError('Decoded array is too short!', BAD_PADDING,
+                            padSize);
     }
     var pad = decoded[0];
     if (padSize == 2) {
       pad = (pad << 8) | decoded[1];
     }
     if (pad > decoded.length) {
-      throw new Error ('Padding is wrong!');
+      throw new CryptoError('Padding is wrong!', BAD_PADDING, padSize);
     }
     // All padded bytes must be zero except the first one.
     for (var i = padSize; i <= pad; i++) {
       if (decoded[i] !== 0) {
-        throw new Error('Padding is wrong!');
+        throw new CryptoError('Padding is wrong!', BAD_PADDING, padSize);
       }
     }
     return decoded.slice(pad + padSize);
   },
 };
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -7,22 +7,25 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
+const {
+  PushCrypto,
+  getCryptoParams,
+  CryptoError,
+} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
 
 const CONNECTION_PROTOCOLS = (function() {
   if ('android' != AppConstants.MOZ_WIDGET_TOOLKIT) {
     const {PushServiceWebSocket} = Cu.import("resource://gre/modules/PushServiceWebSocket.jsm");
     const {PushServiceHttp2} = Cu.import("resource://gre/modules/PushServiceHttp2.jsm");
     return [PushServiceWebSocket, PushServiceHttp2];
   } else {
@@ -30,19 +33,16 @@ const CONNECTION_PROTOCOLS = (function()
     return [PushServiceAndroidGCM];
   }
 })();
 
 XPCOMUtils.defineLazyServiceGetter(this, "gPushNotifier",
                                    "@mozilla.org/push/Notifier;1",
                                    "nsIPushNotifier");
 
-XPCOMUtils.defineLazyGetter(this, "gDOMBundle", () =>
-  Services.strings.createBundle("chrome://global/locale/dom/dom.properties"));
-
 this.EXPORTED_SYMBOLS = ["PushService"];
 
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
     prefix: "PushService",
   });
@@ -747,59 +747,56 @@ this.PushService = {
    * quota for the associated push registration. If the quota is exceeded,
    * the registration and message will be dropped, and the worker will not
    * be notified.
    *
    * @param {String} keyID The push registration ID.
    * @param {String} messageID The message ID, used to report service worker
    *  delivery failures. For Web Push messages, this is the version. If empty,
    *  failures will not be reported.
+   * @param {Object} headers The encryption headers.
    * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
-   * @param {Object} cryptoParams The message encryption settings.
    * @param {Function} updateFunc A function that receives the existing
    *  registration record as its argument, and returns a new record. If the
    *  function returns `null` or `undefined`, the record will not be updated.
    *  `PushServiceWebSocket` uses this to drop incoming updates with older
    *  versions.
    * @returns {Promise} Resolves with an `nsIPushErrorReporter` ack status
    *  code, indicating whether the message was delivered successfully.
    */
-  receivedPushMessage(keyID, messageID, data, cryptoParams, updateFunc) {
+  receivedPushMessage(keyID, messageID, headers, data, updateFunc) {
     console.debug("receivedPushMessage()");
     Services.telemetry.getHistogramById("PUSH_API_NOTIFICATION_RECEIVED").add();
 
     return this._updateRecordAfterPush(keyID, updateFunc).then(record => {
-      if (!record) {
-        throw new Error("Ignoring update for key ID " + keyID);
-      }
       if (record.quotaApplies()) {
         // Update quota after the delay, at which point
         // we check for visible notifications.
         let timeoutID = setTimeout(_ =>
           {
             this._updateQuota(keyID);
             if (!this._updateQuotaTimeouts.delete(timeoutID)) {
               console.debug("receivedPushMessage: quota update timeout missing?");
             }
           }, prefs.get("quotaUpdateDelay"));
         this._updateQuotaTimeouts.add(timeoutID);
       }
-      return this._decryptAndNotifyApp(record, messageID, data, cryptoParams);
+      return this._decryptAndNotifyApp(record, messageID, headers, data);
     }).catch(error => {
       console.error("receivedPushMessage: Error notifying app", error);
       return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
     });
   },
 
   /**
    * Updates a registration record after receiving a push message.
    *
    * @param {String} keyID The push registration ID.
    * @param {Function} updateFunc The function passed to `receivedPushMessage`.
-   * @returns {Promise} Resolves with the updated record, or `null` if the
+   * @returns {Promise} Resolves with the updated record, or rejects if the
    *  record was not updated.
    */
   _updateRecordAfterPush(keyID, updateFunc) {
     return this.getByKeyID(keyID).then(record => {
       if (!record) {
         this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_KEY_NOT_FOUND);
         throw new Error("No record for key ID " + keyID);
       }
@@ -827,64 +824,44 @@ this.PushService = {
           if (newRecord.isExpired()) {
             return null;
           }
           newRecord.receivedPush(lastVisit);
           return newRecord;
         });
       });
     }).then(record => {
-      if (record) {
-        gPushNotifier.notifySubscriptionModified(record.scope,
-                                                 record.principal);
+      if (!record) {
+        throw new Error("Ignoring update for key ID " + keyID);
       }
+      gPushNotifier.notifySubscriptionModified(record.scope,
+                                               record.principal);
       return record;
     });
   },
 
   /**
-   * Decrypts a message. Will resolve with null if cryptoParams is falsy.
-   *
-   * @param {PushRecord} record The receiving registration.
-   * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
-   * @param {Object} cryptoParams The message encryption settings.
-   * @returns {Promise} Resolves with the decrypted message.
-   */
-  _decryptMessage(data, record, cryptoParams) {
-    if (!cryptoParams) {
-      return Promise.resolve(null);
-    }
-    return PushCrypto.decodeMsg(
-      data,
-      record.p256dhPrivateKey,
-      record.p256dhPublicKey,
-      cryptoParams.dh,
-      cryptoParams.salt,
-      cryptoParams.rs,
-      record.authenticationSecret,
-      cryptoParams.padSize
-    );
-  },
-
-  /**
    * Decrypts an incoming message and notifies the associated service worker.
    *
    * @param {PushRecord} record The receiving registration.
    * @param {String} messageID The message ID.
+   * @param {Object} headers The encryption headers.
    * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
-   * @param {Object} cryptoParams The message encryption settings.
    * @returns {Promise} Resolves with an ack status code.
    */
-  _decryptAndNotifyApp(record, messageID, data, cryptoParams) {
-    return this._decryptMessage(data, record, cryptoParams)
+  _decryptAndNotifyApp(record, messageID, headers, data) {
+    return PushCrypto.decrypt(record.p256dhPrivateKey, record.p256dhPublicKey,
+                              record.authenticationSecret, headers, data)
       .then(
         message => this._notifyApp(record, messageID, message),
         error => {
-          let message = gDOMBundle.formatStringFromName(
-            "PushMessageDecryptionFailure", [record.scope, String(error)], 2);
+          console.warn("decryptAndNotifyApp: Error decrypting message",
+            record.scope, messageID, error);
+
+          let message = error.format(record.scope);
           gPushNotifier.notifyError(record.scope, record.principal, message,
                                     Ci.nsIScriptError.errorFlag);
           return Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR;
         });
   },
 
   _updateQuota: function(keyID) {
     console.debug("updateQuota()");
--- a/dom/push/PushServiceAndroidGCM.jsm
+++ b/dom/push/PushServiceAndroidGCM.jsm
@@ -7,20 +7,17 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
 const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
-const {
-  PushCrypto,
-  getCryptoParams,
-} = Cu.import("resource://gre/modules/PushCrypto.jsm");
+const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 Cu.import("resource://gre/modules/Messaging.jsm"); /*global: Messaging */
 Cu.import("resource://gre/modules/Services.jsm"); /*global: Services */
 Cu.import("resource://gre/modules/Preferences.jsm"); /*global: Preferences */
 Cu.import("resource://gre/modules/Promise.jsm"); /*global: Promise */
 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global: XPCOMUtils */
 
 const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("Push");
 
@@ -102,46 +99,45 @@ this.PushServiceAndroidGCM = {
     }
     if (!data) {
       console.error("No data from Java!  Dropping message.");
       return;
     }
     data = JSON.parse(data);
     console.debug("ReceivedPushMessage with data", data);
 
-    let { message, cryptoParams } = this._messageAndCryptoParams(data);
+    let { headers, message } = this._messageAndHeaders(data);
 
-    console.debug("Delivering message to main PushService:", message, cryptoParams);
+    console.debug("Delivering message to main PushService:", message, headers);
     this._mainPushService.receivedPushMessage(
-      data.channelID, "", message, cryptoParams, (record) => {
+      data.channelID, "", headers, message, (record) => {
         // Always update the stored record.
         return record;
       });
   },
 
-  _messageAndCryptoParams(data) {
+  _messageAndHeaders(data) {
     // Default is no data (and no encryption).
     let message = null;
-    let cryptoParams = null;
+    let headers = null;
 
     if (data.message && data.enc && (data.enckey || data.cryptokey)) {
-      let headers = {
+      headers = {
         encryption_key: data.enckey,
         crypto_key: data.cryptokey,
         encryption: data.enc,
         encoding: data.con,
       };
-      cryptoParams = getCryptoParams(headers);
       // Ciphertext is (urlsafe) Base 64 encoded.
       message = ChromeUtils.base64URLDecode(data.message, {
         // The Push server may append padding.
         padding: "ignore",
       });
     }
-    return { message, cryptoParams };
+    return { headers, message };
   },
 
   _configure: function(serverURL, debug) {
     return Messaging.sendRequestForResult({
       type: "PushServiceAndroidGCM:Configure",
       endpoint: serverURL.spec,
       debug: debug,
     });
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -18,17 +18,16 @@ Cu.import("resource://gre/modules/NetUti
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 const {
   PushCrypto,
   concatArray,
-  getCryptoParams,
 } = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 this.EXPORTED_SYMBOLS = ["PushServiceHttp2"];
 
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
@@ -152,23 +151,22 @@ PushChannelListener.prototype = {
         this._mainListener &&
         this._mainListener._pushService) {
       let headers = {
         encryption_key: getHeaderField(aRequest, "Encryption-Key"),
         crypto_key: getHeaderField(aRequest, "Crypto-Key"),
         encryption: getHeaderField(aRequest, "Encryption"),
         encoding: getHeaderField(aRequest, "Content-Encoding"),
       };
-      let cryptoParams = getCryptoParams(headers);
       let msg = concatArray(this._message);
 
       this._mainListener._pushService._pushChannelOnStop(this._mainListener.uri,
                                                          this._ackUri,
-                                                         msg,
-                                                         cryptoParams);
+                                                         headers,
+                                                         msg);
     }
   }
 };
 
 function getHeaderField(aRequest, name) {
   try {
     return aRequest.getRequestHeader(name);
   } catch(e) {
@@ -779,21 +777,21 @@ this.PushServiceHttp2 = {
   },
 
   removeListenerPendingRetry: function(aListener) {
     if (!this._listenersPendingRetry.remove(aListener)) {
       console.debug("removeListenerPendingRetry: listener not in list?");
     }
   },
 
-  _pushChannelOnStop: function(aUri, aAckUri, aMessage, cryptoParams) {
+  _pushChannelOnStop: function(aUri, aAckUri, aHeaders, aMessage) {
     console.debug("pushChannelOnStop()");
 
     this._mainPushService.receivedPushMessage(
-      aUri, "", aMessage, cryptoParams, record => {
+      aUri, "", aHeaders, aMessage, record => {
         // Always update the stored record.
         return record;
       }
     )
     .then(_ => this._ackMsgRecv(aAckUri))
     .catch(err => {
       console.error("pushChannelOnStop: Error receiving message",
         err);
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -14,20 +14,17 @@ Cu.import("resource://gre/modules/AppCon
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
 const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
-const {
-  PushCrypto,
-  getCryptoParams,
-} = Cu.import("resource://gre/modules/PushCrypto.jsm");
+const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 const kPUSHWSDB_DB_NAME = "pushapi";
 const kPUSHWSDB_DB_VERSION = 5; // Change this if the IndexedDB format changes
 const kPUSHWSDB_STORE_NAME = "pushapi";
 
 // WebSocket close code sent by the server to indicate that the client should
 // not automatically reconnect.
 const kBACKOFF_WS_STATUS_CODE = 4774;
@@ -684,32 +681,27 @@ this.PushServiceWebSocket = {
       promise = this._mainPushService.receivedPushMessage(
         update.channelID,
         update.version,
         null,
         null,
         updateRecord
       );
     } else {
-      let params = getCryptoParams(update.headers);
-      if (params) {
-        let message = ChromeUtils.base64URLDecode(update.data, {
-          // The Push server may append padding.
-          padding: "ignore",
-        });
-        promise = this._mainPushService.receivedPushMessage(
-          update.channelID,
-          update.version,
-          message,
-          params,
-          updateRecord
-        );
-      } else {
-        promise = Promise.reject(new Error("Invalid crypto headers"));
-      }
+      let message = ChromeUtils.base64URLDecode(update.data, {
+        // The Push server may append padding.
+        padding: "ignore",
+      });
+      promise = this._mainPushService.receivedPushMessage(
+        update.channelID,
+        update.version,
+        update.headers,
+        message,
+        updateRecord
+      );
     }
     promise.then(status => {
       this._sendAck(update.channelID, update.version, status);
     }, err => {
       console.error("handleDataUpdate: Error delivering message", update, err);
       this._sendAck(update.channelID, update.version,
         Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR);
     }).catch(err => {
--- a/dom/push/test/xpcshell/test_crypto.js
+++ b/dom/push/test/xpcshell/test_crypto.js
@@ -5,19 +5,18 @@ const {
   PushCrypto,
 } = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function* test_crypto_getCryptoParams() {
-  let testData = [
   // These headers should parse correctly.
-  {
+  let shouldParse = [{
     desc: 'aesgcm with multiple keys',
     headers: {
       encoding: 'aesgcm',
       crypto_key: 'keyid=p256dh;dh=Iy1Je2Kv11A,p256ecdsa=o2M8QfiEKuI',
       encryption: 'keyid=p256dh;salt=upk1yFkp1xI',
     },
     params: {
       dh: 'Iy1Je2Kv11A',
@@ -72,63 +71,59 @@ add_task(function* test_crypto_getCrypto
       encryption: 'keyid=v2; salt=khtpyXhpDKM',
     },
     params: {
       dh: 'VA6wmY1IpiE',
       salt: 'khtpyXhpDKM',
       rs: 4096,
       padSize: 1,
     }
-  },
+  }];
+  for (let test of shouldParse) {
+    let params = getCryptoParams(test.headers);
+    deepEqual(params, test.params, test.desc);
+  }
 
   // These headers should be rejected.
-  {
+  let shouldThrow = [{
     desc: 'aesgcm128 with Crypto-Key',
     headers: {
       encoding: 'aesgcm128',
       crypto_key: 'keyid=v2; dh=VA6wmY1IpiE',
       encryption: 'keyid=v2; salt=F0Im7RtGgNY',
     },
-    params: null,
-  },
-  {
+  }, {
     desc: 'Invalid encoding',
     headers: {
       encoding: 'nonexistent',
     },
-    params: null,
   }, {
     desc: 'Invalid record size',
     headers: {
       encoding: 'aesgcm',
       crypto_key: 'dh=pbmv1QkcEDY',
       encryption: 'dh=Esao8aTBfIk;rs=bad',
     },
-    params: null,
   }, {
     desc: 'Insufficiently large record size',
     headers: {
       encoding: 'aesgcm',
       crypto_key: 'dh=fK0EXaw5IU8',
       encryption: 'salt=orbLLmlbJfM;rs=1',
     },
-    params: null,
   }, {
     desc: 'aesgcm with Encryption-Key',
     headers: {
       encoding: 'aesgcm',
       encryption_key: 'dh=FplK5KkvUF0',
       encryption: 'salt=p6YHhFF3BQY',
     },
-    params: null,
   }];
-
-  for (let test of testData) {
-    let params = getCryptoParams(test.headers);
-    deepEqual(params, test.params, test.desc);
+  for (let test of shouldThrow) {
+    throws(() => getCryptoParams(test.headers), test.desc);
   }
 });
 
 add_task(function* test_crypto_decodeMsg() {
   let privateKey = {
     crv: 'P-256',
     d: '4h23G_KkXC9TvBSK2v0Q7ImpS2YAuRd8hQyN0rFAwBg',
     ext: true,
@@ -140,106 +135,115 @@ add_task(function* test_crypto_decodeMsg
   let publicKey = ChromeUtils.base64URLDecode('BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs', {
     padding: "reject",
   });
 
   let expectedSuccesses = [{
     desc: 'padSize = 2, rs = 24, pad = 0',
     result: 'Some message',
     data: 'Oo34w2F9VVnTMFfKtdx48AZWQ9Li9M6DauWJVgXU',
-    senderPublicKey: 'BCHFVrflyxibGLlgztLwKelsRZp4gqX3tNfAKFaxAcBhpvYeN1yIUMrxaDKiLh4LNKPtj0BOXGdr-IQ-QP82Wjo',
-    salt: 'zCU18Rw3A5aB_Xi-vfixmA',
-    rs: 24,
     authSecret: 'aTDc6JebzR6eScy2oLo4RQ',
-    padSize: 2,
+    headers: {
+      crypto_key: 'dh=BCHFVrflyxibGLlgztLwKelsRZp4gqX3tNfAKFaxAcBhpvYeN1yIUMrxaDKiLh4LNKPtj0BOXGdr-IQ-QP82Wjo',
+      encryption: 'salt=zCU18Rw3A5aB_Xi-vfixmA; rs=24',
+      encoding: 'aesgcm',
+    },
   }, {
     desc: 'padSize = 2, rs = 8, pad = 16',
     result: 'Yet another message',
     data: 'uEC5B_tR-fuQ3delQcrzrDCp40W6ipMZjGZ78USDJ5sMj-6bAOVG3AK6JqFl9E6AoWiBYYvMZfwThVxmDnw6RHtVeLKFM5DWgl1EwkOohwH2EhiDD0gM3io-d79WKzOPZE9rDWUSv64JstImSfX_ADQfABrvbZkeaWxh53EG59QMOElFJqHue4dMURpsMXg',
-    senderPublicKey: 'BEaA4gzA3i0JDuirGhiLgymS4hfFX7TNTdEhSk_HBlLpkjgCpjPL5c-GL9uBGIfa_fhGNKKFhXz1k9Kyens2ZpQ',
-    salt: 'ZFhzj0S-n29g9P2p4-I7tA',
-    rs: 8,
     authSecret: '6plwZnSpVUbF7APDXus3UQ',
-    padSize: 2,
+    headers: {
+      crypto_key: 'dh=BEaA4gzA3i0JDuirGhiLgymS4hfFX7TNTdEhSk_HBlLpkjgCpjPL5c-GL9uBGIfa_fhGNKKFhXz1k9Kyens2ZpQ',
+      encryption: 'salt=ZFhzj0S-n29g9P2p4-I7tA; rs=8',
+      encoding: 'aesgcm',
+    },
   }, {
     desc: 'padSize = 1, rs = 4096, pad = 2',
     result: 'aesgcm128 encrypted message',
     data: 'ljBJ44NPzJFH9EuyT5xWMU4vpZ90MdAqaq1TC1kOLRoPNHtNFXeJ0GtuSaE',
-    senderPublicKey: 'BOmnfg02vNd6RZ7kXWWrCGFF92bI-rQ-bV0Pku3-KmlHwbGv4ejWqgasEdLGle5Rhmp6SKJunZw2l2HxKvrIjfI',
-    salt: 'btxxUtclbmgcc30b9rT3Bg',
-    rs: 4096,
-    padSize: 1,
+    headers: {
+      encryption_key: 'dh=BOmnfg02vNd6RZ7kXWWrCGFF92bI-rQ-bV0Pku3-KmlHwbGv4ejWqgasEdLGle5Rhmp6SKJunZw2l2HxKvrIjfI',
+      encryption: 'salt=btxxUtclbmgcc30b9rT3Bg; rs=4096',
+      encoding: 'aesgcm128',
+    },
   }, {
     desc: 'padSize = 2, rs = 3, pad = 0',
     result: 'Small record size',
     data: 'oY4e5eDatDVt2fpQylxbPJM-3vrfhDasfPc8Q1PWt4tPfMVbz_sDNL_cvr0DXXkdFzS1lxsJsj550USx4MMl01ihjImXCjrw9R5xFgFrCAqJD3GwXA1vzS4T5yvGVbUp3SndMDdT1OCcEofTn7VC6xZ-zP8rzSQfDCBBxmPU7OISzr8Z4HyzFCGJeBfqiZ7yUfNlKF1x5UaZ4X6iU_TXx5KlQy_toV1dXZ2eEAMHJUcSdArvB6zRpFdEIxdcHcJyo1BIYgAYTDdAIy__IJVCPY_b2CE5W_6ohlYKB7xDyH8giNuWWXAgBozUfScLUVjPC38yJTpAUi6w6pXgXUWffende5FreQpnMFL1L4G-38wsI_-ISIOzdO8QIrXHxmtc1S5xzYu8bMqSgCinvCEwdeGFCmighRjj8t1zRWo0D14rHbQLPR_b1P5SvEeJTtS9Nm3iibM',
-    senderPublicKey: 'BCg6ZIGuE2ZNm2ti6Arf4CDVD_8--aLXAGLYhpghwjl1xxVjTLLpb7zihuEOGGbyt8Qj0_fYHBP4ObxwJNl56bk',
-    salt: '5LIDBXbvkBvvb7ZdD-T4PQ',
-    rs: 3,
     authSecret: 'g2rWVHUCpUxgcL9Tz7vyeQ',
-    padSize: 2,
+    headers: {
+      crypto_key: 'dh=BCg6ZIGuE2ZNm2ti6Arf4CDVD_8--aLXAGLYhpghwjl1xxVjTLLpb7zihuEOGGbyt8Qj0_fYHBP4ObxwJNl56bk',
+      encryption: 'salt=5LIDBXbvkBvvb7ZdD-T4PQ; rs=3',
+      encoding: 'aesgcm',
+    },
   }];
   for (let test of expectedSuccesses) {
     let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, {
       padding: "reject",
     }) : null;
     let data = ChromeUtils.base64URLDecode(test.data, {
       padding: "reject",
     });
-    let result = yield PushCrypto.decodeMsg(data,
-                                            privateKey, publicKey,
-                                            test.senderPublicKey, test.salt,
-                                            test.rs, authSecret, test.padSize);
+    let result = yield PushCrypto.decrypt(privateKey, publicKey, authSecret,
+                                          test.headers, data);
     let decoder = new TextDecoder('utf-8');
     equal(decoder.decode(new Uint8Array(result)), test.result, test.desc);
   }
 
   let expectedFailures = [{
     desc: 'padSize = 1, rs = 4096, auth secret, pad = 8',
     data: 'h0FmyldY8aT5EQ6CJrbfRn_IdDvytoLeHb9_q5CjtdFRfgDRknxLmOzavLaVG4oOiS0r',
-    senderPublicKey: 'BCXHk7O8CE-9AOp6xx7g7c-NCaNpns1PyyHpdcmDaijLbT6IdGq0ezGatBwtFc34BBfscFxdk4Tjksa2Mx5rRCM',
-    salt: 'aGBpoKklLtrLcAUCcCr7JQ',
-    rs: 4096,
+    senderPublicKey: '',
     authSecret: 'Sxb6u0gJIhGEogyLawjmCw',
-    padSize: 1,
+    headers: {
+      crypto_key: 'dh=BCXHk7O8CE-9AOp6xx7g7c-NCaNpns1PyyHpdcmDaijLbT6IdGq0ezGatBwtFc34BBfscFxdk4Tjksa2Mx5rRCM',
+      encryption: 'salt=aGBpoKklLtrLcAUCcCr7JQ',
+      encoding: 'aesgcm128',
+    },
   }, {
     desc: 'Missing padding',
     data: 'anvsHj7oBQTPMhv7XSJEsvyMS4-8EtbC7HgFZsKaTg',
-    senderPublicKey: 'BMSqfc3ohqw2DDgu3nsMESagYGWubswQPGxrW1bAbYKD18dIHQBUmD3ul_lu7MyQiT5gNdzn5JTXQvCcpf-oZE4',
-    salt: 'Czx2i18rar8XWOXAVDnUuw',
-    rs: 4096,
-    padSize: 1,
+    headers: {
+      crypto_key: 'dh=BMSqfc3ohqw2DDgu3nsMESagYGWubswQPGxrW1bAbYKD18dIHQBUmD3ul_lu7MyQiT5gNdzn5JTXQvCcpf-oZE4',
+      encryption: 'salt=Czx2i18rar8XWOXAVDnUuw',
+      encoding: 'aesgcm128',
+    },
   }, {
     desc: 'padSize > rs',
     data: 'Ct_h1g7O55e6GvuhmpjLsGnv8Rmwvxgw8iDESNKGxk_8E99iHKDzdV8wJPyHA-6b2E6kzuVa5UWiQ7s4Zms1xzJ4FKgoxvBObXkc_r_d4mnb-j245z3AcvRmcYGk5_HZ0ci26SfhAN3lCgxGzTHS4nuHBRkGwOb4Tj4SFyBRlLoTh2jyVK2jYugNjH9tTrGOBg7lP5lajLTQlxOi91-RYZSfFhsLX3LrAkXuRoN7G1CdiI7Y3_eTgbPIPabDcLCnGzmFBTvoJSaQF17huMl_UnWoCj2WovA4BwK_TvWSbdgElNnQ4CbArJ1h9OqhDOphVu5GUGr94iitXRQR-fqKPMad0ULLjKQWZOnjuIdV1RYEZ873r62Yyd31HoveJcSDb1T8l_QK2zVF8V4k0xmK9hGuC0rF5YJPYPHgl5__usknzxMBnRrfV5_MOL5uPZwUEFsu',
-    senderPublicKey: 'BAcMdWLJRGx-kPpeFtwqR3GE1LWzd1TYh2rg6CEFu53O-y3DNLkNe_BtGtKRR4f7ZqpBMVS6NgfE2NwNPm3Ndls',
-    salt: 'NQVTKhB0rpL7ZzKkotTGlA',
-    rs: 1,
-    authSecret: '6plwZnSpVUbF7APDXus3UQ',
-    padSize: 2,
+    headers: {
+      crypto_key: 'dh=BAcMdWLJRGx-kPpeFtwqR3GE1LWzd1TYh2rg6CEFu53O-y3DNLkNe_BtGtKRR4f7ZqpBMVS6NgfE2NwNPm3Ndls',
+      encryption: 'salt=NQVTKhB0rpL7ZzKkotTGlA; rs=1',
+      encoding: 'aesgcm',
+    },
   }, {
     desc: 'Encrypted with padSize = 1, decrypted with padSize = 2 and auth secret',
     data: 'fwkuwTTChcLnrzsbDI78Y2EoQzfnbMI8Ax9Z27_rwX8',
-    senderPublicKey: 'BCHn-I-J3dfPRLJBlNZ3xFoAqaBLZ6qdhpaz9W7Q00JW1oD-hTxyEECn6KYJNK8AxKUyIDwn6Icx_PYWJiEYjQ0',
-    salt: 'c6JQl9eJ0VvwrUVCQDxY7Q',
-    rs: 4096,
     authSecret: 'BhbpNTWyO5wVJmVKTV6XaA',
-    padSize: 2,
+    headers: {
+      crypto_key: 'dh=BCHn-I-J3dfPRLJBlNZ3xFoAqaBLZ6qdhpaz9W7Q00JW1oD-hTxyEECn6KYJNK8AxKUyIDwn6Icx_PYWJiEYjQ0',
+      encryption: 'salt=c6JQl9eJ0VvwrUVCQDxY7Q',
+      encoding: 'aesgcm',
+    },
   }, {
     desc: 'Truncated input',
     data: 'AlDjj6NvT5HGyrHbT8M5D6XBFSra6xrWS9B2ROaCIjwSu3RyZ1iyuv0',
-    rs: 25,
+    headers: {
+      crypto_key: 'dh=BCHn-I-J3dfPRLJBlNZ3xFoAqaBLZ6qdhpaz9W7Q00JW1oD-hTxyEECn6KYJNK8AxKUyIDwn6Icx_PYWJiEYjQ0',
+      encryption: 'salt=c6JQl9eJ0VvwrUVCQDxY7Q; rs=25',
+      encoding: 'aesgcm',
+    },
   }];
   for (let test of expectedFailures) {
     let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, {
       padding: "reject",
     }) : null;
     let data = ChromeUtils.base64URLDecode(test.data, {
       padding: "reject",
     });
     yield rejects(
-      PushCrypto.decodeMsg(data, privateKey, publicKey,
-                           test.senderPublicKey, test.salt, test.rs,
-                           authSecret, test.padSize),
+      PushCrypto.decrypt(privateKey, publicKey, authSecret,
+                         test.headers, data),
       test.desc
     );
   }
 });
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -191,17 +191,17 @@ partial interface HTMLInputElement {
   // to update this list if nsIDOMNSEditableElement changes.
 
   [Pure, ChromeOnly]
   readonly attribute nsIEditor? editor;
 
   // This is similar to set .value on nsIDOMInput/TextAreaElements, but handling
   // of the value change is closer to the normal user input, so 'change' event
   // for example will be dispatched when focusing out the element.
-  [ChromeOnly]
+  [Func="IsChromeOrXBL", NeedsSubjectPrincipal]
   void setUserInput(DOMString input);
 };
 
 partial interface HTMLInputElement {
   [Pref="dom.input.dirpicker", SetterThrows]
   attribute boolean allowdirs;
 
   [Pref="dom.input.dirpicker"]
@@ -229,8 +229,33 @@ HTMLInputElement implements MozPhonetic;
 // Webkit/Blink
 partial interface HTMLInputElement {
   [Pref="dom.webkitBlink.filesystem.enabled", Frozen, Cached, Pure]
   readonly attribute sequence<FileSystemEntry> webkitEntries;
 
   [Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows]
           attribute boolean webkitdirectory;
 };
+
+dictionary DateTimeValue {
+  long hour;
+  long minute;
+};
+
+partial interface HTMLInputElement {
+  [Pref="dom.forms.datetime", ChromeOnly]
+  DateTimeValue getDateTimeInputBoxValue();
+
+  [Pref="dom.forms.datetime", ChromeOnly]
+  void updateDateTimeInputBox(optional DateTimeValue value);
+
+  [Pref="dom.forms.datetime", ChromeOnly]
+  void setDateTimePickerState(boolean open);
+
+  [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
+  void openDateTimePicker(optional DateTimeValue initialValue);
+
+  [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
+  void updateDateTimePicker(optional DateTimeValue value);
+
+  [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
+  void closeDateTimePicker();
+};
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -856,17 +856,18 @@ nsXULElement::BindToTree(nsIDocument* aD
       // an <audio> or <video> element. This assertion is here to make sure
       // that we don't fail to notice if a change to bindings causes us to
       // start pulling in xul.css much more frequently. If this assertion
       // fails then we need to figure out why, and how we can continue to avoid
       // pulling in xul.css.
       // Note that add-ons may introduce bindings that cause this assertion to
       // fire.
       NS_ASSERTION(IsInVideoControls(this) ||
-                   IsInFeedSubscribeLine(this),
+                   IsInFeedSubscribeLine(this) ||
+                   IsXULElement(nsGkAtoms::datetimebox),
                    "Unexpected XUL element in non-XUL doc");
     }
   }
 
   if (aDocument) {
       NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                    "Missing a script blocker!");
       // We're in a document now.  Kick off the frame load.
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -58,24 +58,28 @@ GPUProcessManager::Shutdown()
 
 GPUProcessManager::GPUProcessManager()
  : mTaskFactory(this),
    mNextLayerTreeId(0),
    mNumProcessAttempts(0),
    mProcess(nullptr),
    mGPUChild(nullptr)
 {
+  MOZ_COUNT_CTOR(GPUProcessManager);
+
   mObserver = new Observer(this);
   nsContentUtils::RegisterShutdownObserver(mObserver);
 
   LayerTreeOwnerTracker::Initialize();
 }
 
 GPUProcessManager::~GPUProcessManager()
 {
+  MOZ_COUNT_DTOR(GPUProcessManager);
+
   LayerTreeOwnerTracker::Shutdown();
 
   // The GPU process should have already been shut down.
   MOZ_ASSERT(!mProcess && !mGPUChild);
 
   // We should have already removed observers.
   MOZ_ASSERT(!mObserver);
 }
@@ -367,24 +371,18 @@ GPUProcessManager::NotifyRemoteActorDest
   // can happen if the ActorDestroy for a bridged protocol fires
   // before the ActorDestroy for PGPUChild.
   OnProcessUnexpectedShutdown(mProcess);
 }
 
 void
 GPUProcessManager::CleanShutdown()
 {
-  if (!mProcess) {
-    return;
-  }
-
-#ifdef NS_FREE_PERMANENT_DATA
-  mVsyncBridge->Close();
-#endif
   DestroyProcess();
+  mVsyncIOThread = nullptr;
 }
 
 void
 GPUProcessManager::DestroyProcess()
 {
   if (!mProcess) {
     return;
   }
--- a/gfx/ipc/VsyncIOThreadHolder.cpp
+++ b/gfx/ipc/VsyncIOThreadHolder.cpp
@@ -6,25 +6,32 @@
 
 #include "VsyncIOThreadHolder.h"
 
 namespace mozilla {
 namespace gfx {
 
 VsyncIOThreadHolder::VsyncIOThreadHolder()
 {
+  MOZ_COUNT_CTOR(VsyncIOThreadHolder);
 }
 
 VsyncIOThreadHolder::~VsyncIOThreadHolder()
 {
+  MOZ_COUNT_DTOR(VsyncIOThreadHolder);
+
   if (!mThread) {
     return;
   }
 
-  NS_DispatchToMainThread(NewRunnableMethod(mThread, &nsIThread::AsyncShutdown));
+  if (NS_IsMainThread()) {
+    mThread->AsyncShutdown();
+  } else {
+    NS_DispatchToMainThread(NewRunnableMethod(mThread, &nsIThread::AsyncShutdown));
+  }
 }
 
 bool
 VsyncIOThreadHolder::Start()
 {
   nsresult rv = NS_NewNamedThread("VsyncIOThread", getter_AddRefs(mThread));
   return NS_SUCCEEDED(rv);
 }
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -726,17 +726,20 @@ BufferTextureHost::PrepareTextureSource(
   if (mFirstSource && mFirstSource->IsOwnedBy(this)) {
     // We are already attached to a TextureSource, nothing to do except tell
     // the compositable to use it.
     aTexture = mFirstSource.get();
     return;
   }
 
   // We don't own it, apparently.
-  mFirstSource = nullptr;
+  if (mFirstSource) {
+    mNeedsFullUpdate = true;
+    mFirstSource = nullptr;
+  }
 
   DataTextureSource* texture = aTexture.get() ? aTexture->AsDataTextureSource() : nullptr;
 
   bool compatibleFormats = texture && IsCompatibleTextureSource(texture,
                                                                 mDescriptor,
                                                                 mCompositor);
 
   bool shouldCreateTexture = !compatibleFormats
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -3649,18 +3649,22 @@ nsCSSFrameConstructor::FindInputData(Ele
     SIMPLE_INT_CREATE(NS_FORM_INPUT_PASSWORD, NS_NewTextControlFrame),
     { NS_FORM_INPUT_COLOR,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewColorControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
     // TODO: this is temporary until a frame is written: bug 635240.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame),
     // TODO: this is temporary until a frame is written: bug 888320.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
-    // TODO: this is temporary until a frame is written: bug 888320
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+    // On Android/B2G, date/time input appears as a normal text box.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame),
+#else
+    SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame),
+#endif
     // TODO: this is temporary until a frame is written: bug 888320
     SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
     // TODO: this is temporary until a frame is written: bug 888320
     SIMPLE_INT_CREATE(NS_FORM_INPUT_WEEK, NS_NewTextControlFrame),
     { NS_FORM_INPUT_SUBMIT,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
     { NS_FORM_INPUT_RESET,
--- a/layout/forms/moz.build
+++ b/layout/forms/moz.build
@@ -17,16 +17,17 @@ EXPORTS += [
     'nsISelectControlFrame.h',
     'nsITextControlFrame.h',
 ]
 
 UNIFIED_SOURCES += [
     'nsButtonFrameRenderer.cpp',
     'nsColorControlFrame.cpp',
     'nsComboboxControlFrame.cpp',
+    'nsDateTimeControlFrame.cpp',
     'nsFieldSetFrame.cpp',
     'nsFileControlFrame.cpp',
     'nsFormControlFrame.cpp',
     'nsGfxButtonControlFrame.cpp',
     'nsGfxCheckboxControlFrame.cpp',
     'nsGfxRadioControlFrame.cpp',
     'nsHTMLButtonControlFrame.cpp',
     'nsImageControlFrame.cpp',
new file mode 100644
--- /dev/null
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -0,0 +1,414 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This frame type is used for input type=date, time, month, week, and
+ * datetime-local.
+ */
+
+#include "nsDateTimeControlFrame.h"
+
+#include "nsContentUtils.h"
+#include "nsFormControlFrame.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsNodeInfoManager.h"
+#include "nsIDateTimeInputArea.h"
+#include "nsIObserverService.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsIDOMMutationEvent.h"
+#include "jsapi.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIFrame*
+NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+  return new (aPresShell) nsDateTimeControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsDateTimeControlFrame)
+
+NS_QUERYFRAME_HEAD(nsDateTimeControlFrame)
+  NS_QUERYFRAME_ENTRY(nsDateTimeControlFrame)
+  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+nsDateTimeControlFrame::nsDateTimeControlFrame(nsStyleContext* aContext)
+  : nsContainerFrame(aContext)
+{
+}
+
+void
+nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+  nsContentUtils::DestroyAnonymousContent(&mInputAreaContent);
+  nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsDateTimeControlFrame::UpdateInputBoxValue()
+{
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+    do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->NotifyInputElementValueChanged();
+  }
+}
+
+void
+nsDateTimeControlFrame::SetValueFromPicker(const DateTimeValue& aValue)
+{
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+    do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    AutoJSAPI api;
+    if (!api.Init(mContent->OwnerDoc()->GetScopeObject())) {
+      return;
+    }
+
+    JSObject* wrapper = mContent->GetWrapper();
+    if (!wrapper) {
+      return;
+    }
+
+    JSObject* scope = xpc::GetXBLScope(api.cx(), wrapper);
+    AutoJSAPI jsapi;
+    if (!scope || !jsapi.Init(scope)) {
+      return;
+    }
+
+    JS::Rooted<JS::Value> jsValue(jsapi.cx());
+    if (!ToJSValue(jsapi.cx(), aValue, &jsValue)) {
+      return;
+    }
+
+    inputAreaContent->SetValueFromPicker(jsValue);
+  }
+}
+
+void
+nsDateTimeControlFrame::SetPickerState(bool aOpen)
+{
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+    do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->SetPickerState(aOpen);
+  }
+}
+
+void
+nsDateTimeControlFrame::HandleFocusEvent()
+{
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+    do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->FocusInnerTextBox();
+  }
+}
+
+void
+nsDateTimeControlFrame::HandleBlurEvent()
+{
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+    do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->BlurInnerTextBox();
+  }
+}
+
+nscoord
+nsDateTimeControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+  nscoord result;
+  DISPLAY_MIN_WIDTH(this, result);
+
+  nsIFrame* kid = mFrames.FirstChild();
+  if (kid) { // display:none?
+    result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+                                                  kid,
+                                                  nsLayoutUtils::MIN_ISIZE);
+  } else {
+    result = 0;
+  }
+
+  return result;
+}
+
+nscoord
+nsDateTimeControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+  nscoord result;
+  DISPLAY_PREF_WIDTH(this, result);
+
+  nsIFrame* kid = mFrames.FirstChild();
+  if (kid) { // display:none?
+    result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+                                                  kid,
+                                                  nsLayoutUtils::PREF_ISIZE);
+  } else {
+    result = 0;
+  }
+
+  return result;
+}
+
+void
+nsDateTimeControlFrame::Reflow(nsPresContext* aPresContext,
+                               ReflowOutput& aDesiredSize,
+                               const ReflowInput& aReflowInput,
+                               nsReflowStatus& aStatus)
+{
+  MarkInReflow();
+
+  DO_GLOBAL_REFLOW_COUNT("nsDateTimeControlFrame");
+  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+                 ("enter nsDateTimeControlFrame::Reflow: availSize=%d,%d",
+                  aReflowInput.AvailableWidth(),
+                  aReflowInput.AvailableHeight()));
+
+  NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
+
+  const WritingMode myWM = aReflowInput.GetWritingMode();
+
+  // The ISize of our content box, which is the available ISize
+  // for our anonymous content:
+  const nscoord contentBoxISize = aReflowInput.ComputedISize();
+  nscoord contentBoxBSize = aReflowInput.ComputedBSize();
+
+  // Figure out our border-box sizes as well (by adding borderPadding to
+  // content-box sizes):
+  const nscoord borderBoxISize = contentBoxISize +
+    aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
+
+  nscoord borderBoxBSize;
+  if (contentBoxBSize != NS_INTRINSICSIZE) {
+    borderBoxBSize = contentBoxBSize +
+      aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+  } // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
+
+  nsIFrame* inputAreaFrame = mFrames.FirstChild();
+  if (!inputAreaFrame) { // display:none?
+    if (contentBoxBSize == NS_INTRINSICSIZE) {
+      contentBoxBSize = 0;
+      borderBoxBSize =
+        aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+    }
+  } else {
+    NS_ASSERTION(inputAreaFrame->GetContent() == mInputAreaContent,
+                 "What is this child doing here?");
+
+    ReflowOutput childDesiredSize(aReflowInput);
+
+    WritingMode wm = inputAreaFrame->GetWritingMode();
+    LogicalSize availSize = aReflowInput.ComputedSize(wm);
+    availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
+
+    ReflowInput childReflowOuput(aPresContext, aReflowInput,
+                                 inputAreaFrame, availSize);
+
+    // Convert input area margin into my own writing-mode (in case it differs):
+    LogicalMargin childMargin =
+      childReflowOuput.ComputedLogicalMargin().ConvertTo(myWM, wm);
+
+    // offsets of input area frame within this frame:
+    LogicalPoint
+      childOffset(myWM,
+                  aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
+                  childMargin.IStart(myWM),
+                  aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
+                  childMargin.BStart(myWM));
+
+    nsReflowStatus childStatus;
+    // We initially reflow the child with a dummy containerSize; positioning
+    // will be fixed later.
+    const nsSize dummyContainerSize;
+    ReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
+                childReflowOuput, myWM, childOffset, dummyContainerSize, 0,
+                childStatus);
+    MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus),
+               "We gave our child unconstrained available block-size, "
+               "so it should be complete");
+
+    nscoord childMarginBoxBSize =
+      childDesiredSize.BSize(myWM) + childMargin.BStartEnd(myWM);
+
+    if (contentBoxBSize == NS_INTRINSICSIZE) {
+      // We are intrinsically sized -- we should shrinkwrap the input area's
+      // block-size:
+      contentBoxBSize = childMarginBoxBSize;
+
+      // Make sure we obey min/max-bsize in the case when we're doing intrinsic
+      // sizing (we get it for free when we have a non-intrinsic
+      // aReflowInput.ComputedBSize()).  Note that we do this before
+      // adjusting for borderpadding, since ComputedMaxBSize and
+      // ComputedMinBSize are content heights.
+      contentBoxBSize =
+        NS_CSS_MINMAX(contentBoxBSize,
+                      aReflowInput.ComputedMinBSize(),
+                      aReflowInput.ComputedMaxBSize());
+
+      borderBoxBSize = contentBoxBSize +
+        aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+    }
+
+    // Center child in block axis
+    nscoord extraSpace = contentBoxBSize - childMarginBoxBSize;
+    childOffset.B(myWM) += std::max(0, extraSpace / 2);
+
+    // Needed in FinishReflowChild, for logical-to-physical conversion:
+    nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize).
+                           GetPhysicalSize(myWM);
+
+    // Place the child
+    FinishReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
+                      &childReflowOuput, myWM, childOffset, borderBoxSize, 0);
+
+    nsSize contentBoxSize =
+      LogicalSize(myWM, contentBoxISize, contentBoxBSize).
+        GetPhysicalSize(myWM);
+    aDesiredSize.SetBlockStartAscent(
+      childDesiredSize.BlockStartAscent() +
+      inputAreaFrame->BStart(aReflowInput.GetWritingMode(),
+                             contentBoxSize));
+  }
+
+  LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
+  aDesiredSize.SetSize(myWM, logicalDesiredSize);
+
+  aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+  if (inputAreaFrame) {
+    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inputAreaFrame);
+  }
+
+  FinishAndStoreOverflow(&aDesiredSize);
+
+  aStatus = NS_FRAME_COMPLETE;
+
+  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+                 ("exit nsDateTimeControlFrame::Reflow: size=%d,%d",
+                  aDesiredSize.Width(), aDesiredSize.Height()));
+  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+nsresult
+nsDateTimeControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+  // Set up "datetimebox" XUL element which will be XBL-bound to the
+  // actual controls.
+  nsNodeInfoManager* nodeInfoManager =
+    mContent->GetComposedDoc()->NodeInfoManager();
+  RefPtr<NodeInfo> nodeInfo =
+    nodeInfoManager->GetNodeInfo(nsGkAtoms::datetimebox, nullptr,
+                                 kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
+  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+  NS_TrustedNewXULElement(getter_AddRefs(mInputAreaContent), nodeInfo.forget());
+  aElements.AppendElement(mInputAreaContent);
+
+  // Propogate our tabindex.
+  nsAutoString tabIndexStr;
+  if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr)) {
+    mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
+                               tabIndexStr, false);
+  }
+
+  // Propagate our readonly state.
+  nsAutoString readonly;
+  if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
+    mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly,
+                               false);
+  }
+
+  SyncDisabledState();
+
+  return NS_OK;
+}
+
+void
+nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+                                                 uint32_t aFilter)
+{
+  if (mInputAreaContent) {
+    aElements.AppendElement(mInputAreaContent);
+  }
+}
+
+void
+nsDateTimeControlFrame::SyncDisabledState()
+{
+  EventStates eventStates = mContent->AsElement()->State();
+  if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+    mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
+                               EmptyString(), true);
+  } else {
+    mInputAreaContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+  }
+}
+
+nsresult
+nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
+                                         nsIAtom* aAttribute,
+                                         int32_t aModType)
+{
+  NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
+
+  // nsGkAtoms::disabled is handled by SyncDisabledState
+  if (aNameSpaceID == kNameSpaceID_None) {
+    if (aAttribute == nsGkAtoms::value ||
+        aAttribute == nsGkAtoms::readonly ||
+        aAttribute == nsGkAtoms::tabindex) {
+      MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
+      auto contentAsInputElem = static_cast<dom::HTMLInputElement*>(mContent);
+      // If script changed the <input>'s type before setting these attributes
+      // then we don't need to do anything since we are going to be reframed.
+      if (contentAsInputElem->GetType() == NS_FORM_INPUT_TIME) {
+        if (aAttribute == nsGkAtoms::value) {
+          nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+            do_QueryInterface(mInputAreaContent);
+          if (inputAreaContent) {
+            nsContentUtils::AddScriptRunner(NewRunnableMethod(inputAreaContent,
+              &nsIDateTimeInputArea::NotifyInputElementValueChanged));
+          }
+        } else {
+          if (aModType == nsIDOMMutationEvent::REMOVAL) {
+            mInputAreaContent->UnsetAttr(aNameSpaceID, aAttribute, true);
+          } else {
+            MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
+                       aModType == nsIDOMMutationEvent::MODIFICATION);
+            nsAutoString value;
+            mContent->GetAttr(aNameSpaceID, aAttribute, value);
+            mInputAreaContent->SetAttr(aNameSpaceID, aAttribute, value, true);
+          }
+        }
+      }
+    }
+  }
+
+  return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+                                            aModType);
+}
+
+void
+nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates)
+{
+  if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
+    nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
+  }
+}
+
+nsIAtom*
+nsDateTimeControlFrame::GetType() const
+{
+  return nsGkAtoms::dateTimeControlFrame;
+}
new file mode 100644
--- /dev/null
+++ b/layout/forms/nsDateTimeControlFrame.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This frame type is used for input type=date, time, month, week, and
+ * datetime-local.
+ *
+ * NOTE: some of the above-mentioned input types are still to-be-implemented.
+ * See nsCSSFrameConstructor::FindInputData, as well as bug 1286182 (date),
+ * bug 1306215 (month), bug 1306216 (week) and bug 1306217 (datetime-local).
+ */
+
+#ifndef nsDateTimeControlFrame_h__
+#define nsDateTimeControlFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+struct DateTimeValue;
+} // namespace dom
+} // namespace mozilla
+
+class nsDateTimeControlFrame final : public nsContainerFrame,
+                                     public nsIAnonymousContentCreator
+{
+  typedef mozilla::dom::DateTimeValue DateTimeValue;
+
+  explicit nsDateTimeControlFrame(nsStyleContext* aContext);
+
+public:
+  friend nsIFrame* NS_NewDateTimeControlFrame(nsIPresShell* aPresShell,
+                                              nsStyleContext* aContext);
+
+  void ContentStatesChanged(mozilla::EventStates aStates) override;
+  void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+  NS_DECL_QUERYFRAME_TARGET(nsDateTimeControlFrame)
+  NS_DECL_QUERYFRAME
+  NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG_FRAME_DUMP
+  nsresult GetFrameName(nsAString& aResult) const override {
+    return MakeFrameName(NS_LITERAL_STRING("DateTimeControl"), aResult);
+  }
+#endif
+
+  nsIAtom* GetType() const override;
+
+  bool IsFrameOfType(uint32_t aFlags) const override
+  {
+    return nsContainerFrame::IsFrameOfType(aFlags &
+      ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+  }
+
+  // Reflow
+  nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+
+  nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
+
+  void Reflow(nsPresContext* aPresContext,
+              ReflowOutput& aDesiredSize,
+              const ReflowInput& aReflowState,
+              nsReflowStatus& aStatus) override;
+
+  // nsIAnonymousContentCreator
+  nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+  void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+                                uint32_t aFilter) override;
+
+  nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute,
+                            int32_t aModType) override;
+
+  void UpdateInputBoxValue();
+  void SetValueFromPicker(const DateTimeValue& aValue);
+  void HandleFocusEvent();
+  void HandleBlurEvent();
+  void SetPickerState(bool aOpen);
+
+private:
+  class SyncDisabledStateEvent;
+  friend class SyncDisabledStateEvent;
+  class SyncDisabledStateEvent : public mozilla::Runnable
+  {
+  public:
+    explicit SyncDisabledStateEvent(nsDateTimeControlFrame* aFrame)
+    : mFrame(aFrame)
+    {}
+
+    NS_IMETHOD Run() override
+    {
+      nsDateTimeControlFrame* frame =
+        static_cast<nsDateTimeControlFrame*>(mFrame.GetFrame());
+      NS_ENSURE_STATE(frame);
+
+      frame->SyncDisabledState();
+      return NS_OK;
+    }
+
+  private:
+    nsWeakFrame mFrame;
+  };
+
+  /**
+   * Sync the disabled state of the anonymous children up with our content's.
+   */
+  void SyncDisabledState();
+
+  // Anonymous child which is bound via XBL to an element that wraps the input
+  // area and reset button.
+  nsCOMPtr<nsIContent> mInputAreaContent;
+};
+
+#endif // nsDateTimeControlFrame_h__
--- a/layout/forms/test/mochitest.ini
+++ b/layout/forms/test/mochitest.ini
@@ -60,9 +60,11 @@ skip-if = (buildapp == 'b2g' && (toolkit
 [test_textarea_resize.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(resizing textarea not available in b2g) b2g-debug(resizing textarea not available in b2g) b2g-desktop(resizing textarea not available in b2g)
 [test_bug961363.html]
 skip-if = toolkit == 'android' # Bug 1021644 - Fails when pushed into a different chunk on Android
 [test_bug1111995.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g'
 [test_select_vertical.html]
 skip-if = e10s || buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # Bug 1170129 - vertical <select> popup not implemented for e10s # <select> elements don't use an in-page popup on B2G/Android
+[test_bug1301290.html]
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(resizing textarea not available in b2g) b2g-debug(resizing textarea not available in b2g) b2g-desktop(resizing textarea not available in b2g)
 [test_bug1305282.html]
new file mode 100644
--- /dev/null
+++ b/layout/forms/test/test_bug1301290.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Test for Bug 1301290</title>
+        <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+        <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+        <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+        <style type="text/css">
+            .blue, .green {
+                border: none;
+                box-sizing: border-box;
+                display: block;
+                width: 200px;
+                height: 100px;
+                overflow: scroll;
+                resize: both;
+            }
+
+            .blue {
+                background: blue;
+            }
+
+            .green {
+                background: green;
+                margin-top: -100px;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="blue"></div>
+        <textarea class="green" id="textarea"></textarea>
+        <script type="application/javascript">
+            SimpleTest.waitForExplicitFinish();
+            addLoadEvent(() => SimpleTest.executeSoon(function() {
+                var textarea = $("textarea");
+                var rect = textarea.getBoundingClientRect();
+
+                synthesizeMouse(textarea, rect.width - 10, rect.height - 10, { type: "mousedown" });
+                synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type: "mousemove" });
+                synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type: "mouseup" });
+
+                var newrect = textarea.getBoundingClientRect();
+                ok(newrect.width > rect.width, "width did not increase");
+                ok(newrect.height > rect.height, "height did not increase");
+                SimpleTest.finish();
+            }));
+        </script>
+    </body>
+</html>
--- a/layout/generic/nsFrameIdList.h
+++ b/layout/generic/nsFrameIdList.h
@@ -14,16 +14,17 @@ FRAME_ID(nsBulletFrame)
 FRAME_ID(nsButtonBoxFrame)
 FRAME_ID(nsCanvasFrame)
 FRAME_ID(nsColorControlFrame)
 FRAME_ID(nsColumnSetFrame)
 FRAME_ID(nsComboboxControlFrame)
 FRAME_ID(nsComboboxDisplayFrame)
 FRAME_ID(nsContainerFrame)
 FRAME_ID(nsContinuingTextFrame)
+FRAME_ID(nsDateTimeControlFrame)
 FRAME_ID(nsDeckFrame)
 FRAME_ID(nsDocElementBoxFrame)
 FRAME_ID(nsFieldSetFrame)
 FRAME_ID(nsFileControlFrame)
 FRAME_ID(nsFirstLetterFrame)
 FRAME_ID(nsFirstLineFrame)
 FRAME_ID(nsFlexContainerFrame)
 FRAME_ID(nsFormControlFrame)
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2932,17 +2932,17 @@ ScrollFrameHelper::ScrollToImpl(nsPoint 
   if (docShell) {
     docShell->NotifyScrollObservers();
   }
 }
 
 static int32_t
 MaxZIndexInList(nsDisplayList* aList, nsDisplayListBuilder* aBuilder)
 {
-  int32_t maxZIndex = 0;
+  int32_t maxZIndex = -1;
   for (nsDisplayItem* item = aList->GetBottom(); item; item = item->GetAbove()) {
     maxZIndex = std::max(maxZIndex, item->ZIndex());
   }
   return maxZIndex;
 }
 
 // Finds the max z-index of the items in aList that meet the following conditions
 //   1) have z-index auto or z-index >= 0.
@@ -2961,16 +2961,30 @@ MaxZIndexInListOfItemsContainedInFrame(n
     }
     if (nsLayoutUtils::IsProperAncestorFrame(aFrame, itemFrame)) {
       maxZIndex = std::max(maxZIndex, item->ZIndex());
     }
   }
   return maxZIndex;
 }
 
+template<class T>
+static void
+AppendInternalItemToTop(const nsDisplayListSet& aLists,
+                        T* aItem,
+                        int32_t aZIndex)
+{
+  if (aZIndex >= 0) {
+    aItem->SetOverrideZIndex(aZIndex);
+    aLists.PositionedDescendants()->AppendNewToTop(aItem);
+  } else {
+    aLists.Content()->AppendNewToTop(aItem);
+  }
+}
+
 static const uint32_t APPEND_OWN_LAYER = 0x1;
 static const uint32_t APPEND_POSITIONED = 0x2;
 static const uint32_t APPEND_SCROLLBAR_CONTAINER = 0x4;
 
 static void
 AppendToTop(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists,
             nsDisplayList* aSource, nsIFrame* aSourceFrame, uint32_t aFlags)
 {
@@ -2986,23 +3000,18 @@ AppendToTop(nsDisplayListBuilder* aBuild
   } else {
     newItem = new (aBuilder) nsDisplayWrapList(aBuilder, aSourceFrame, aSource);
   }
 
   if (aFlags & APPEND_POSITIONED) {
     // We want overlay scrollbars to always be on top of the scrolled content,
     // but we don't want them to unnecessarily cover overlapping elements from
     // outside our scroll frame.
-    nsDisplayList* positionedDescendants = aLists.PositionedDescendants();
-    if (!positionedDescendants->IsEmpty()) {
-      newItem->SetOverrideZIndex(MaxZIndexInList(positionedDescendants, aBuilder));
-      positionedDescendants->AppendNewToTop(newItem);
-    } else {
-      aLists.Outlines()->AppendNewToTop(newItem);
-    }
+    int32_t zIndex = MaxZIndexInList(aLists.PositionedDescendants(), aBuilder);
+    AppendInternalItemToTop(aLists, newItem, zIndex);
   } else {
     aLists.BorderBackground()->AppendNewToTop(newItem);
   }
 }
 
 struct HoveredStateComparator
 {
   bool Equals(nsIFrame* A, nsIFrame* B) const {
@@ -3568,27 +3577,19 @@ ScrollFrameHelper::BuildDisplayList(nsDi
         !mWillBuildScrollableLayer &&
         aBuilder->IsBuildingLayerEventRegions())
     {
       inactiveRegionItem = new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame);
       inactiveRegionItem->AddInactiveScrollPort(mScrollPort + aBuilder->ToReferenceFrame(mOuter));
     }
 
     if (inactiveRegionItem) {
-      nsDisplayList* positionedDescendants = scrolledContent.PositionedDescendants();
-      nsDisplayList* destinationList = nullptr;
-      int32_t zindex =
-        MaxZIndexInListOfItemsContainedInFrame(positionedDescendants, mOuter);
-      if (zindex >= 0) {
-        destinationList = positionedDescendants;
-        inactiveRegionItem->SetOverrideZIndex(zindex);
-      } else {
-        destinationList = scrolledContent.Outlines();
-      }
-      destinationList->AppendNewToTop(inactiveRegionItem);
+      int32_t zIndex =
+        MaxZIndexInListOfItemsContainedInFrame(scrolledContent.PositionedDescendants(), mOuter);
+      AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
     }
 
     if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
       aBuilder->AppendNewScrollInfoItemForHoisting(
         new (aBuilder) nsDisplayScrollInfoLayer(aBuilder, mScrolledFrame,
                                                 mOuter));
     }
   }
--- a/layout/generic/nsHTMLParts.h
+++ b/layout/generic/nsHTMLParts.h
@@ -166,16 +166,18 @@ NS_NewComboboxControlFrame(nsIPresShell*
 nsIFrame*
 NS_NewProgressFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewMeterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsBlockFrame*
 NS_NewDetailsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 // Table frame factories
 class nsTableWrapperFrame;
 nsTableWrapperFrame*
 NS_NewTableWrapperFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 class nsTableFrame;
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/from-time-to-other-type-unthemed-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="checkbox" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/from-time-to-other-type-unthemed.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: when switching to another type, the input element should look
+             like that type (not like an input time element) -->
+  <script type="text/javascript">
+    function setToCheckbox()
+    {
+      document.getElementById("i").type = "checkbox";
+      document.documentElement.className = "";
+    }
+    document.addEventListener("MozReftestInvalidate", setToCheckbox);
+  </script>
+  <body>
+    <input type="time" id="i" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/reftest.list
@@ -0,0 +1,13 @@
+default-preferences pref(dom.forms.datetime,true)
+
+# not valid on Android/B2G where type=time looks like type=text
+skip-if(Android||B2G||Mulet) != time-simple-unthemed.html time-simple-unthemed-ref.html
+skip-if(Android||B2G||Mulet) != time-large-font.html time-basic.html
+skip-if(Android||B2G||Mulet) != time-width-height.html time-basic.html
+skip-if(Android||B2G||Mulet) != time-border.html time-basic.html
+# only valid on Android/B2G where type=number looks the same as type=text
+skip-if(!Android&&!B2G&&!Mulet) == time-simple-unthemed.html time-simple-unthemed-ref.html
+
+# type change
+skip-if(Android||B2G||Mulet) == to-time-from-other-type-unthemed.html time-simple-unthemed.html
+skip-if(Android||B2G||Mulet) == from-time-to-other-type-unthemed.html from-time-to-other-type-unthemed-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-basic.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="time" value="12:30">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-border.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="time" value="12:30" style="border:10px solid blue">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-large-font.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="time" value="12:30" style="font-size: 32px;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-simple-unthemed-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="text" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-simple-unthemed.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="time" style="-moz-appearance:none;">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-width-height.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="time" style="width:200px; height:50px">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/to-time-from-other-type-unthemed.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <!-- Test: input element changed to time state doesn't look like checkbox state -->
+  <script type="text/javascript">
+    function setToTime()
+    {
+      document.getElementById("i").type = "time";
+      document.documentElement.className = "";
+    }
+    document.addEventListener("MozReftestInvalidate", setToTime);
+  </script>
+  <body>
+    <input type="checkbox" id="i" style="-moz-appearance:none;">
+  </body>
+</html>
--- a/layout/reftests/forms/input/reftest.list
+++ b/layout/reftests/forms/input/reftest.list
@@ -6,8 +6,9 @@ include url/reftest.list
 include number/reftest.list
 include file/reftest.list
 include radio/reftest.list
 include range/reftest.list
 include text/reftest.list
 include percentage/reftest.list
 include hidden/reftest.list
 include color/reftest.list
+include datetime/reftest.list
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -762,16 +762,23 @@ audio:not([controls]) {
   transform: translate(0) !important;
 }
 
 video > .caption-box {
   position: relative;
   overflow: hidden;
 }
 
+/* datetime elements */
+
+input[type="time"] > xul|datetimebox {
+  display: flex;
+  -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
+}
+
 /* details & summary */
 /* Need to revert Bug 1259889 Part 2 when removing details preference. */
 @supports -moz-bool-pref("dom.details_element.enabled") {
   details > summary:first-of-type,
   details > summary:-moz-native-anonymous {
     display: list-item;
     list-style: disclosure-closed inside;
   }
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -36,25 +36,24 @@ ClearKeySession::ClearKeySession(const s
 {
   CK_LOGD("ClearKeySession ctor %p", this);
 }
 
 ClearKeySession::~ClearKeySession()
 {
   CK_LOGD("ClearKeySession dtor %p", this);
 
-  auto& keyIds = GetKeyIds();
-  for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
-    assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(*it));
-
-    ClearKeyDecryptionManager::Get()->ReleaseKeyId(*it);
-    mCallback->KeyStatusChanged(&mSessionId[0], mSessionId.size(),
-                                &(*it)[0], it->size(),
-                                kGMPUnknown);
+  std::vector<GMPMediaKeyInfo> key_infos;
+  for (const KeyId& keyId : mKeyIds) {
+    assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(keyId));
+    ClearKeyDecryptionManager::Get()->ReleaseKeyId(keyId);
+    key_infos.push_back(GMPMediaKeyInfo(&keyId[0], keyId.size(), kGMPUnknown));
   }
+  mCallback->BatchedKeyStatusChanged(&mSessionId[0], mSessionId.size(),
+                                     key_infos.data(), key_infos.size());
 }
 
 void
 ClearKeySession::Init(uint32_t aCreateSessionToken,
                       uint32_t aPromiseId,
                       const std::string& aInitDataType,
                       const uint8_t* aInitData, uint32_t aInitDataSize)
 {
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -163,34 +163,43 @@ ClearKeySessionManager::PersistentSessio
   }
 
   ClearKeySession* session = new ClearKeySession(aSessionId,
                                                  mCallback,
                                                  kGMPPersistentSession);
   mSessions[aSessionId] = session;
 
   uint32_t numKeys = aKeyDataSize / (2 * CLEARKEY_KEY_LEN);
+
+  vector<GMPMediaKeyInfo> key_infos;
+  vector<KeyIdPair> keyPairs;
   for (uint32_t i = 0; i < numKeys; i ++) {
     const uint8_t* base = aKeyData + 2 * CLEARKEY_KEY_LEN * i;
 
-    KeyId keyId(base, base + CLEARKEY_KEY_LEN);
-    assert(keyId.size() == CLEARKEY_KEY_LEN);
+    KeyIdPair keyPair;
+
+    keyPair.mKeyId = KeyId(base, base + CLEARKEY_KEY_LEN);
+    assert(keyPair.mKeyId.size() == CLEARKEY_KEY_LEN);
 
-    Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
-    assert(key.size() == CLEARKEY_KEY_LEN);
+    keyPair.mKey = Key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
+    assert(keyPair.mKey.size() == CLEARKEY_KEY_LEN);
 
-    session->AddKeyId(keyId);
+    session->AddKeyId(keyPair.mKeyId);
 
-    mDecryptionManager->ExpectKeyId(keyId);
-    mDecryptionManager->InitKey(keyId, key);
-    mKeyIds.insert(key);
-    mCallback->KeyStatusChanged(&aSessionId[0], aSessionId.size(),
-                                &keyId[0], keyId.size(),
-                                kGMPUsable);
+    mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
+    mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+    mKeyIds.insert(keyPair.mKey);
+
+    keyPairs.push_back(keyPair);
+    key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0],
+                                        keyPairs[i].mKeyId.size(),
+                                        kGMPUsable));
   }
+  mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(),
+                                     key_infos.data(), key_infos.size());
 
   mCallback->ResolveLoadSessionPromise(aPromiseId, true);
 }
 
 void
 ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
                                       const char* aSessionId,
                                       uint32_t aSessionIdLength,
@@ -211,23 +220,27 @@ ClearKeySessionManager::UpdateSession(ui
   // Parse the response for any (key ID, key) pairs.
   vector<KeyIdPair> keyPairs;
   if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
     CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
     mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, nullptr, 0);
     return;
   }
 
-  for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) {
-    mDecryptionManager->InitKey(it->mKeyId, it->mKey);
-    mKeyIds.insert(it->mKeyId);
-    mCallback->KeyStatusChanged(aSessionId, aSessionIdLength,
-                                &it->mKeyId[0], it->mKeyId.size(),
-                                kGMPUsable);
+  vector<GMPMediaKeyInfo> key_infos;
+  for (size_t i = 0; i < keyPairs.size(); i++) {
+    KeyIdPair& keyPair = keyPairs[i];
+    mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+    mKeyIds.insert(keyPair.mKeyId);
+    key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
+                                        keyPair.mKeyId.size(),
+                                        kGMPUsable));
   }
+  mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
+                                     key_infos.data(), key_infos.size());
 
   if (session->Type() != kGMPPersistentSession) {
     mCallback->ResolvePromise(aPromiseId);
     return;
   }
 
   // Store the keys on disk. We store a record whose name is the sessionId,
   // and simply append each keyId followed by its key.
--- a/mobile/android/components/FxAccountsPush.js
+++ b/mobile/android/components/FxAccountsPush.js
@@ -96,74 +96,64 @@ FxAccountsPush.prototype = {
     }).catch(err => {
       Log.e("Error during unsubscribe", err);
     });
   },
 
   _decodePushMessage(data) {
     Log.i("FxAccountsPush _decodePushMessage");
     data = JSON.parse(data);
-    let { message, cryptoParams } = this._messageAndCryptoParams(data);
+    let { headers, message } = this._messageAndHeaders(data);
     return new Promise((resolve, reject) => {
       PushService.getSubscription(FXA_PUSH_SCOPE,
         Services.scriptSecurityManager.getSystemPrincipal(),
         (result, subscription) => {
           if (!subscription) {
             return reject(new Error("No subscription found"));
           }
           return resolve(subscription);
         });
     }).then(subscription => {
-      if (!cryptoParams) {
-        return new Uint8Array();
-      }
-      return PushCrypto.decodeMsg(
-        message,
-        subscription.p256dhPrivateKey,
-        new Uint8Array(subscription.getKey("p256dh")),
-        cryptoParams.dh,
-        cryptoParams.salt,
-        cryptoParams.rs,
-        new Uint8Array(subscription.getKey("auth")),
-        cryptoParams.padSize
-      );
+      return PushCrypto.decrypt(subscription.p256dhPrivateKey,
+                                new Uint8Array(subscription.getKey("p256dh")),
+                                new Uint8Array(subscription.getKey("auth")),
+                                headers, message);
     })
-    .then(decryptedMessage => {
-      decryptedMessage = _decoder.decode(decryptedMessage);
+    .then(plaintext => {
+      let decryptedMessage = plaintext ? _decoder.decode(plaintext) : "";
       Messaging.sendRequestForResult({
         type: "FxAccountsPush:ReceivedPushMessageToDecode:Response",
         message: decryptedMessage
       });
     })
     .catch(err => {
       Log.d("Error while decoding incoming message : " + err);
     });
   },
 
   // Copied from PushServiceAndroidGCM
-  _messageAndCryptoParams(data) {
+  _messageAndHeaders(data) {
     // Default is no data (and no encryption).
     let message = null;
-    let cryptoParams = null;
+    let headers = null;
 
     if (data.message && data.enc && (data.enckey || data.cryptokey)) {
       let headers = {
         encryption_key: data.enckey,
         crypto_key: data.cryptokey,
         encryption: data.enc,
         encoding: data.con,
       };
-      cryptoParams = getCryptoParams(headers);
       // Ciphertext is (urlsafe) Base 64 encoded.
       message = ChromeUtils.base64URLDecode(data.message, {
         // The Push server may append padding.
         padding: "ignore",
       });
     }
-    return { message, cryptoParams };
+    return { headers, message };
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   classID: Components.ID("{d1bbb0fd-1d47-4134-9c12-d7b1be20b721}")
 };
 
 function urlsafeBase64Encode(key) {
--- a/security/certverifier/ExtendedValidation.cpp
+++ b/security/certverifier/ExtendedValidation.cpp
@@ -6,16 +6,17 @@
 
 #include "ExtendedValidation.h"
 
 #include "base64.h"
 #include "cert.h"
 #include "certdb.h"
 #include "hasht.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
 #include "mozilla/PodOperations.h"
 #include "pk11pub.h"
 #include "pkix/pkixtypes.h"
 #include "prerror.h"
 #include "prinit.h"
 #include "secerr.h"
 
 extern mozilla::LazyLogModule gPIPNSSLog;
@@ -27,17 +28,16 @@ struct nsMyTrustedEVInfo
 {
   const char* dotted_oid;
   const char* oid_name; // Set this to null to signal an invalid structure,
                   // (We can't have an empty list, so we'll use a dummy entry)
   SECOidTag oid_tag;
   const unsigned char ev_root_sha256_fingerprint[SHA256_LENGTH];
   const char* issuer_base64;
   const char* serial_base64;
-  mozilla::UniqueCERTCertificate cert;
 };
 
 // HOWTO enable additional CA root certificates for EV:
 //
 // For each combination of "root certificate" and "policy OID",
 // one entry must be added to the array named myTrustedEVInfos.
 //
 // We use the combination of "issuer name" and "serial number" to
@@ -70,17 +70,16 @@ struct nsMyTrustedEVInfo
 //   to be sure).
 // - the constant SEC_OID_UNKNOWN
 //   (it will be replaced at runtime with another identifier)
 // - the SHA-256 fingerprint
 // - the "Issuer DER Base64" as printed by the pp tool.
 //   Remove all whitespaces. If you use multiple lines, make sure that
 //   only the final line will be followed by a comma.
 // - the "Serial DER Base64" (as printed by pp)
-// - nullptr
 //
 // After adding an entry, test it locally against the test site that
 // has been provided by the CA. Note that you must use a version of NSS
 // where the root certificate has already been added and marked as trusted
 // for issuing SSL server certificates (at least).
 //
 // If you are able to connect to the site without certificate errors,
 // but you don't see the EV status indicator, then most likely the CA
@@ -120,17 +119,16 @@ static struct nsMyTrustedEVInfo myTruste
     "1.3.6.1.4.1.13769.666.666.666.1.500.9.1",
     "DEBUGtesting EV OID",
     SEC_OID_UNKNOWN,
     { 0xE4, 0xFB, 0x04, 0x16, 0x10, 0x32, 0x67, 0x08, 0x6C, 0x84, 0x2E,
       0x91, 0xF3, 0xEF, 0x0E, 0x45, 0x99, 0xBC, 0xA8, 0x54, 0x73, 0xF5,
       0x03, 0x2C, 0x7B, 0xDC, 0x09, 0x70, 0x76, 0x49, 0xBF, 0xAA },
     "MBExDzANBgNVBAMMBmV2cm9vdA==",
     "W9j5PS8YoKgynZdYa9i2Kwexnp8=",
-    nullptr
   },
   {
     // This is an RSA root with an inadequate key size. It is used to test that
     // minimum key sizes are enforced when verifying for EV. It can be
     // generated using pycert.py and the following specification:
     //
     // issuer:ev_root_rsa_2040
     // subject:ev_root_rsa_2040
@@ -145,1122 +143,1041 @@ static struct nsMyTrustedEVInfo myTruste
     "1.3.6.1.4.1.13769.666.666.666.1.500.9.1",
     "DEBUGtesting EV OID",
     SEC_OID_UNKNOWN,
     { 0x49, 0x46, 0x10, 0xF4, 0xF5, 0xB1, 0x96, 0xE7, 0xFB, 0xFA, 0x4D,
       0xA6, 0x34, 0x03, 0xD0, 0x99, 0x22, 0xD4, 0x77, 0x20, 0x3F, 0x84,
       0xE0, 0xDF, 0x1C, 0xAD, 0xB4, 0xC2, 0x76, 0xBB, 0x63, 0x24 },
     "MBsxGTAXBgNVBAMMEGV2X3Jvb3RfcnNhXzIwNDA=",
     "P1iIBgxk6kH+x64EUBTV3qoHuas=",
-    nullptr
   },
 #endif
   {
     // OU=Security Communication EV RootCA1,O="SECOM Trust Systems CO.,LTD.",C=JP
     "1.2.392.200091.100.721.1",
     "SECOM EV OID",
     SEC_OID_UNKNOWN,
     { 0xA2, 0x2D, 0xBA, 0x68, 0x1E, 0x97, 0x37, 0x6E, 0x2D, 0x39, 0x7D,
       0x72, 0x8A, 0xAE, 0x3A, 0x9B, 0x62, 0x96, 0xB9, 0xFD, 0xBA, 0x60,
       0xBC, 0x2E, 0x11, 0xF6, 0x47, 0xF2, 0xC6, 0x75, 0xFB, 0x37 },
     "MGAxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENP"
     "LixMVEQuMSowKAYDVQQLEyFTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVWIFJvb3RD"
     "QTE=",
     "AA==",
-    nullptr
   },
   {
     // CN=Cybertrust Global Root,O=Cybertrust, Inc
     "1.3.6.1.4.1.6334.1.100.1",
     "Cybertrust EV OID",
     SEC_OID_UNKNOWN,
     { 0x96, 0x0A, 0xDF, 0x00, 0x63, 0xE9, 0x63, 0x56, 0x75, 0x0C, 0x29,
       0x65, 0xDD, 0x0A, 0x08, 0x67, 0xDA, 0x0B, 0x9C, 0xBD, 0x6E, 0x77,
       0x71, 0x4A, 0xEA, 0xFB, 0x23, 0x49, 0xAB, 0x39, 0x3D, 0xA3 },
     "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVz"
     "dCBHbG9iYWwgUm9vdA==",
     "BAAAAAABD4WqLUg=",
-    nullptr
   },
   {
     // CN=SwissSign Gold CA - G2,O=SwissSign AG,C=CH
     "2.16.756.1.89.1.2.1.1",
     "SwissSign EV OID",
     SEC_OID_UNKNOWN,
     { 0x62, 0xDD, 0x0B, 0xE9, 0xB9, 0xF5, 0x0A, 0x16, 0x3E, 0xA0, 0xF8,
       0xE7, 0x5C, 0x05, 0x3B, 0x1E, 0xCA, 0x57, 0xEA, 0x55, 0xC8, 0x68,
       0x8F, 0x64, 0x7C, 0x68, 0x81, 0xF2, 0xC8, 0x35, 0x7B, 0x95 },
     "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMT"
     "FlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=",
     "ALtAHEP1Xk+w",
-    nullptr
   },
   {
     // CN=StartCom Certification Authority,OU=Secure Digital Certificate Signing,O=StartCom Ltd.,C=IL
     "1.3.6.1.4.1.23223.1.1.1",
     "StartCom EV OID",
     SEC_OID_UNKNOWN,
     { 0xC7, 0x66, 0xA9, 0xBE, 0xF2, 0xD4, 0x07, 0x1C, 0x86, 0x3A, 0x31,
       0xAA, 0x49, 0x20, 0xE8, 0x13, 0xB2, 0xD1, 0x98, 0x60, 0x8C, 0xB7,
       0xB7, 0xCF, 0xE2, 0x11, 0x43, 0xB8, 0x36, 0xDF, 0x09, 0xEA },
     "MH0xCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQL"
     "EyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBT"
     "dGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "AQ==",
-    nullptr
   },
   {
     // CN=StartCom Certification Authority,OU=Secure Digital Certificate Signing,O=StartCom Ltd.,C=IL
     "1.3.6.1.4.1.23223.1.1.1",
     "StartCom EV OID",
     SEC_OID_UNKNOWN,
     { 0xE1, 0x78, 0x90, 0xEE, 0x09, 0xA3, 0xFB, 0xF4, 0xF4, 0x8B, 0x9C,
       0x41, 0x4A, 0x17, 0xD6, 0x37, 0xB7, 0xA5, 0x06, 0x47, 0xE9, 0xBC,
       0x75, 0x23, 0x22, 0x72, 0x7F, 0xCC, 0x17, 0x42, 0xA9, 0x11 },
     "MH0xCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQL"
     "EyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBT"
     "dGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "LQ==",
-    nullptr
   },
   {
     // CN=StartCom Certification Authority G2,O=StartCom Ltd.,C=IL
     "1.3.6.1.4.1.23223.1.1.1",
     "StartCom EV OID",
     SEC_OID_UNKNOWN,
     { 0xC7, 0xBA, 0x65, 0x67, 0xDE, 0x93, 0xA7, 0x98, 0xAE, 0x1F, 0xAA,
       0x79, 0x1E, 0x71, 0x2D, 0x37, 0x8F, 0xAE, 0x1F, 0x93, 0xC4, 0x39,
       0x7F, 0xEA, 0x44, 0x1B, 0xB7, 0xCB, 0xE6, 0xFD, 0x59, 0x95 },
     "MFMxCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSwwKgYDVQQD"
     "EyNTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBHMg==",
     "Ow==",
-    nullptr
   },
   {
     // CN=VeriSign Class 3 Public Primary Certification Authority - G5,OU="(c) 2006 VeriSign, Inc. - For authorized use only",OU=VeriSign Trust Network,O="VeriSign, Inc.",C=US
     "2.16.840.1.113733.1.7.23.6",
     "VeriSign EV OID",
     SEC_OID_UNKNOWN,
     { 0x9A, 0xCF, 0xAB, 0x7E, 0x43, 0xC8, 0xD8, 0x80, 0xD0, 0x6B, 0x26,
       0x2A, 0x94, 0xDE, 0xEE, 0xE4, 0xB4, 0x65, 0x99, 0x89, 0xC3, 0xD0,
       0xCA, 0xF1, 0x9B, 0xAF, 0x64, 0x05, 0xE4, 0x1A, 0xB7, 0xDF },
     "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNV"
     "BAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZl"
     "cmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMT"
     "PFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBB"
     "dXRob3JpdHkgLSBHNQ==",
     "GNrRniZ96LtKIVjNzGs7Sg==",
-    nullptr
   },
   {
     // CN=GeoTrust Primary Certification Authority,O=GeoTrust Inc.,C=US
     "1.3.6.1.4.1.14370.1.6",
     "GeoTrust EV OID",
     SEC_OID_UNKNOWN,
     { 0x37, 0xD5, 0x10, 0x06, 0xC5, 0x12, 0xEA, 0xAB, 0x62, 0x64, 0x21,
       0xF1, 0xEC, 0x8C, 0x92, 0x01, 0x3F, 0xC5, 0xF8, 0x2A, 0xE9, 0x8E,
       0xE5, 0x33, 0xEB, 0x46, 0x19, 0xB8, 0xDE, 0xB4, 0xD0, 0x6C },
     "MFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQD"
     "EyhHZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5",
     "GKy1av1pthU6Y2yv2vrEoQ==",
-    nullptr
   },
   {
     // CN=thawte Primary Root CA,OU="(c) 2006 thawte, Inc. - For authorized use only",OU=Certification Services Division,O="thawte, Inc.",C=US
     "2.16.840.1.113733.1.7.48.1",
     "Thawte EV OID",
     SEC_OID_UNKNOWN,
     { 0x8D, 0x72, 0x2F, 0x81, 0xA9, 0xC1, 0x13, 0xC0, 0x79, 0x1D, 0xF1,
       0x36, 0xA2, 0x96, 0x6D, 0xB2, 0x6C, 0x95, 0x0A, 0x97, 0x1D, 0xB4,
       0x6B, 0x41, 0x99, 0xF4, 0xEA, 0x54, 0xB7, 0x8B, 0xFB, 0x9F },
     "MIGpMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMSgwJgYDVQQL"
     "Ex9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYDVQQLEy8oYykg"
     "MjAwNiB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0G"
     "A1UEAxMWdGhhd3RlIFByaW1hcnkgUm9vdCBDQQ==",
     "NE7VVyDV7exJ9C/ON9srbQ==",
-    nullptr
   },
   {
     // CN=XRamp Global Certification Authority,O=XRamp Security Services Inc,OU=www.xrampsecurity.com,C=US
     "2.16.840.1.114404.1.1.2.4.1",
     "Trustwave EV OID",
     SEC_OID_UNKNOWN,
     { 0xCE, 0xCD, 0xDC, 0x90, 0x50, 0x99, 0xD8, 0xDA, 0xDF, 0xC5, 0xB1,
       0xD2, 0x09, 0xB7, 0x37, 0xCB, 0xE2, 0xC1, 0x8C, 0xFB, 0x2C, 0x10,
       0xC0, 0xFF, 0x0B, 0xCF, 0x0D, 0x32, 0x86, 0xFC, 0x1A, 0xA2 },
     "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29t"
     "MSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMT"
     "JFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "UJRs7Bjq1ZxN1ZfvdY+grQ==",
-    nullptr
   },
   {
     // CN=SecureTrust CA,O=SecureTrust Corporation,C=US
     "2.16.840.1.114404.1.1.2.4.1",
     "Trustwave EV OID",
     SEC_OID_UNKNOWN,
     { 0xF1, 0xC1, 0xB5, 0x0A, 0xE5, 0xA2, 0x0D, 0xD8, 0x03, 0x0E, 0xC9,
       0xF6, 0xBC, 0x24, 0x82, 0x3D, 0xD3, 0x67, 0xB5, 0x25, 0x57, 0x59,
       0xB4, 0xE7, 0x1B, 0x61, 0xFC, 0xE9, 0xF7, 0x37, 0x5D, 0x73 },
     "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlv"
     "bjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=",
     "DPCOXAgWpa1Cf/DrJxhZ0A==",
-    nullptr
   },
   {
     // CN=Secure Global CA,O=SecureTrust Corporation,C=US
     "2.16.840.1.114404.1.1.2.4.1",
     "Trustwave EV OID",
     SEC_OID_UNKNOWN,
     { 0x42, 0x00, 0xF5, 0x04, 0x3A, 0xC8, 0x59, 0x0E, 0xBB, 0x52, 0x7D,
       0x20, 0x9E, 0xD1, 0x50, 0x30, 0x29, 0xFB, 0xCB, 0xD4, 0x1C, 0xA1,
       0xB5, 0x06, 0xEC, 0x27, 0xF1, 0x5A, 0xDE, 0x7D, 0xAC, 0x69 },
     "MEoxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlv"
     "bjEZMBcGA1UEAxMQU2VjdXJlIEdsb2JhbCBDQQ==",
     "B1YipOjUiolN9BPI8PjqpQ==",
-    nullptr
   },
   {
     // CN=COMODO ECC Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
     "1.3.6.1.4.1.6449.1.2.1.5.1",
     "Comodo EV OID",
     SEC_OID_UNKNOWN,
     { 0x17, 0x93, 0x92, 0x7A, 0x06, 0x14, 0x54, 0x97, 0x89, 0xAD, 0xCE,
       0x2F, 0x8F, 0x34, 0xF7, 0xF0, 0xB6, 0x6D, 0x0F, 0x3A, 0xE3, 0xA3,
       0xB8, 0x4D, 0x21, 0xEC, 0x15, 0xDB, 0xBA, 0x4F, 0xAD, 0xC7 },
     "MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAw"
     "DgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkG"
     "A1UEAxMiQ09NT0RPIEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "H0evqmIAcFBUTAGem2OZKg==",
-    nullptr
   },
   {
     // CN=COMODO Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
     "1.3.6.1.4.1.6449.1.2.1.5.1",
     "Comodo EV OID",
     SEC_OID_UNKNOWN,
     { 0x0C, 0x2C, 0xD6, 0x3D, 0xF7, 0x80, 0x6F, 0xA3, 0x99, 0xED, 0xE8,
       0x09, 0x11, 0x6B, 0x57, 0x5B, 0xF8, 0x79, 0x89, 0xF0, 0x65, 0x18,
       0xF9, 0x80, 0x8C, 0x86, 0x05, 0x03, 0x17, 0x8B, 0xAF, 0x66 },
     "MIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAw"
     "DgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEnMCUG"
     "A1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5",
     "ToEtioJl4AsC7j41AkblPQ==",
-    nullptr
   },
   {
     // CN=AddTrust External CA Root,OU=AddTrust External TTP Network,O=AddTrust AB,C=SE
     "1.3.6.1.4.1.6449.1.2.1.5.1",
     "Comodo EV OID",
     SEC_OID_UNKNOWN,
     { 0x68, 0x7F, 0xA4, 0x51, 0x38, 0x22, 0x78, 0xFF, 0xF0, 0xC8, 0xB1,
       0x1F, 0x8D, 0x43, 0xD5, 0x76, 0x67, 0x1C, 0x6E, 0xB2, 0xBC, 0xEA,
       0xB4, 0x13, 0xFB, 0x83, 0xD9, 0x65, 0xD0, 0x6D, 0x2F, 0xF2 },
     "MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMd"
     "QWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0"
     "IEV4dGVybmFsIENBIFJvb3Q=",
     "AQ==",
-    nullptr
   },
   {
     // CN=UTN-USERFirst-Hardware,OU=http://www.usertrust.com,O=The USERTRUST Network,L=Salt Lake City,ST=UT,C=US
     "1.3.6.1.4.1.6449.1.2.1.5.1",
     "Comodo EV OID",
     SEC_OID_UNKNOWN,
     { 0x6E, 0xA5, 0x47, 0x41, 0xD0, 0x04, 0x66, 0x7E, 0xED, 0x1B, 0x48,
       0x16, 0x63, 0x4A, 0xA3, 0xA7, 0x9E, 0x6E, 0x4B, 0x96, 0x95, 0x0F,
       0x82, 0x79, 0xDA, 0xFC, 0x8D, 0x9B, 0xD8, 0x81, 0x21, 0x37 },
     "MIGXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFr"
     "ZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsT"
     "GGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEfMB0GA1UEAxMWVVROLVVTRVJGaXJz"
     "dC1IYXJkd2FyZQ==",
     "RL4Mi1AAJLQR0zYq/mUK/Q==",
-    nullptr
   },
   {
     // OU=Go Daddy Class 2 Certification Authority,O=\"The Go Daddy Group, Inc.\",C=US
     "2.16.840.1.114413.1.7.23.3",
     "Go Daddy EV OID a",
     SEC_OID_UNKNOWN,
     { 0xC3, 0x84, 0x6B, 0xF2, 0x4B, 0x9E, 0x93, 0xCA, 0x64, 0x27, 0x4C,
       0x0E, 0xC6, 0x7C, 0x1E, 0xCC, 0x5E, 0x02, 0x4F, 0xFC, 0xAC, 0xD2,
       0xD7, 0x40, 0x19, 0x35, 0x0E, 0x81, 0xFE, 0x54, 0x6A, 0xE4 },
     "MGMxCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhUaGUgR28gRGFkZHkgR3JvdXAsIElu"
     "Yy4xMTAvBgNVBAsTKEdvIERhZGR5IENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRo"
     "b3JpdHk=",
     "AA==",
-    nullptr
   },
   {
     // CN=Go Daddy Root Certificate Authority - G2,O="GoDaddy.com, Inc.",L=Scottsdale,ST=Arizona,C=US
     "2.16.840.1.114413.1.7.23.3",
     "Go Daddy EV OID a",
     SEC_OID_UNKNOWN,
     { 0x45, 0x14, 0x0B, 0x32, 0x47, 0xEB, 0x9C, 0xC8, 0xC5, 0xB4, 0xF0,
       0xD7, 0xB5, 0x30, 0x91, 0xF7, 0x32, 0x92, 0x08, 0x9E, 0x6E, 0x5A,
       0x63, 0xE2, 0x74, 0x9D, 0xD3, 0xAC, 0xA9, 0x19, 0x8E, 0xDA },
     "MIGDMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2Nv"
     "dHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMTAvBgNVBAMTKEdv"
     "IERhZGR5IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=",
     "AA==",
-    nullptr
   },
   {
     // OU=Starfield Class 2 Certification Authority,O=\"Starfield Technologies, Inc.\",C=US
     "2.16.840.1.114414.1.7.23.3",
     "Go Daddy EV OID b",
     SEC_OID_UNKNOWN,
     { 0x14, 0x65, 0xFA, 0x20, 0x53, 0x97, 0xB8, 0x76, 0xFA, 0xA6, 0xF0,
       0xA9, 0x95, 0x8E, 0x55, 0x90, 0xE4, 0x0F, 0xCC, 0x7F, 0xAA, 0x4F,
       0xB7, 0xC2, 0xC8, 0x67, 0x75, 0x21, 0xFB, 0x5F, 0xB6, 0x58 },
     "MGgxCzAJBgNVBAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVz"
     "LCBJbmMuMTIwMAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9u"
     "IEF1dGhvcml0eQ==",
     "AA==",
-    nullptr
   },
   {
     // CN=Starfield Root Certificate Authority - G2,O="Starfield Technologies, Inc.",L=Scottsdale,ST=Arizona,C=US
     "2.16.840.1.114414.1.7.23.3",
     "Go Daddy EV OID b",
     SEC_OID_UNKNOWN,
     { 0x2C, 0xE1, 0xCB, 0x0B, 0xF9, 0xD2, 0xF9, 0xE1, 0x02, 0x99, 0x3F,
       0xBE, 0x21, 0x51, 0x52, 0xC3, 0xB2, 0xDD, 0x0C, 0xAB, 0xDE, 0x1C,
       0x68, 0xE5, 0x31, 0x9B, 0x83, 0x91, 0x54, 0xDB, 0xB7, 0xF5 },
     "MIGPMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2Nv"
     "dHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEy"
     "MDAGA1UEAxMpU3RhcmZpZWxkIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0g"
     "RzI=",
     "AA==",
-    nullptr
   },
   {
     // CN=DigiCert High Assurance EV Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US
     "2.16.840.1.114412.2.1",
     "DigiCert EV OID",
     SEC_OID_UNKNOWN,
     { 0x74, 0x31, 0xE5, 0xF4, 0xC3, 0xC1, 0xCE, 0x46, 0x90, 0x77, 0x4F,
       0x0B, 0x61, 0xE0, 0x54, 0x40, 0x88, 0x3B, 0xA9, 0xA0, 0x1E, 0xD0,
       0x0B, 0xA6, 0xAB, 0xD7, 0x80, 0x6E, 0xD3, 0xB1, 0x18, 0xCF },
     "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT"
     "EHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJh"
     "bmNlIEVWIFJvb3QgQ0E=",
     "AqxcJmoLQJuPC3nyrkYldw==",
-    nullptr
   },
   {
     // CN=QuoVadis Root CA 2,O=QuoVadis Limited,C=BM
     "1.3.6.1.4.1.8024.0.2.100.1.2",
     "Quo Vadis EV OID",
     SEC_OID_UNKNOWN,
     { 0x85, 0xA0, 0xDD, 0x7D, 0xD7, 0x20, 0xAD, 0xB7, 0xFF, 0x05, 0xF8,
       0x3D, 0x54, 0x2B, 0x20, 0x9D, 0xC7, 0xFF, 0x45, 0x28, 0xF7, 0xD6,
       0x77, 0xB1, 0x83, 0x89, 0xFE, 0xA5, 0xE5, 0xC4, 0x9E, 0x86 },
     "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYD"
     "VQQDExJRdW9WYWRpcyBSb290IENBIDI=",
     "BQk=",
-    nullptr
   },
   {
     // CN=Network Solutions Certificate Authority,O=Network Solutions L.L.C.,C=US
     "1.3.6.1.4.1.782.1.2.1.8.1",
     "Network Solutions EV OID",
     SEC_OID_UNKNOWN,
     { 0x15, 0xF0, 0xBA, 0x00, 0xA3, 0xAC, 0x7A, 0xF3, 0xAC, 0x88, 0x4C,
       0x07, 0x2B, 0x10, 0x11, 0xA0, 0x77, 0xBD, 0x77, 0xC0, 0x97, 0xF4,
       0x01, 0x64, 0xB2, 0xF8, 0x59, 0x8A, 0xBD, 0x83, 0x86, 0x0C },
     "MGIxCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhOZXR3b3JrIFNvbHV0aW9ucyBMLkwu"
     "Qy4xMDAuBgNVBAMTJ05ldHdvcmsgU29sdXRpb25zIENlcnRpZmljYXRlIEF1dGhv"
     "cml0eQ==",
     "V8szb8JcFuZHFhfjkDFo4A==",
-    nullptr
   },
   {
     // CN=Entrust Root Certification Authority,OU="(c) 2006 Entrust, Inc.",OU=www.entrust.net/CPS is incorporated by reference,O="Entrust, Inc.",C=US
     "2.16.840.1.114028.10.1.2",
     "Entrust EV OID",
     SEC_OID_UNKNOWN,
     { 0x73, 0xC1, 0x76, 0x43, 0x4F, 0x1B, 0xC6, 0xD5, 0xAD, 0xF4, 0x5B,
       0x0E, 0x76, 0xE7, 0x27, 0x28, 0x7C, 0x8D, 0xE5, 0x76, 0x16, 0xC1,
       0xE6, 0xE6, 0x14, 0x1A, 0x2B, 0x2C, 0xBC, 0x7D, 0x8E, 0x4C },
     "MIGwMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UE"
     "CxMwd3d3LmVudHJ1c3QubmV0L0NQUyBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJl"
     "bmNlMR8wHQYDVQQLExYoYykgMjAwNiBFbnRydXN0LCBJbmMuMS0wKwYDVQQDEyRF"
     "bnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=",
     "RWtQVA==",
-    nullptr
   },
   {
     // CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
     "1.3.6.1.4.1.4146.1.1",
     "GlobalSign EV OID",
     SEC_OID_UNKNOWN,
     { 0xEB, 0xD4, 0x10, 0x40, 0xE4, 0xBB, 0x3E, 0xC7, 0x42, 0xC9, 0xE3,
       0x81, 0xD3, 0x1E, 0xF2, 0xA4, 0x1A, 0x48, 0xB6, 0x68, 0x5C, 0x96,
       0xE7, 0xCE, 0xF3, 0xC1, 0xDF, 0x6C, 0xD4, 0x33, 0x1C, 0x99 },
     "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYD"
     "VQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=",
     "BAAAAAABFUtaw5Q=",
-    nullptr
   },
   {
     // CN=GlobalSign,O=GlobalSign,OU=GlobalSign Root CA - R2
     "1.3.6.1.4.1.4146.1.1",
     "GlobalSign EV OID",
     SEC_OID_UNKNOWN,
     { 0xCA, 0x42, 0xDD, 0x41, 0x74, 0x5F, 0xD0, 0xB8, 0x1E, 0xB9, 0x02,
       0x36, 0x2C, 0xF9, 0xD8, 0xBF, 0x71, 0x9D, 0xA1, 0xBD, 0x1B, 0x1E,
       0xFC, 0x94, 0x6F, 0x5B, 0x4C, 0x99, 0xF4, 0x2C, 0x1B, 0x9E },
     "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpH"
     "bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu",
     "BAAAAAABD4Ym5g0=",
-    nullptr
   },
   {
     // CN=GlobalSign,O=GlobalSign,OU=GlobalSign Root CA - R3
     "1.3.6.1.4.1.4146.1.1",
     "GlobalSign EV OID",
     SEC_OID_UNKNOWN,
     { 0xCB, 0xB5, 0x22, 0xD7, 0xB7, 0xF1, 0x27, 0xAD, 0x6A, 0x01, 0x13,
       0x86, 0x5B, 0xDF, 0x1C, 0xD4, 0x10, 0x2E, 0x7D, 0x07, 0x59, 0xAF,
       0x63, 0x5A, 0x7C, 0xF4, 0x72, 0x0D, 0xC9, 0x63, 0xC5, 0x3B },
     "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpH"
     "bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu",
     "BAAAAAABIVhTCKI=",
-    nullptr
   },
   {
     // CN=Buypass Class 3 Root CA,O=Buypass AS-983163327,C=NO
     "2.16.578.1.26.1.3.3",
     "Buypass EV OID",
     SEC_OID_UNKNOWN,
     { 0xED, 0xF7, 0xEB, 0xBC, 0xA2, 0x7A, 0x2A, 0x38, 0x4D, 0x38, 0x7B,
       0x7D, 0x40, 0x10, 0xC6, 0x66, 0xE2, 0xED, 0xB4, 0x84, 0x3E, 0x4C,
       0x29, 0xB4, 0xAE, 0x1D, 0x5B, 0x93, 0x32, 0xE6, 0xB2, 0x4D },
     "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEg"
     "MB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAzIFJvb3QgQ0E=",
     "Ag==",
-    nullptr
   },
   {
     // CN=Class 2 Primary CA,O=Certplus,C=FR
     "1.3.6.1.4.1.22234.2.5.2.3.1",
     "Certplus EV OID",
     SEC_OID_UNKNOWN,
     { 0x0F, 0x99, 0x3C, 0x8A, 0xEF, 0x97, 0xBA, 0xAF, 0x56, 0x87, 0x14,
       0x0E, 0xD5, 0x9A, 0xD1, 0x82, 0x1B, 0xB4, 0xAF, 0xAC, 0xF0, 0xAA,
       0x9A, 0x58, 0xB5, 0xD5, 0x7A, 0x33, 0x8A, 0x3A, 0xFB, 0xCB },
     "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xh"
     "c3MgMiBQcmltYXJ5IENB",
     "AIW9S/PY2uNp9pTXX8OlRCM=",
-    nullptr
   },
   {
     // CN=Chambers of Commerce Root - 2008,O=AC Camerfirma S.A.,serialNumber=A82743287,L=Madrid (see current address at www.camerfirma.com/address),C=EU
     "1.3.6.1.4.1.17326.10.14.2.1.2",
     "Camerfirma EV OID a",
     SEC_OID_UNKNOWN,
     { 0x06, 0x3E, 0x4A, 0xFA, 0xC4, 0x91, 0xDF, 0xD3, 0x32, 0xF3, 0x08,
       0x9B, 0x85, 0x42, 0xE9, 0x46, 0x17, 0xD8, 0x93, 0xD7, 0xFE, 0x94,
       0x4E, 0x10, 0xA7, 0x93, 0x7E, 0xE2, 0x9D, 0x96, 0x93, 0xC0 },
     "MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBh"
     "ZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ"
     "QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMT"
     "IENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4",
     "AKPaQn6ksa7a",
-    nullptr
   },
   {
     // CN=Global Chambersign Root - 2008,O=AC Camerfirma S.A.,serialNumber=A82743287,L=Madrid (see current address at www.camerfirma.com/address),C=EU
     "1.3.6.1.4.1.17326.10.8.12.1.2",
     "Camerfirma EV OID b",
     SEC_OID_UNKNOWN,
     { 0x13, 0x63, 0x35, 0x43, 0x93, 0x34, 0xA7, 0x69, 0x80, 0x16, 0xA0,
       0xD3, 0x24, 0xDE, 0x72, 0x28, 0x4E, 0x07, 0x9D, 0x7B, 0x52, 0x20,
       0xBB, 0x8F, 0xBD, 0x74, 0x78, 0x16, 0xEE, 0xBE, 0xBA, 0xCA },
     "MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBh"
     "ZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ"
     "QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMT"
     "Hkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==",
     "AMnN0+nVfSPO",
-    nullptr
   },
   {
     // CN=AffirmTrust Commercial,O=AffirmTrust,C=US
     "1.3.6.1.4.1.34697.2.1",
     "AffirmTrust EV OID a",
     SEC_OID_UNKNOWN,
     { 0x03, 0x76, 0xAB, 0x1D, 0x54, 0xC5, 0xF9, 0x80, 0x3C, 0xE4, 0xB2,
       0xE2, 0x01, 0xA0, 0xEE, 0x7E, 0xEF, 0x7B, 0x57, 0xB6, 0x36, 0xE8,
       0xA9, 0x3C, 0x9B, 0x8D, 0x48, 0x60, 0xC9, 0x6F, 0x5F, 0xA7 },
     "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwW"
     "QWZmaXJtVHJ1c3QgQ29tbWVyY2lhbA==",
     "d3cGJyapsXw=",
-    nullptr
   },
   {
     // CN=AffirmTrust Networking,O=AffirmTrust,C=US
     "1.3.6.1.4.1.34697.2.2",
     "AffirmTrust EV OID b",
     SEC_OID_UNKNOWN,
     { 0x0A, 0x81, 0xEC, 0x5A, 0x92, 0x97, 0x77, 0xF1, 0x45, 0x90, 0x4A,
       0xF3, 0x8D, 0x5D, 0x50, 0x9F, 0x66, 0xB5, 0xE2, 0xC5, 0x8F, 0xCD,
       0xB5, 0x31, 0x05, 0x8B, 0x0E, 0x17, 0xF3, 0xF0, 0xB4, 0x1B },
     "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwW"
     "QWZmaXJtVHJ1c3QgTmV0d29ya2luZw==",
     "fE8EORzUmS0=",
-    nullptr
   },
   {
     // CN=AffirmTrust Premium,O=AffirmTrust,C=US
     "1.3.6.1.4.1.34697.2.3",
     "AffirmTrust EV OID c",
     SEC_OID_UNKNOWN,
     { 0x70, 0xA7, 0x3F, 0x7F, 0x37, 0x6B, 0x60, 0x07, 0x42, 0x48, 0x90,
       0x45, 0x34, 0xB1, 0x14, 0x82, 0xD5, 0xBF, 0x0E, 0x69, 0x8E, 0xCC,
       0x49, 0x8D, 0xF5, 0x25, 0x77, 0xEB, 0xF2, 0xE9, 0x3B, 0x9A },
     "MEExCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEcMBoGA1UEAwwT"
     "QWZmaXJtVHJ1c3QgUHJlbWl1bQ==",
     "bYwURrGmCu4=",
-    nullptr
   },
   {
     // CN=AffirmTrust Premium ECC,O=AffirmTrust,C=US
     "1.3.6.1.4.1.34697.2.4",
     "AffirmTrust EV OID d",
     SEC_OID_UNKNOWN,
     { 0xBD, 0x71, 0xFD, 0xF6, 0xDA, 0x97, 0xE4, 0xCF, 0x62, 0xD1, 0x64,
       0x7A, 0xDD, 0x25, 0x81, 0xB0, 0x7D, 0x79, 0xAD, 0xF8, 0x39, 0x7E,
       0xB4, 0xEC, 0xBA, 0x9C, 0x5E, 0x84, 0x88, 0x82, 0x14, 0x23 },
     "MEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwX"
     "QWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0M=",
     "dJclisc/elQ=",
-    nullptr
   },
   {
     // CN=Certum Trusted Network CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL
     "1.2.616.1.113527.2.5.1.1",
     "Certum EV OID",
     SEC_OID_UNKNOWN,
     { 0x5C, 0x58, 0x46, 0x8D, 0x55, 0xF5, 0x8E, 0x49, 0x7E, 0x74, 0x39,
       0x82, 0xD2, 0xB5, 0x00, 0x10, 0xB6, 0xD1, 0x65, 0x37, 0x4A, 0xCF,
       0x83, 0xA7, 0xD4, 0xA3, 0x2D, 0xB7, 0x68, 0xC4, 0x40, 0x8E },
     "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBT"
     "LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAg"
     "BgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0E=",
     "BETA",
-    nullptr
   },
   {
     // CN=Certum Trusted Network CA 2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL
     "1.2.616.1.113527.2.5.1.1",
     "Certum EV OID",
     SEC_OID_UNKNOWN,
     { 0xB6, 0x76, 0xF2, 0xED, 0xDA, 0xE8, 0x77, 0x5C, 0xD3, 0x6C, 0xB0,
       0xF6, 0x3C, 0xD1, 0xD4, 0x60, 0x39, 0x61, 0xF4, 0x9E, 0x62, 0x65,
       0xBA, 0x01, 0x3A, 0x2F, 0x03, 0x07, 0xB6, 0xD0, 0xB8, 0x04 },
     "MIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg"
     "Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQw"
     "IgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBIDI=",
     "IdbQSk8lD8kyN/yqXhKN6Q==",
-    nullptr
   },
   {
     // CN=Izenpe.com,O=IZENPE S.A.,C=ES
     "1.3.6.1.4.1.14777.6.1.1",
     "Izenpe EV OID 1",
     SEC_OID_UNKNOWN,
     { 0x25, 0x30, 0xCC, 0x8E, 0x98, 0x32, 0x15, 0x02, 0xBA, 0xD9, 0x6F,
       0x9B, 0x1F, 0xBA, 0x1B, 0x09, 0x9E, 0x2D, 0x29, 0x9E, 0x0F, 0x45,
       0x48, 0xBB, 0x91, 0x4F, 0x36, 0x3B, 0xC0, 0xD4, 0x53, 0x1F },
     "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwK"
     "SXplbnBlLmNvbQ==",
     "ALC3WhZIX7/hy/WL1xnmfQ==",
-    nullptr
   },
   {
     // CN=Izenpe.com,O=IZENPE S.A.,C=ES
     "1.3.6.1.4.1.14777.6.1.2",
     "Izenpe EV OID 2",
     SEC_OID_UNKNOWN,
     { 0x25, 0x30, 0xCC, 0x8E, 0x98, 0x32, 0x15, 0x02, 0xBA, 0xD9, 0x6F,
       0x9B, 0x1F, 0xBA, 0x1B, 0x09, 0x9E, 0x2D, 0x29, 0x9E, 0x0F, 0x45,
       0x48, 0xBB, 0x91, 0x4F, 0x36, 0x3B, 0xC0, 0xD4, 0x53, 0x1F },
     "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwK"
     "SXplbnBlLmNvbQ==",
     "ALC3WhZIX7/hy/WL1xnmfQ==",
-    nullptr
   },
   {
     // CN=T-TeleSec GlobalRoot Class 3,OU=T-Systems Trust Center,O=T-Systems Enterprise Services GmbH,C=DE
     "1.3.6.1.4.1.7879.13.24.1",
     "T-Systems EV OID",
     SEC_OID_UNKNOWN,
     { 0xFD, 0x73, 0xDA, 0xD3, 0x1C, 0x64, 0x4F, 0xF1, 0xB4, 0x3B, 0xEF,
       0x0C, 0xCD, 0xDA, 0x96, 0x71, 0x0B, 0x9C, 0xD9, 0x87, 0x5E, 0xCA,
       0x7E, 0x31, 0x70, 0x7A, 0xF3, 0xE9, 0x6D, 0x52, 0x2B, 0xBD },
     "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2Ug"
     "U2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjEl"
     "MCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMw==",
     "AQ==",
-    nullptr
   },
   {
     // CN=China Internet Network Information Center EV Certificates Root,O=China Internet Network Information Center,C=CN
     "1.3.6.1.4.1.29836.1.10",
     "CNNIC EV OID",
     SEC_OID_UNKNOWN,
     { 0x1C, 0x01, 0xC6, 0xF4, 0xDB, 0xB2, 0xFE, 0xFC, 0x22, 0x55, 0x8B,
       0x2B, 0xCA, 0x32, 0x56, 0x3F, 0x49, 0x84, 0x4A, 0xCF, 0xC3, 0x2B,
       0x7B, 0xE4, 0xB0, 0xFF, 0x59, 0x9F, 0x9E, 0x8C, 0x7A, 0xF7 },
     "MIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29y"
     "ayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNoaW5hIEludGVybmV0IE5l"
     "dHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRlcyBSb290",
     "SJ8AAQ==",
-    nullptr
   },
   {
     // CN=TWCA Root Certification Authority,OU=Root CA,O=TAIWAN-CA,C=TW
     "1.3.6.1.4.1.40869.1.1.22.3",
     "TWCA EV OID",
     SEC_OID_UNKNOWN,
     { 0xBF, 0xD8, 0x8F, 0xE1, 0x10, 0x1C, 0x41, 0xAE, 0x3E, 0x80, 0x1B,
       0xF8, 0xBE, 0x56, 0x35, 0x0E, 0xE9, 0xBA, 0xD1, 0xA6, 0xB9, 0xBD,
       0x51, 0x5E, 0xDC, 0x5C, 0x6D, 0x5B, 0x87, 0x11, 0xAC, 0x44 },
     "MF8xCzAJBgNVBAYTAlRXMRIwEAYDVQQKDAlUQUlXQU4tQ0ExEDAOBgNVBAsMB1Jv"
     "b3QgQ0ExKjAoBgNVBAMMIVRXQ0EgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0"
     "eQ==",
     "AQ==",
-    nullptr
   },
   {
     // CN=D-TRUST Root Class 3 CA 2 EV 2009,O=D-Trust GmbH,C=DE
     "1.3.6.1.4.1.4788.2.202.1",
     "D-TRUST EV OID",
     SEC_OID_UNKNOWN,
     { 0xEE, 0xC5, 0x49, 0x6B, 0x98, 0x8C, 0xE9, 0x86, 0x25, 0xB9, 0x34,
       0x09, 0x2E, 0xEC, 0x29, 0x08, 0xBE, 0xD0, 0xB0, 0xF3, 0x16, 0xC2,
       0xD4, 0x73, 0x0C, 0x84, 0xEA, 0xF1, 0xF3, 0xD3, 0x48, 0x81 },
     "MFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMM"
     "IUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOQ==",
     "CYP0",
-    nullptr
   },
   {
     // CN=Swisscom Root EV CA 2,OU=Digital Certificate Services,O=Swisscom,C=ch
     "2.16.756.1.83.21.0",
     "Swisscom  EV OID",
     SEC_OID_UNKNOWN,
     { 0xD9, 0x5F, 0xEA, 0x3C, 0xA4, 0xEE, 0xDC, 0xE7, 0x4C, 0xD7, 0x6E,
       0x75, 0xFC, 0x6D, 0x1F, 0xF6, 0x2C, 0x44, 0x1F, 0x0F, 0xA8, 0xBC,
       0x77, 0xF0, 0x34, 0xB1, 0x9E, 0x5D, 0xB2, 0x58, 0x01, 0x5D },
     "MGcxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln"
     "aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEeMBwGA1UEAxMVU3dpc3Njb20gUm9v"
     "dCBFViBDQSAy",
     "APL6ZOJ0Y9ON/RAdBB92ylg=",
-    nullptr
   },
   {
     // CN=VeriSign Universal Root Certification Authority,OU="(c) 2008 VeriSign, Inc. - For authorized use only",OU=VeriSign Trust Network,O="VeriSign, Inc.",C=US
     "2.16.840.1.113733.1.7.23.6",
     "VeriSign EV OID",
     SEC_OID_UNKNOWN,
     { 0x23, 0x99, 0x56, 0x11, 0x27, 0xA5, 0x71, 0x25, 0xDE, 0x8C, 0xEF,
       0xEA, 0x61, 0x0D, 0xDF, 0x2F, 0xA0, 0x78, 0xB5, 0xC8, 0x06, 0x7F,
       0x4E, 0x82, 0x82, 0x90, 0xBF, 0xB8, 0x60, 0xE8, 0x4B, 0x3C },
     "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNV"
     "BAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZl"
     "cmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMT"
     "L1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5",
     "QBrEZCGzEyEDDrvkEhrFHQ==",
-    nullptr
   },
   {
     // CN=GeoTrust Primary Certification Authority - G3,OU=(c) 2008 GeoTrust Inc. - For authorized use only,O=GeoTrust Inc.,C=US
     "1.3.6.1.4.1.14370.1.6",
     "GeoTrust EV OID",
     SEC_OID_UNKNOWN,
     { 0xB4, 0x78, 0xB8, 0x12, 0x25, 0x0D, 0xF8, 0x78, 0x63, 0x5C, 0x2A,
       0xA7, 0xEC, 0x7D, 0x15, 0x5E, 0xAA, 0x62, 0x5E, 0xE8, 0x29, 0x16,
       0xE2, 0xCD, 0x29, 0x43, 0x61, 0x88, 0x6C, 0xD1, 0xFB, 0xD4 },
     "MIGYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjE5MDcGA1UE"
     "CxMwKGMpIDIwMDggR2VvVHJ1c3QgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBv"
     "bmx5MTYwNAYDVQQDEy1HZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0"
     "aG9yaXR5IC0gRzM=",
     "FaxulBmyeUtB9iepwxgPHw==",
-    nullptr
   },
   {
     // CN=thawte Primary Root CA - G3,OU="(c) 2008 thawte, Inc. - For authorized use only",OU=Certification Services Division,O="thawte, Inc.",C=US
     "2.16.840.1.113733.1.7.48.1",
     "Thawte EV OID",
     SEC_OID_UNKNOWN,
     { 0x4B, 0x03, 0xF4, 0x58, 0x07, 0xAD, 0x70, 0xF2, 0x1B, 0xFC, 0x2C,
       0xAE, 0x71, 0xC9, 0xFD, 0xE4, 0x60, 0x4C, 0x06, 0x4C, 0xF5, 0xFF,
       0xB6, 0x86, 0xBA, 0xE5, 0xDB, 0xAA, 0xD7, 0xFD, 0xD3, 0x4C },
     "MIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMSgwJgYDVQQL"
     "Ex9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYDVQQLEy8oYykg"
     "MjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG"
     "A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz",
     "YAGXt0an6rS0mtZLL/eQ+w==",
-    nullptr
   },
   {
     // CN = Autoridad de Certificacion Firmaprofesional CIF A62634068, C = ES
     "1.3.6.1.4.1.13177.10.1.3.10",
     "Firmaprofesional EV OID",
     SEC_OID_UNKNOWN,
     { 0x04, 0x04, 0x80, 0x28, 0xBF, 0x1F, 0x28, 0x64, 0xD4, 0x8F, 0x9A,
       0xD4, 0xD8, 0x32, 0x94, 0x36, 0x6A, 0x82, 0x88, 0x56, 0x55, 0x3F,
       0x3B, 0x14, 0x30, 0x3F, 0x90, 0x14, 0x7F, 0x5D, 0x40, 0xEF },
     "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNh"
     "Y2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=",
     "U+w77vuySF8=",
-    nullptr
   },
   {
     // CN = TWCA Global Root CA, OU = Root CA, O = TAIWAN-CA, C = TW
     "1.3.6.1.4.1.40869.1.1.22.3",
     "TWCA EV OID",
     SEC_OID_UNKNOWN,
     { 0x59, 0x76, 0x90, 0x07, 0xF7, 0x68, 0x5D, 0x0F, 0xCD, 0x50, 0x87,
       0x2F, 0x9F, 0x95, 0xD5, 0x75, 0x5A, 0x5B, 0x2B, 0x45, 0x7D, 0x81,
       0xF3, 0x69, 0x2B, 0x61, 0x0A, 0x98, 0x67, 0x2F, 0x0E, 0x1B },
     "MFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jv"
     "b3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0E=",
     "DL4=",
-    nullptr
   },
   {
     // CN = E-Tugra Certification Authority, OU = E-Tugra Sertifikasyon Merkezi, O = E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş., L = Ankara, C = TR
     "2.16.792.3.0.4.1.1.4",
     "ETugra EV OID",
     SEC_OID_UNKNOWN,
     { 0xB0, 0xBF, 0xD5, 0x2B, 0xB0, 0xD7, 0xD9, 0xBD, 0x92, 0xBF, 0x5D,
       0x4D, 0xC1, 0x3D, 0xA2, 0x55, 0xC0, 0x2C, 0x54, 0x2F, 0x37, 0x83,
       0x65, 0xEA, 0x89, 0x39, 0x11, 0xF5, 0x5E, 0x55, 0xF2, 0x3C },
     "MIGyMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMUAwPgYDVQQKDDdFLVR1"
     "xJ9yYSBFQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEu"
     "xZ4uMSYwJAYDVQQLDB1FLVR1Z3JhIFNlcnRpZmlrYXN5b24gTWVya2V6aTEoMCYG"
     "A1UEAwwfRS1UdWdyYSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "amg+nFGby1M=",
-    nullptr
   },
   {
     // CN=Actalis Authentication Root CA,O=Actalis S.p.A./03358520967,L=Milan,C=IT
     "1.3.159.1.17.1",
     "Actalis EV OID",
     SEC_OID_UNKNOWN,
     { 0x55, 0x92, 0x60, 0x84, 0xEC, 0x96, 0x3A, 0x64, 0xB9, 0x6E, 0x2A,
       0xBE, 0x01, 0xCE, 0x0B, 0xA8, 0x6A, 0x64, 0xFB, 0xFE, 0xBC, 0xC7,
       0xAA, 0xB5, 0xAF, 0xC1, 0x55, 0xB3, 0x7F, 0xD7, 0x60, 0x66 },
     "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxp"
     "cyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGlj"
     "YXRpb24gUm9vdCBDQQ==",
     "VwoRl0LE48w=",
-    nullptr
   },
   {
     // CN=Certification Authority of WoSign,O=WoSign CA Limited,C=CN
     "1.3.6.1.4.1.36305.2",
     "WoSign EV OID",
     SEC_OID_UNKNOWN,
     { 0x4B, 0x22, 0xD5, 0xA6, 0xAE, 0xC9, 0x9F, 0x3C, 0xDB, 0x79, 0xAA,
       0x5E, 0xC0, 0x68, 0x38, 0x47, 0x9C, 0xD5, 0xEC, 0xBA, 0x71, 0x64,
       0xF7, 0xF2, 0x2D, 0xC1, 0xD6, 0x5F, 0x63, 0xD8, 0x57, 0x08 },
     "MFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEqMCgG"
     "A1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgb2YgV29TaWdu",
     "XmjWEXGUY1BWAGjzPsnFkQ==",
-    nullptr
   },
   {
     // CN=CA ...............,O=WoSign CA Limited,C=CN
     "1.3.6.1.4.1.36305.2",
     "WoSign EV OID",
     SEC_OID_UNKNOWN,
     { 0xD6, 0xF0, 0x34, 0xBD, 0x94, 0xAA, 0x23, 0x3F, 0x02, 0x97, 0xEC,
       0xA4, 0x24, 0x5B, 0x28, 0x39, 0x73, 0xE4, 0x47, 0xAA, 0x59, 0x0F,
       0x31, 0x0C, 0x77, 0xF4, 0x8F, 0xDF, 0x83, 0x11, 0x22, 0x54 },
     "MEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkG"
     "A1UEAwwSQ0Eg5rKD6YCa5qC56K+B5Lmm",
     "UHBrzdgT/BtOOzNy0hFIjQ==",
-    nullptr
   },
   {
     // CN=DigiCert Assured ID Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US
     "2.16.840.1.114412.2.1",
     "DigiCert EV OID",
     SEC_OID_UNKNOWN,
     { 0x7D, 0x05, 0xEB, 0xB6, 0x82, 0x33, 0x9F, 0x8C, 0x94, 0x51, 0xEE,
       0x09, 0x4E, 0xEB, 0xFE, 0xFA, 0x79, 0x53, 0xA1, 0x14, 0xED, 0xB2,
       0xF4, 0x49, 0x49, 0x45, 0x2F, 0xAB, 0x7D, 0x2F, 0xC1, 0x85 },
     "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT"
     "EHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQg"
     "Um9vdCBHMg==",
     "C5McOtY5Z+pnI7/Dr5r0Sw==",
-    nullptr
   },
   {
     // CN=DigiCert Assured ID Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US
     "2.16.840.1.114412.2.1",
     "DigiCert EV OID",
     SEC_OID_UNKNOWN,
     { 0x7E, 0x37, 0xCB, 0x8B, 0x4C, 0x47, 0x09, 0x0C, 0xAB, 0x36, 0x55,
       0x1B, 0xA6, 0xF4, 0x5D, 0xB8, 0x40, 0x68, 0x0F, 0xBA, 0x16, 0x6A,
       0x95, 0x2D, 0xB1, 0x00, 0x71, 0x7F, 0x43, 0x05, 0x3F, 0xC2 },
     "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT"
     "EHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQg"
     "Um9vdCBHMw==",
     "C6Fa+h3foLVJRK/NJKBs7A==",
-    nullptr,
   },
   {
     // CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US
     "2.16.840.1.114412.2.1",
     "DigiCert EV OID",
      SEC_OID_UNKNOWN,
     { 0xCB, 0x3C, 0xCB, 0xB7, 0x60, 0x31, 0xE5, 0xE0, 0x13, 0x8F, 0x8D,
       0xD3, 0x9A, 0x23, 0xF9, 0xDE, 0x47, 0xFF, 0xC3, 0x5E, 0x43, 0xC1,
       0x14, 0x4C, 0xEA, 0x27, 0xD4, 0x6A, 0x5A, 0xB1, 0xCB, 0x5F },
     "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT"
     "EHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290"
     "IEcy",
     "Azrx5qcRqaC7KGSxHQn65Q==",
-    nullptr,
   },
   {
     // CN=DigiCert Global Root G3,OU=www.digicert.com,O=DigiCert Inc,C=US
     "2.16.840.1.114412.2.1",
     "DigiCert EV OID",
     SEC_OID_UNKNOWN,
     { 0x31, 0xAD, 0x66, 0x48, 0xF8, 0x10, 0x41, 0x38, 0xC7, 0x38, 0xF3,
       0x9E, 0xA4, 0x32, 0x01, 0x33, 0x39, 0x3E, 0x3A, 0x18, 0xCC, 0x02,
       0x29, 0x6E, 0xF9, 0x7C, 0x2A, 0xC9, 0xEF, 0x67, 0x31, 0xD0 },
     "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT"
     "EHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290"
     "IEcz",
     "BVVWvPJepDU1w6QP1atFcg==",
-    nullptr
   },
   {
     // CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US
     "2.16.840.1.114412.2.1",
     "DigiCert EV OID",
     SEC_OID_UNKNOWN,
     { 0x55, 0x2F, 0x7B, 0xDC, 0xF1, 0xA7, 0xAF, 0x9E, 0x6C, 0xE6, 0x72,
       0x01, 0x7F, 0x4F, 0x12, 0xAB, 0xF7, 0x72, 0x40, 0xC7, 0x8E, 0x76,
       0x1A, 0xC2, 0x03, 0xD1, 0xD9, 0xD2, 0x0A, 0xC8, 0x99, 0x88 },
     "MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT"
     "EHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9v"
     "dCBHNA==",
     "BZsbV56OITLiOQe9p3d1XA==",
-    nullptr
   },
   {
     // CN=QuoVadis Root CA 2 G3,O=QuoVadis Limited,C=BM
     "1.3.6.1.4.1.8024.0.2.100.1.2",
     "QuoVadis EV OID",
     SEC_OID_UNKNOWN,
     { 0x8F, 0xE4, 0xFB, 0x0A, 0xF9, 0x3A, 0x4D, 0x0D, 0x67, 0xDB, 0x0B,
       0xEB, 0xB2, 0x3E, 0x37, 0xC7, 0x1B, 0xF3, 0x25, 0xDC, 0xBC, 0xDD,
       0x24, 0x0E, 0xA0, 0x4D, 0xAF, 0x58, 0xB4, 0x7E, 0x18, 0x40 },
     "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYD"
     "VQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=",
     "RFc0JFuBiZs18s64KztbpybwdSg=",
-    nullptr
   },
   {
     // CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
     "1.3.6.1.4.1.6449.1.2.1.5.1",
     "Comodo EV OID",
     SEC_OID_UNKNOWN,
     { 0x52, 0xF0, 0xE1, 0xC4, 0xE5, 0x8E, 0xC6, 0x29, 0x29, 0x1B, 0x60,
       0x31, 0x7F, 0x07, 0x46, 0x71, 0xB8, 0x5D, 0x7E, 0xA8, 0x0D, 0x5B,
       0x07, 0x27, 0x34, 0x63, 0x53, 0x4B, 0x32, 0xB4, 0x02, 0x34 },
     "MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAw"
     "DgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkG"
     "A1UEAxMiQ09NT0RPIFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "TKr5yttjb+Af907YWwOGnQ==",
-    nullptr
   },
   {
     // CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US
     "1.3.6.1.4.1.6449.1.2.1.5.1",
     "Comodo EV OID",
     SEC_OID_UNKNOWN,
     { 0xE7, 0x93, 0xC9, 0xB0, 0x2F, 0xD8, 0xAA, 0x13, 0xE2, 0x1C, 0x31,
       0x22, 0x8A, 0xCC, 0xB0, 0x81, 0x19, 0x64, 0x3B, 0x74, 0x9C, 0x89,
       0x89, 0x64, 0xB1, 0x74, 0x6D, 0x46, 0xC3, 0xD4, 0xCB, 0xD2 },
     "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxML"
     "SmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwG"
     "A1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "Af1tMPyjylGoG7xkDjUDLQ==",
-    nullptr
   },
   {
     // CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US
     "1.3.6.1.4.1.6449.1.2.1.5.1",
     "Comodo EV OID",
     SEC_OID_UNKNOWN,
     { 0x4F, 0xF4, 0x60, 0xD5, 0x4B, 0x9C, 0x86, 0xDA, 0xBF, 0xBC, 0xFC,
       0x57, 0x12, 0xE0, 0x40, 0x0D, 0x2B, 0xED, 0x3F, 0xBC, 0x4D, 0x4F,
       0xBD, 0xAA, 0x86, 0xE0, 0x6A, 0xDC, 0xD2, 0xA9, 0xAD, 0x7A },
     "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxML"
     "SmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwG"
     "A1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==",
     "XIuZxVqUxdJxVt7NiYDMJg==",
-    nullptr
   },
   {
     // CN=GlobalSign,O=GlobalSign,OU=GlobalSign ECC Root CA - R4
     "1.3.6.1.4.1.4146.1.1",
     "GlobalSign EV OID",
     SEC_OID_UNKNOWN,
     { 0xBE, 0xC9, 0x49, 0x11, 0xC2, 0x95, 0x56, 0x76, 0xDB, 0x6C, 0x0A,
       0x55, 0x09, 0x86, 0xD7, 0x6E, 0x3B, 0xA0, 0x05, 0x66, 0x7C, 0x44,
       0x2C, 0x97, 0x62, 0xB4, 0xFB, 0xB7, 0x73, 0xDE, 0x22, 0x8C },
     "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UE"
     "ChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==",
     "KjikHJYKBN5CsiilC+g0mAI=",
-    nullptr
   },
   {
     // CN=GlobalSign,O=GlobalSign,OU=GlobalSign ECC Root CA - R5
     "1.3.6.1.4.1.4146.1.1",
     "GlobalSign EV OID",
     SEC_OID_UNKNOWN,
     { 0x17, 0x9F, 0xBC, 0x14, 0x8A, 0x3D, 0xD0, 0x0F, 0xD2, 0x4E, 0xA1,
       0x34, 0x58, 0xCC, 0x43, 0xBF, 0xA7, 0xF5, 0x9C, 0x81, 0x82, 0xD7,
       0x83, 0xA5, 0x13, 0xF6, 0xEB, 0xEC, 0x10, 0x0C, 0x89, 0x24 },
     "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UE"
     "ChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==",
     "YFlJ4CYuu1X5CneKcflK2Gw=",
-    nullptr
   },
   {
     // CN=Entrust.net Certification Authority (2048),OU=(c) 1999 Entrust.net Limited,OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.),O=Entrust.net
     "2.16.840.1.114028.10.1.2",
     "Entrust EV OID",
     SEC_OID_UNKNOWN,
     { 0x6D, 0xC4, 0x71, 0x72, 0xE0, 0x1C, 0xBC, 0xB0, 0xBF, 0x62, 0x58,
       0x0D, 0x89, 0x5F, 0xE2, 0xB8, 0xAC, 0x9A, 0xD4, 0xF8, 0x73, 0x80,
       0x1E, 0x0C, 0x10, 0xB9, 0xC8, 0x37, 0xD2, 0x1E, 0xB1, 0x77 },
     "MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3Qu"
     "bmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMG"
     "A1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50"
     "cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp",
     "OGPe+A==",
-    nullptr
   },
   {
     // CN=Staat der Nederlanden EV Root CA,O=Staat der Nederlanden,C=NL
     "2.16.528.1.1003.1.2.7",
     "Staat der Nederlanden EV OID",
     SEC_OID_UNKNOWN,
     { 0x4D, 0x24, 0x91, 0x41, 0x4C, 0xFE, 0x95, 0x67, 0x46, 0xEC, 0x4C,
       0xEF, 0xA6, 0xCF, 0x6F, 0x72, 0xE2, 0x8A, 0x13, 0x29, 0x43, 0x2F,
       0x9D, 0x8A, 0x90, 0x7A, 0xC4, 0xCB, 0x5D, 0xAD, 0xC1, 0x5A },
     "MFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4x"
     "KTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBSb290IENB",
     "AJiWjQ==",
-    nullptr
   },
   {
     // CN=Entrust Root Certification Authority - G2,OU="(c) 2009 Entrust, Inc. - for authorized use only",OU=See www.entrust.net/legal-terms,O="Entrust, Inc.",C=US
     "2.16.840.1.114028.10.1.2",
     "Entrust EV OID",
     SEC_OID_UNKNOWN,
     { 0x43, 0xDF, 0x57, 0x74, 0xB0, 0x3E, 0x7F, 0xEF, 0x5F, 0xE4, 0x0D,
       0x93, 0x1A, 0x7B, 0xED, 0xF1, 0xBB, 0x2E, 0x6B, 0x42, 0x73, 0x8C,
       0x4E, 0x6D, 0x38, 0x41, 0x10, 0x3D, 0x3A, 0xA7, 0xF3, 0x39 },
     "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UE"
     "CxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMp"
     "IDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIw"
     "MAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH"
     "Mg==",
     "SlOMKA==",
-    nullptr
   },
   {
     // CN=Entrust Root Certification Authority - EC1,OU="(c) 2012 Entrust, Inc. - for authorized use only",OU=See www.entrust.net/legal-terms,O="Entrust, Inc.",C=US
     "2.16.840.1.114028.10.1.2",
     "Entrust EV OID",
     SEC_OID_UNKNOWN,
     { 0x02, 0xED, 0x0E, 0xB2, 0x8C, 0x14, 0xDA, 0x45, 0x16, 0x5C, 0x56,
       0x67, 0x91, 0x70, 0x0D, 0x64, 0x51, 0xD7, 0xFB, 0x56, 0xF0, 0xB2,
       0xAB, 0x1D, 0x3B, 0x8E, 0xB0, 0x70, 0xE5, 0x6E, 0xDF, 0xF5 },
     "MIG/MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UE"
     "CxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMp"
     "IDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTMw"
     "MQYDVQQDEypFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBF"
     "QzE=",
     "AKaLeSkAAAAAUNCR+Q==",
-    nullptr
   },
   {
     // CN=CFCA EV ROOT,O=China Financial Certification Authority,C=CN
     "2.16.156.112554.3",
     "CFCA EV OID",
     SEC_OID_UNKNOWN,
     { 0x5C, 0xC3, 0xD7, 0x8E, 0x4E, 0x1D, 0x5E, 0x45, 0x54, 0x7A, 0x04,
       0xE6, 0x87, 0x3E, 0x64, 0xF9, 0x0C, 0xF9, 0x53, 0x6D, 0x1C, 0xCC,
       0x2E, 0xF8, 0x00, 0xF3, 0x55, 0xC4, 0xC5, 0xFD, 0x70, 0xFD },
     "MFYxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlm"
     "aWNhdGlvbiBBdXRob3JpdHkxFTATBgNVBAMMDENGQ0EgRVYgUk9PVA==",
     "GErM1g==",
-    nullptr
   },
   {
     // CN=Certification Authority of WoSign G2,O=WoSign CA Limited,C=CN
     "1.3.6.1.4.1.36305.2",
     "WoSign EV OID",
     SEC_OID_UNKNOWN,
     { 0xD4, 0x87, 0xA5, 0x6F, 0x83, 0xB0, 0x74, 0x82, 0xE8, 0x5E, 0x96,
       0x33, 0x94, 0xC1, 0xEC, 0xC2, 0xC9, 0xE5, 0x1D, 0x09, 0x03, 0xEE,
       0x94, 0x6B, 0x02, 0xC3, 0x01, 0x58, 0x1E, 0xD9, 0x9E, 0x16 },
     "MFgxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEtMCsG"
     "A1UEAxMkQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgb2YgV29TaWduIEcy",
     "ayXaioidfLwPBbOxemFFRA==",
-    nullptr
   },
   {
     // CN=CA WoSign ECC Root,O=WoSign CA Limited,C=CN
     "1.3.6.1.4.1.36305.2",
     "WoSign EV OID",
     SEC_OID_UNKNOWN,
     { 0x8B, 0x45, 0xDA, 0x1C, 0x06, 0xF7, 0x91, 0xEB, 0x0C, 0xAB, 0xF2,
       0x6B, 0xE5, 0x88, 0xF5, 0xFB, 0x23, 0x16, 0x5C, 0x2E, 0x61, 0x4B,
       0xF8, 0x85, 0x56, 0x2D, 0x0D, 0xCE, 0x50, 0xB2, 0x9B, 0x02 },
     "MEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkG"
     "A1UEAxMSQ0EgV29TaWduIEVDQyBSb290",
     "aEpYcIBr8I8C+vbe6LCQkA==",
-    nullptr
   },
   {
     // CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6,O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A...,L=Ankara,C=TR
     "2.16.792.3.0.3.1.1.5",
     "TurkTrust EV OID",
     SEC_OID_UNKNOWN,
     { 0x8D, 0xE7, 0x86, 0x55, 0xE1, 0xBE, 0x7F, 0x78, 0x47, 0x80, 0x0B,
       0x93, 0xF6, 0x94, 0xD2, 0x1D, 0x36, 0x8C, 0xC0, 0x6E, 0x03, 0x3E,
       0x7F, 0xAB, 0x04, 0xBB, 0x5E, 0xB9, 0x9D, 0xA6, 0xB7, 0x00 },
     "MIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xS"
     "S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg"
     "SGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlr"
     "IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2",
     "faHyZeyK",
-    nullptr
   },
   {
     // OU=Security Communication RootCA2,O="SECOM Trust Systems CO.,LTD.",C=JP
     "1.2.392.200091.100.721.1",
     "SECOM EV OID",
     SEC_OID_UNKNOWN,
     { 0x51, 0x3B, 0x2C, 0xEC, 0xB8, 0x10, 0xD4, 0xCD, 0xE5, 0xDD, 0x85,
       0x39, 0x1A, 0xDF, 0xC6, 0xC2, 0xDD, 0x60, 0xD8, 0x7B, 0xB7, 0x36,
       0xD2, 0xB5, 0x21, 0x48, 0x4A, 0xA4, 0x7A, 0x0E, 0xBE, 0xF6 },
     "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENP"
     "LixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=",
     "AA==",
-    nullptr
   },
   {
     // CN=OISTE WISeKey Global Root GB CA,OU=OISTE Foundation Endorsed,O=WISeKey,C=CH
     "2.16.756.5.14.7.4.8",
     "WISeKey EV OID",
     SEC_OID_UNKNOWN,
     { 0x6B, 0x9C, 0x08, 0xE8, 0x6E, 0xB0, 0xF7, 0x67, 0xCF, 0xAD, 0x65,
       0xCD, 0x98, 0xB6, 0x21, 0x49, 0xE5, 0x49, 0x4A, 0x67, 0xF5, 0x84,
       0x5E, 0x7B, 0xD1, 0xED, 0x01, 0x9F, 0x27, 0xB8, 0x6B, 0xD6 },
     "MG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNU"
     "RSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds"
     "b2JhbCBSb290IEdCIENB",
     "drEgUnTwhYdGs/gjGvbCwA==",
-    nullptr
   },
   {
     // CN=Certplus Root CA G1,O=Certplus,C=FR
     "1.3.6.1.4.1.22234.3.5.3.1",
     "DocuSign EV OID 1",
     SEC_OID_UNKNOWN,
     { 0x15, 0x2A, 0x40, 0x2B, 0xFC, 0xDF, 0x2C, 0xD5, 0x48, 0x05, 0x4D,
       0x22, 0x75, 0xB3, 0x9C, 0x7F, 0xCA, 0x3E, 0xC0, 0x97, 0x80, 0x78,
       0xB0, 0xF0, 0xEA, 0x76, 0xE5, 0x61, 0xA6, 0xC7, 0x43, 0x3E },
     "MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy"
     "dHBsdXMgUm9vdCBDQSBHMQ==",
     "ESBVg+QtPlRWhS2DN7cs3EYR",
-    nullptr
   },
   {
     // CN=Certplus Root CA G2,O=Certplus,C=FR
     "1.3.6.1.4.1.22234.3.5.3.2",
     "DocuSign EV OID 2",
     SEC_OID_UNKNOWN,
     { 0x6C, 0xC0, 0x50, 0x41, 0xE6, 0x44, 0x5E, 0x74, 0x69, 0x6C, 0x4C,
       0xFB, 0xC9, 0xF8, 0x0F, 0x54, 0x3B, 0x7E, 0xAB, 0xBB, 0x44, 0xB4,
       0xCE, 0x6F, 0x78, 0x7C, 0x6A, 0x99, 0x71, 0xC4, 0x2F, 0x17 },
     "MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy"
     "dHBsdXMgUm9vdCBDQSBHMg==",
     "ESDZkc6uo+jF5//pAq/Pc7xV",
-    nullptr
   },
   {
     // CN=OpenTrust Root CA G1,O=OpenTrust,C=FR
     "1.3.6.1.4.1.22234.2.14.3.11",
     "DocuSign EV OID 3",
     SEC_OID_UNKNOWN,
     { 0x56, 0xC7, 0x71, 0x28, 0xD9, 0x8C, 0x18, 0xD9, 0x1B, 0x4C, 0xFD,
       0xFF, 0xBC, 0x25, 0xEE, 0x91, 0x03, 0xD4, 0x75, 0x8E, 0xA2, 0xAB,
       0xAD, 0x82, 0x6A, 0x90, 0xF3, 0x45, 0x7D, 0x46, 0x0E, 0xB4 },
     "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w"
     "ZW5UcnVzdCBSb290IENBIEcx",
     "ESCzkFU5fX82bWTCp59rY45n",
-    nullptr
   },
   {
     // CN=OpenTrust Root CA G2,O=OpenTrust,C=FR
     "1.3.6.1.4.1.22234.2.14.3.11",
     "DocuSign EV OID 3",
     SEC_OID_UNKNOWN,
     { 0x27, 0x99, 0x58, 0x29, 0xFE, 0x6A, 0x75, 0x15, 0xC1, 0xBF, 0xE8,
       0x48, 0xF9, 0xC4, 0x76, 0x1D, 0xB1, 0x6C, 0x22, 0x59, 0x29, 0x25,
       0x7B, 0xF4, 0x0D, 0x08, 0x94, 0xF2, 0x9E, 0xA8, 0xBA, 0xF2 },
     "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w"
     "ZW5UcnVzdCBSb290IENBIEcy",
     "ESChaRu/vbm9UpaPI+hIvyYR",
-    nullptr
   },
   {
     // CN=OpenTrust Root CA G3,O=OpenTrust,C=FR
     "1.3.6.1.4.1.22234.2.14.3.11",
     "DocuSign EV OID 3",
     SEC_OID_UNKNOWN,
     { 0xB7, 0xC3, 0x62, 0x31, 0x70, 0x6E, 0x81, 0x07, 0x8C, 0x36, 0x7C,
       0xB8, 0x96, 0x19, 0x8F, 0x1E, 0x32, 0x08, 0xDD, 0x92, 0x69, 0x49,
       0xDD, 0x8F, 0x57, 0x09, 0xA4, 0x10, 0xF7, 0x5B, 0x62, 0x92 },
     "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w"
     "ZW5UcnVzdCBSb290IENBIEcz",
     "ESDm+Ez8JLC+BUCs2oMbNGA/",
-    nullptr
   },
   {
     // CN=VeriSign Class 3 Public Primary Certification Authority - G4,OU="(c) 2007 VeriSign, Inc. - For authorized use only",OU=VeriSign Trust Network,O="VeriSign, Inc.",C=US
     "2.16.840.1.113733.1.7.23.6",
     "VeriSign EV OID",
     SEC_OID_UNKNOWN,
     { 0x69, 0xDD, 0xD7, 0xEA, 0x90, 0xBB, 0x57, 0xC9, 0x3E, 0x13, 0x5D,
       0xC8, 0x5E, 0xA6, 0xFC, 0xD5, 0x48, 0x0B, 0x60, 0x32, 0x39, 0xBD,
       0xC4, 0x54, 0xFC, 0x75, 0x8B, 0x2A, 0x26, 0xCF, 0x7F, 0x79 },
     "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNV"
     "BAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA3IFZl"
     "cmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMT"
     "PFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBB"
     "dXRob3JpdHkgLSBHNA==",
     "L4D+I4wOIg9IZxIokYessw==",
-    nullptr
   },
 };
 
 static SECOidTag
 RegisterOID(const SECItem& oidItem, const char* oidName)
 {
   SECOidData od;
   od.oid.len = oidItem.len;
@@ -1296,29 +1213,41 @@ bool
 CertIsAuthoritativeForEVPolicy(const UniqueCERTCertificate& cert,
                                const mozilla::pkix::CertPolicyId& policy)
 {
   PR_ASSERT(cert);
   if (!cert) {
     return false;
   }
 
+  unsigned char fingerprint[SHA256_LENGTH];
+  SECStatus srv =
+    PK11_HashBuf(SEC_OID_SHA256, fingerprint, cert->derCert.data,
+                 AssertedCast<int32_t>(cert->derCert.len));
+  if (srv != SECSuccess) {
+    return false;
+  }
+
   const SECOidData* cabforumOIDData = SECOID_FindOIDByTag(sCABForumEVOIDTag);
   for (const nsMyTrustedEVInfo& entry : myTrustedEVInfos) {
-    if (entry.cert && CERT_CompareCerts(cert.get(), entry.cert.get())) {
-      if (cabforumOIDData && cabforumOIDData->oid.len == policy.numBytes &&
-          mozilla::PodEqual(cabforumOIDData->oid.data, policy.bytes,
-                            policy.numBytes)) {
-        return true;
-      }
-      const SECOidData* oidData = SECOID_FindOIDByTag(entry.oid_tag);
-      if (oidData && oidData->oid.len == policy.numBytes &&
-          mozilla::PodEqual(oidData->oid.data, policy.bytes, policy.numBytes)) {
-        return true;
-      }
+    // This check ensures that only the specific roots we approve for EV get
+    // that status, and not certs (roots or otherwise) that happen to have an
+    // OID that's already been approved for EV.
+    if (!PodEqual(fingerprint, entry.ev_root_sha256_fingerprint)) {
+      continue;
+    }
+
+    if (cabforumOIDData && cabforumOIDData->oid.len == policy.numBytes &&
+        PodEqual(cabforumOIDData->oid.data, policy.bytes, policy.numBytes)) {
+      return true;
+    }
+    const SECOidData* oidData = SECOID_FindOIDByTag(entry.oid_tag);
+    if (oidData && oidData->oid.len == policy.numBytes &&
+        PodEqual(oidData->oid.data, policy.bytes, policy.numBytes)) {
+      return true;
     }
   }
 
   return false;
 }
 
 static PRStatus
 IdentityInfoInit()
@@ -1355,39 +1284,38 @@ IdentityInfoInit()
 
     CERTIssuerAndSN ias;
     ias.derIssuer.data = derIssuer.data;
     ias.derIssuer.len = derIssuer.len;
     ias.serialNumber.data = serialNumber.data;
     ias.serialNumber.len = serialNumber.len;
     ias.serialNumber.type = siUnsignedInteger;
 
-    entry.cert = UniqueCERTCertificate(CERT_FindCertByIssuerAndSN(nullptr, &ias));
+    UniqueCERTCertificate cert(CERT_FindCertByIssuerAndSN(nullptr, &ias));
 
     // If an entry is missing in the NSS root database, it may be because the
     // root database is out of sync with what we expect (e.g. a different
     // version of system NSS is installed). We assert on debug builds, but
     // silently continue on release builds. In both cases, the root cert does
     // not get EV treatment.
-    if (!entry.cert) {
+    if (!cert) {
 #ifdef DEBUG
       // The debug CA structs are at positions 0 to NUM_TEST_EV_ROOTS - 1, and
       // are NOT in the NSS root DB.
       if (iEV < NUM_TEST_EV_ROOTS) {
         continue;
       }
 #endif
       PR_NOT_REACHED("Could not find EV root in NSS storage");
       continue;
     }
 
     unsigned char certFingerprint[SHA256_LENGTH];
-    rv = PK11_HashBuf(SEC_OID_SHA256, certFingerprint,
-                      entry.cert->derCert.data,
-                      static_cast<int32_t>(entry.cert->derCert.len));
+    rv = PK11_HashBuf(SEC_OID_SHA256, certFingerprint, cert->derCert.data,
+                      AssertedCast<int32_t>(cert->derCert.len));
     PR_ASSERT(rv == SECSuccess);
     if (rv == SECSuccess) {
       bool same = !memcmp(certFingerprint, entry.ev_root_sha256_fingerprint,
                           sizeof(certFingerprint));
       PR_ASSERT(same);
       if (same) {
         mozilla::ScopedAutoSECItem evOIDItem;
         rv = SEC_StringToOID(nullptr, &evOIDItem, entry.dotted_oid, 0);
@@ -1400,17 +1328,16 @@ IdentityInfoInit()
         }
       } else {
         PR_SetError(SEC_ERROR_BAD_DATA, 0);
         rv = SECFailure;
       }
     }
 
     if (rv != SECSuccess) {
-      entry.cert = nullptr;
       entry.oid_tag = SEC_OID_UNKNOWN;
       return PR_FAILURE;
     }
   }
 
   return PR_SUCCESS;
 }
 
@@ -1420,20 +1347,16 @@ void
 EnsureIdentityInfoLoaded()
 {
   (void) PR_CallOnce(&sIdentityInfoCallOnce, IdentityInfoInit);
 }
 
 void
 CleanupIdentityInfo()
 {
-  for (nsMyTrustedEVInfo& entry : myTrustedEVInfos) {
-    entry.cert = nullptr;
-  }
-
   memset(&sIdentityInfoCallOnce, 0, sizeof(PRCallOnceType));
 }
 
 // Find the first policy OID that is known to be an EV policy OID.
 SECStatus
 GetFirstEVPolicy(CERTCertificate* cert,
                  /*out*/ mozilla::pkix::CertPolicyId& policy,
                  /*out*/ SECOidTag& policyOidTag)
--- a/testing/marionette/browser.js
+++ b/testing/marionette/browser.js
@@ -263,16 +263,26 @@ browser.Context = class {
   executeWhenReady(cb) {
     if (this.hasRemotenessChange()) {
       this.pendingCommands.push(cb);
     } else {
       cb();
     }
   }
 
+  /**
+   * Returns the position of the OS window.
+   */
+  get position() {
+    return {
+      x: this.window.screenX,
+      y: this.window.screenY,
+    };
+  }
+
 };
 
 /**
  * The window storage is used to save outer window IDs mapped to weak
  * references of Window objects.
  *
  * Usage:
  *
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1225,47 +1225,50 @@ GeckoDriver.prototype.getChromeWindowHan
   }
   resp.body = hs;
 };
 
 /**
  * Get the current window position.
  *
  * @return {Object.<string, number>}
- *     Object with x and y coordinates.
+ *     Object with |x| and |y| coordinates.
  */
 GeckoDriver.prototype.getWindowPosition = function(cmd, resp) {
-  let win = this.getCurrentWindow();
-  resp.body.x = win.screenX;
-  resp.body.y = win.screenY;
+  return this.curBrowser.position;
 };
 
 /**
  * Set the window position of the browser on the OS Window Manager
  *
  * @param {number} x
  *     X coordinate of the top/left of the window that it will be
  *     moved to.
  * @param {number} y
  *     Y coordinate of the top/left of the window that it will be
  *     moved to.
+ *
+ * @return {Object.<string, number>}
+ *     Object with |x| and |y| coordinates.
  */
 GeckoDriver.prototype.setWindowPosition = function(cmd, resp) {
   if (this.appName != "Firefox") {
-    throw new WebDriverError("Unable to set the window position on mobile");
+    throw new UnsupportedOperationError("Unable to set the window position on mobile");
   }
 
-  let x = parseInt(cmd.parameters.x);
-  let y  = parseInt(cmd.parameters.y);
-  if (isNaN(x) || isNaN(y)) {
-    throw new UnknownError("x and y arguments should be integers");
+  let {x, y} = cmd.parameters;
+  if (!Number.isInteger(x) || !Number.isInteger(y) ||
+      x < 0 || y < 0) {
+    throw new InvalidArgumentError();
   }
 
   let win = this.getCurrentWindow();
   win.moveTo(x, y);
+
+  return this.curBrowser.position;
 };
 
 /**
  * Switch current top-level browsing context by name or server-assigned ID.
  * Searches for windows by name, then ID.  Content windows take precedence.
  *
  * @param {string} name
  *     Target name or ID of the window to switch to.
--- a/testing/marionette/harness/marionette/tests/unit/test_window_position.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_window_position.py
@@ -8,27 +8,33 @@
 #
 #Unless required by applicable law or agreed to in writing, software
 #distributed under the License is distributed on an "AS IS" BASIS,
 #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 #See the License for the specific language governing permissions and
 #limitations under the License.
 
 from marionette import MarionetteTestCase
-from marionette_driver.errors import MarionetteException
+from marionette_driver.errors import InvalidArgumentException
 
 class TestWindowPosition(MarionetteTestCase):
-
-    def test_that_we_return_the_window_position(self):
+    def test_get_types(self):
         position = self.marionette.get_window_position()
-        self.assertTrue(isinstance(position['x'], int))
-        self.assertTrue(isinstance(position['y'], int))
+        self.assertTrue(isinstance(position["x"], int))
+        self.assertTrue(isinstance(position["y"], int))
+
+    def test_set_types(self):
+        for x, y in (["a", "b"], [1.2, 3.4], [True, False], [[], []], [{}, {}]):
+            with self.assertRaises(InvalidArgumentException):
+                self.marionette.set_window_position(x, y)
 
-    def test_that_we_can_set_the_window_position(self):
+    def test_out_of_bounds_arguments(self):
+        with self.assertRaises(InvalidArgumentException):
+            self.marionette.set_window_position(-1, 0)
+        with self.assertRaises(InvalidArgumentException):
+            self.marionette.set_window_position(0, -1)
+
+    def test_move(self):
         old_position = self.marionette.get_window_position()
-        new_position = {"x": old_position['x'] + 10, "y": old_position['y'] + 10}
-        self.marionette.set_window_position(new_position['x'], new_position['y'])
-        self.assertNotEqual(old_position['x'], new_position['x'])
-        self.assertNotEqual(old_position['y'], new_position['y'])
-
-    def test_that_we_can_get_an_error_when_passing_something_other_than_integers(self):
-        self.assertRaises(MarionetteException, self.marionette.set_window_position, "a","b")
-
+        new_position = {"x": old_position["x"] + 10, "y": old_position["y"] + 10}
+        self.marionette.set_window_position(new_position["x"], new_position["y"])
+        self.assertNotEqual(old_position['x'], new_position["x"])
+        self.assertNotEqual(old_position['y'], new_position["y"])
deleted file mode 100644
--- a/testing/web-platform/meta/encrypted-media/Google/encrypted-media-keystatuses.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[encrypted-media-keystatuses.html]
-  type: testharness
-  [Verify MediaKeySession.keyStatuses.]
-    expected: FAIL
-
--- a/toolkit/components/extensions/schemas/manifest.json
+++ b/toolkit/components/extensions/schemas/manifest.json
@@ -53,17 +53,17 @@
           },
 
           "description": {
             "type": "string",
             "optional": true,
             "preprocess": "localize"
           },
 
-          "creator": {
+          "author": {
             "type": "string",
             "optional": true,
             "preprocess": "localize"
           },
 
           "version": {
             "type": "string",
             "optional": false
@@ -175,17 +175,35 @@
             },
             "optional": true
           },
 
           "web_accessible_resources": {
             "type": "array",
             "items": { "type": "string" },
             "optional": true
+          },
+
+          "developer": {
+            "type": "object",
+            "optional": true,
+            "properties": {
+              "name": {
+                "type": "string",
+                "optional": true,
+                "preprocess": "localize"
+              },
+              "url": {
+                "type": "string",
+                "optional": true,
+                "preprocess": "localize"
+              }
+            }
           }
+
         },
 
         "additionalProperties": { "$ref": "UnrecognizedProperty" }
       },
       {
         "id": "Permission",
         "choices": [
           {
--- a/toolkit/components/satchel/test/test_form_autocomplete.html
+++ b/toolkit/components/satchel/test/test_form_autocomplete.html
@@ -162,17 +162,17 @@ function setupFormHistory(aCallback) {
     { op : "add", fieldname : "field5", value : "123" },
     { op : "add", fieldname : "field5", value : "1234" },
     { op : "add", fieldname : "field6", value : "value" },
     { op : "add", fieldname : "field7", value : "value" },
     { op : "add", fieldname : "field8", value : "value" },
     { op : "add", fieldname : "field9", value : "value" },
     { op : "add", fieldname : "field10", value : "42" },
     { op : "add", fieldname : "field11", value : "2010-10-10" },
-    { op : "add", fieldname : "field12", value : "21:21" },
+    { op : "add", fieldname : "field12", value : "21:21" }, // not used, since type=time doesn't have autocomplete currently
     { op : "add", fieldname : "field13", value : "32" },  // not used, since type=range doesn't have a drop down menu
     { op : "add", fieldname : "field14", value : "#ffffff" }, // not used, since type=color doesn't have autocomplete currently
     { op : "add", fieldname : "field15", value : "2016-08" },
     { op : "add", fieldname : "field16", value : "2016-W32" },
     { op : "add", fieldname : "searchbar-history", value : "blacklist test" },
   ], aCallback);
 }
 
@@ -908,25 +908,23 @@ function runTest() {
     case 405:
         checkMenuEntries(["2010-10-10"]);
         doKey("down");
         doKey("return");
         checkForm("2010-10-10");
 
         input = $_(15, "field12");
         restoreForm();
-        expectPopup();
-        doKey("down");
+        waitForMenuChange(0);
         break;
 
     case 406:
-        checkMenuEntries(["21:21"]);
-        doKey("down");
-        doKey("return");
-        checkForm("21:21");
+        checkMenuEntries([]); // type=time with it's own control frame does not
+                              // have a drop down menu for now
+        checkForm("");
 
         input = $_(16, "field13");
         restoreForm();
         doKey("down");
         waitForMenuChange(0);
         break;
 
     case 407:
--- a/toolkit/components/url-classifier/HashStore.cpp
+++ b/toolkit/components/url-classifier/HashStore.cpp
@@ -163,16 +163,36 @@ TableUpdateV2::NewSubComplete(uint32_t a
 }
 
 void
 TableUpdateV4::NewPrefixes(int32_t aSize, std::string& aPrefixes)
 {
   NS_ENSURE_TRUE_VOID(aPrefixes.size() % aSize == 0);
   NS_ENSURE_TRUE_VOID(!mPrefixesMap.Get(aSize));
 
+  if (LOG_ENABLED() && 4 == aSize) {
+    int numOfPrefixes = aPrefixes.size() / 4;
+    uint32_t* p = (uint32_t*)aPrefixes.c_str();
+
+    // Dump the first/last 10 fixed-length prefixes for debugging.
+    LOG(("* The first 10 (maximum) fixed-length prefixes: "));
+    for (int i = 0; i < std::min(10, numOfPrefixes); i++) {
+      uint8_t* c = (uint8_t*)&p[i];
+      LOG(("%.2X%.2X%.2X%.2X", c[0], c[1], c[2], c[3]));
+    }
+
+    LOG(("* The last 10 (maximum) fixed-length prefixes: "));
+    for (int i = std::max(0, numOfPrefixes - 10); i < numOfPrefixes; i++) {
+      uint8_t* c = (uint8_t*)&p[i];
+      LOG(("%.2X%.2X%.2X%.2X", c[0], c[1], c[2], c[3]));
+    }
+
+    LOG(("---- %d fixed-length prefixes in total.", aPrefixes.size() / aSize));
+  }
+
   PrefixStdString* prefix = new PrefixStdString(aPrefixes);
   mPrefixesMap.Put(aSize, prefix);
 }
 
 void
 TableUpdateV4::NewRemovalIndices(const uint32_t* aIndices, size_t aNumOfIndices)
 {
   for (size_t i = 0; i < aNumOfIndices; i++) {
--- a/toolkit/components/url-classifier/ProtocolParser.cpp
+++ b/toolkit/components/url-classifier/ProtocolParser.cpp
@@ -8,16 +8,18 @@
 #include "nsNetCID.h"
 #include "mozilla/Logging.h"
 #include "prnetdb.h"
 #include "prprf.h"
 
 #include "nsUrlClassifierUtils.h"
 #include "nsPrintfCString.h"
 #include "mozilla/Base64.h"
+#include "RiceDeltaDecoder.h"
+#include "mozilla/EndianUtils.h"
 
 // MOZ_LOG=UrlClassifierProtocolParser:5
 mozilla::LazyLogModule gUrlClassifierProtocolParserLog("UrlClassifierProtocolParser");
 #define PARSER_LOG(args) MOZ_LOG(gUrlClassifierProtocolParserLog, mozilla::LogLevel::Debug, args)
 
 namespace mozilla {
 namespace safebrowsing {
 
@@ -903,18 +905,18 @@ ProtocolParserProtobuf::ProcessAdditionO
       break;
 
     case RAW:
       ret = (aIsAddition ? ProcessRawAddition(aTableUpdate, update)
                          : ProcessRawRemoval(aTableUpdate, update));
       break;
 
     case RICE:
-      // Not implemented yet (see bug 1285848),
-      NS_WARNING("Encoded table update is not supported yet.");
+      ret = (aIsAddition ? ProcessEncodedAddition(aTableUpdate, update)
+                         : ProcessEncodedRemoval(aTableUpdate, update));
       break;
     }
   }
 
   return ret;
 }
 
 nsresult
@@ -972,11 +974,137 @@ ProtocolParserProtobuf::ProcessRawRemova
   PARSER_LOG(("  - # of removal: %d", indices.size()));
 
   aTableUpdate.NewRemovalIndices((const uint32_t*)indices.data(),
                                  indices.size());
 
   return NS_OK;
 }
 
+static nsresult
+DoRiceDeltaDecode(const RiceDeltaEncoding& aEncoding,
+                  nsTArray<uint32_t>& aDecoded)
+{
+  // Sanity check of the encoding info.
+  if (!aEncoding.has_first_value() ||
+      !aEncoding.has_rice_parameter() ||
+      !aEncoding.has_num_entries() ||
+      !aEncoding.has_encoded_data()) {
+    PARSER_LOG(("The encoding info is incomplete."));
+    return NS_ERROR_FAILURE;
+  }
+
+  PARSER_LOG(("* Encoding info:"));
+  PARSER_LOG(("  - First value: %d", aEncoding.first_value()));
+  PARSER_LOG(("  - Num of entries: %d", aEncoding.num_entries()));
+  PARSER_LOG(("  - Rice parameter: %d", aEncoding.rice_parameter()));
+
+  // Set up the input buffer. Note that the bits should be read
+  // from LSB to MSB so that we in-place reverse the bits before
+  // feeding to the decoder.
+  auto encoded = const_cast<RiceDeltaEncoding&>(aEncoding).mutable_encoded_data();
+  RiceDeltaDecoder decoder((uint8_t*)encoded->c_str(), encoded->size());
+
+  // Setup the output buffer. The "first value" is included in
+  // the output buffer.
+  aDecoded.SetLength(aEncoding.num_entries() + 1);
+  aDecoded[0] = aEncoding.first_value();
+
+  // Decode!
+  bool rv = decoder.Decode(aEncoding.rice_parameter(),
+                           aEncoding.first_value(), // first value.
+                           aEncoding.num_entries(), // # of entries (first value not included).
+                           &aDecoded[1]);
+
+  NS_ENSURE_TRUE(rv, NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+nsresult
+ProtocolParserProtobuf::ProcessEncodedAddition(TableUpdateV4& aTableUpdate,
+                                               const ThreatEntrySet& aAddition)
+{
+  if (!aAddition.has_rice_hashes()) {
+    PARSER_LOG(("* No rice encoded addition."));
+    return NS_OK;
+  }
+
+  nsTArray<uint32_t> decoded;
+  nsresult rv = DoRiceDeltaDecode(aAddition.rice_hashes(), decoded);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  //  Say we have the following raw prefixes
+  //                              BE            LE
+  //   00 00 00 01                 1      16777216
+  //   00 00 02 00               512        131072
+  //   00 03 00 00            196608           768
+  //   04 00 00 00          67108864             4
+  //
+  // which can be treated as uint32 (big-endian) sorted in increasing order:
+  //
+  // [1, 512, 196608, 67108864]
+  //
+  // According to https://developers.google.com/safe-browsing/v4/compression,
+  // the following should be done prior to compression:
+  //
+  // 1) re-interpret in little-endian ==> [16777216, 131072, 768, 4]
+  // 2) sort in increasing order       ==> [4, 768, 131072, 16777216]
+  //
+  // In order to get the original byte stream from |decoded|
+  // ([4, 768, 131072, 16777216] in this case), we have to:
+  //
+  // 1) sort in big-endian order      ==> [16777216, 131072, 768, 4]
+  // 2) copy each uint32 in little-endian to the result string
+  //
+
+  // The 4-byte prefixes have to be re-sorted in Big-endian increasing order.
+  struct CompareBigEndian
+  {
+    bool Equals(const uint32_t& aA, const uint32_t& aB) const
+    {
+      return aA == aB;
+    }
+
+    bool LessThan(const uint32_t& aA, const uint32_t& aB) const
+    {
+      return NativeEndian::swapToBigEndian(aA) <
+             NativeEndian::swapToBigEndian(aB);
+    }
+  };
+  decoded.Sort(CompareBigEndian());
+
+  // The encoded prefixes are always 4 bytes.
+  std::string prefixes;
+  for (size_t i = 0; i < decoded.Length(); i++) {
+    // Note that the third argument is the number of elements we want
+    // to copy (and swap) but not the number of bytes we want to copy.
+    char p[4];
+    NativeEndian::copyAndSwapToLittleEndian(p, &decoded[i], 1);
+    prefixes.append(p, 4);
+  }
+
+  aTableUpdate.NewPrefixes(4, prefixes);
+
+  return NS_OK;
+}
+
+nsresult
+ProtocolParserProtobuf::ProcessEncodedRemoval(TableUpdateV4& aTableUpdate,
+                                              const ThreatEntrySet& aRemoval)
+{
+  if (!aRemoval.has_rice_indices()) {
+    PARSER_LOG(("* No rice encoded removal."));
+    return NS_OK;
+  }
+
+  nsTArray<uint32_t> decoded;
+  nsresult rv = DoRiceDeltaDecode(aRemoval.rice_indices(), decoded);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // The encoded prefixes are always 4 bytes.
+  aTableUpdate.NewRemovalIndices(&decoded[0], decoded.Length());
+
+  return NS_OK;
+}
 
 } // namespace safebrowsing
 } // namespace mozilla
--- a/toolkit/components/url-classifier/ProtocolParser.h
+++ b/toolkit/components/url-classifier/ProtocolParser.h
@@ -179,14 +179,20 @@ private:
                                     const ThreatEntrySetList& aUpdate,
                                     bool aIsAddition);
 
   nsresult ProcessRawAddition(TableUpdateV4& aTableUpdate,
                               const ThreatEntrySet& aAddition);
 
   nsresult ProcessRawRemoval(TableUpdateV4& aTableUpdate,
                              const ThreatEntrySet& aRemoval);
+
+  nsresult ProcessEncodedAddition(TableUpdateV4& aTableUpdate,
+                                  const ThreatEntrySet& aAddition);
+
+  nsresult ProcessEncodedRemoval(TableUpdateV4& aTableUpdate,
+                                 const ThreatEntrySet& aRemoval);
 };
 
 } // namespace safebrowsing
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/RiceDeltaDecoder.cpp
@@ -0,0 +1,308 @@
+/* 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 "RiceDeltaDecoder.h"
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////
+// BitBuffer is copied and modified from webrtc/base/bitbuffer.h
+//
+
+/*
+ *  Copyright 2015 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree (webrtc/base/bitbuffer.h/cc). An additional intellectual property
+ *  rights grant can be found in the file PATENTS.  All contributing
+ *  project authors may be found in the AUTHORS file in the root of
+ *  the source tree.
+ */
+
+class BitBuffer {
+ public:
+  BitBuffer(const uint8_t* bytes, size_t byte_count);
+
+  // Gets the current offset, in bytes/bits, from the start of the buffer. The
+  // bit offset is the offset into the current byte, in the range [0,7].
+  void GetCurrentOffset(size_t* out_byte_offset, size_t* out_bit_offset);
+
+  // The remaining bits in the byte buffer.
+  uint64_t RemainingBitCount() const;
+
+  // Reads byte-sized values from the buffer. Returns false if there isn't
+  // enough data left for the specified type.
+  bool ReadUInt8(uint8_t* val);
+  bool ReadUInt16(uint16_t* val);
+  bool ReadUInt32(uint32_t* val);
+
+  // Reads bit-sized values from the buffer. Returns false if there isn't enough
+  // data left for the specified bit count..
+  bool ReadBits(uint32_t* val, size_t bit_count);
+
+  // Peeks bit-sized values from the buffer. Returns false if there isn't enough
+  // data left for the specified number of bits. Doesn't move the current
+  // offset.
+  bool PeekBits(uint32_t* val, size_t bit_count);
+
+  // Reads the exponential golomb encoded value at the current offset.
+  // Exponential golomb values are encoded as:
+  // 1) x = source val + 1
+  // 2) In binary, write [countbits(x) - 1] 1s, then x
+  // To decode, we count the number of leading 1 bits, read that many + 1 bits,
+  // and increment the result by 1.
+  // Returns false if there isn't enough data left for the specified type, or if
+  // the value wouldn't fit in a uint32_t.
+  bool ReadExponentialGolomb(uint32_t* val);
+  // Reads signed exponential golomb values at the current offset. Signed
+  // exponential golomb values are just the unsigned values mapped to the
+  // sequence 0, 1, -1, 2, -2, etc. in order.
+  bool ReadSignedExponentialGolomb(int32_t* val);
+
+  // Moves current position |byte_count| bytes forward. Returns false if
+  // there aren't enough bytes left in the buffer.
+  bool ConsumeBytes(size_t byte_count);
+  // Moves current position |bit_count| bits forward. Returns false if
+  // there aren't enough bits left in the buffer.
+  bool ConsumeBits(size_t bit_count);
+
+  // Sets the current offset to the provied byte/bit offsets. The bit
+  // offset is from the given byte, in the range [0,7].
+  bool Seek(size_t byte_offset, size_t bit_offset);
+
+ protected:
+  const uint8_t* const bytes_;
+  // The total size of |bytes_|.
+  size_t byte_count_;
+  // The current offset, in bytes, from the start of |bytes_|.
+  size_t byte_offset_;
+  // The current offset, in bits, into the current byte.
+  size_t bit_offset_;
+};
+
+} // end of unnamed namespace
+
+static void
+ReverseByte(uint8_t& b)
+{
+  b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+  b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+  b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+}
+
+namespace mozilla {
+namespace safebrowsing {
+
+RiceDeltaDecoder::RiceDeltaDecoder(uint8_t* aEncodedData,
+                                   size_t aEncodedDataSize)
+  : mEncodedData(aEncodedData)
+  , mEncodedDataSize(aEncodedDataSize)
+{
+}
+
+bool
+RiceDeltaDecoder::Decode(uint32_t aRiceParameter,
+                         uint32_t aFirstValue,
+                         uint32_t aNumEntries,
+                         uint32_t* aDecodedData)
+{
+  // Reverse each byte before reading bits from the byte buffer.
+  for (size_t i = 0; i < mEncodedDataSize; i++) {
+    ReverseByte(mEncodedData[i]);
+  }
+
+  BitBuffer bitBuffer(mEncodedData, mEncodedDataSize);
+
+  // q = quotient
+  // r = remainder
+  // k = RICE parameter
+  const uint32_t k = aRiceParameter;
+  uint32_t previous = aFirstValue;
+  for (uint32_t i = 0; i < aNumEntries; i++) {
+    // Read the quotient of N.
+    uint32_t q;
+    if (!bitBuffer.ReadExponentialGolomb(&q)) {
+      LOG(("Encoded data underflow!"));
+      return false;
+    }
+
+    // Read the remainder of N, one bit at a time.
+    uint32_t r = 0;
+    for (uint32_t j = 0; j < k; j++) {
+      uint32_t b = 0;
+      if (!bitBuffer.ReadBits(&b, 1)) {
+        // Insufficient bits. Just leave them as zeros.
+        break;
+      }
+      // Add the bit to the right position so that it's in Little Endian order.
+      r |= b << j;
+    }
+
+    // Caculate N from q,r,k.
+    aDecodedData[i] = ((q << k) + r) + previous;
+    previous = aDecodedData[i];
+  }
+
+  return true;
+}
+
+} // end of namespace mozilla
+} // end of namespace safebrowsing
+
+namespace {
+//////////////////////////////////////////////////////////////////////////
+// The BitBuffer impl is copied and modified from webrtc/base/bitbuffer.cc
+//
+
+// Returns the lowest (right-most) |bit_count| bits in |byte|.
+uint8_t LowestBits(uint8_t byte, size_t bit_count) {
+  return byte & ((1 << bit_count) - 1);
+}
+
+// Returns the highest (left-most) |bit_count| bits in |byte|, shifted to the
+// lowest bits (to the right).
+uint8_t HighestBits(uint8_t byte, size_t bit_count) {
+  MOZ_ASSERT(bit_count < 8u);
+  uint8_t shift = 8 - static_cast<uint8_t>(bit_count);
+  uint8_t mask = 0xFF << shift;
+  return (byte & mask) >> shift;
+}
+
+BitBuffer::BitBuffer(const uint8_t* bytes, size_t byte_count)
+    : bytes_(bytes), byte_count_(byte_count), byte_offset_(), bit_offset_() {
+  MOZ_ASSERT(static_cast<uint64_t>(byte_count_) <=
+             std::numeric_limits<uint32_t>::max());
+}
+
+uint64_t BitBuffer::RemainingBitCount() const {
+  return (static_cast<uint64_t>(byte_count_) - byte_offset_) * 8 - bit_offset_;
+}
+
+bool BitBuffer::ReadUInt8(uint8_t* val) {
+  uint32_t bit_val;
+  if (!ReadBits(&bit_val, sizeof(uint8_t) * 8)) {
+    return false;
+  }
+  MOZ_ASSERT(bit_val <= std::numeric_limits<uint8_t>::max());
+  *val = static_cast<uint8_t>(bit_val);
+  return true;
+}
+
+bool BitBuffer::ReadUInt16(uint16_t* val) {
+  uint32_t bit_val;
+  if (!ReadBits(&bit_val, sizeof(uint16_t) * 8)) {
+    return false;
+  }
+  MOZ_ASSERT(bit_val <= std::numeric_limits<uint16_t>::max());
+  *val = static_cast<uint16_t>(bit_val);
+  return true;
+}
+
+bool BitBuffer::ReadUInt32(uint32_t* val) {
+  return ReadBits(val, sizeof(uint32_t) * 8);
+}
+
+bool BitBuffer::PeekBits(uint32_t* val, size_t bit_count) {
+  if (!val || bit_count > RemainingBitCount() || bit_count > 32) {
+    return false;
+  }
+  const uint8_t* bytes = bytes_ + byte_offset_;
+  size_t remaining_bits_in_current_byte = 8 - bit_offset_;
+  uint32_t bits = LowestBits(*bytes++, remaining_bits_in_current_byte);
+  // If we're reading fewer bits than what's left in the current byte, just
+  // return the portion of this byte that we need.
+  if (bit_count < remaining_bits_in_current_byte) {
+    *val = HighestBits(bits, bit_offset_ + bit_count);
+    return true;
+  }
+  // Otherwise, subtract what we've read from the bit count and read as many
+  // full bytes as we can into bits.
+  bit_count -= remaining_bits_in_current_byte;
+  while (bit_count >= 8) {
+    bits = (bits << 8) | *bytes++;
+    bit_count -= 8;
+  }
+  // Whatever we have left is smaller than a byte, so grab just the bits we need
+  // and shift them into the lowest bits.
+  if (bit_count > 0) {
+    bits <<= bit_count;
+    bits |= HighestBits(*bytes, bit_count);
+  }
+  *val = bits;
+  return true;
+}
+
+bool BitBuffer::ReadBits(uint32_t* val, size_t bit_count) {
+  return PeekBits(val, bit_count) && ConsumeBits(bit_count);
+}
+
+bool BitBuffer::ConsumeBytes(size_t byte_count) {
+  return ConsumeBits(byte_count * 8);
+}
+
+bool BitBuffer::ConsumeBits(size_t bit_count) {
+  if (bit_count > RemainingBitCount()) {
+    return false;
+  }
+
+  byte_offset_ += (bit_offset_ + bit_count) / 8;
+  bit_offset_ = (bit_offset_ + bit_count) % 8;
+  return true;
+}
+
+bool BitBuffer::ReadExponentialGolomb(uint32_t* val) {
+  if (!val) {
+    return false;
+  }
+
+  *val = 0;
+
+  // Count the number of leading 0 bits by peeking/consuming them one at a time.
+  size_t one_bit_count = 0;
+  uint32_t peeked_bit;
+  while (PeekBits(&peeked_bit, 1) && peeked_bit == 1) {
+    one_bit_count++;
+    ConsumeBits(1);
+  }
+  if (!ConsumeBits(1)) {
+    return false; // The stream is incorrectly terminated at '1'.
+  }
+
+  *val = one_bit_count;
+  return true;
+}
+
+bool BitBuffer::ReadSignedExponentialGolomb(int32_t* val) {
+  uint32_t unsigned_val;
+  if (!ReadExponentialGolomb(&unsigned_val)) {
+    return false;
+  }
+  if ((unsigned_val & 1) == 0) {
+    *val = -static_cast<int32_t>(unsigned_val / 2);
+  } else {
+    *val = (unsigned_val + 1) / 2;
+  }
+  return true;
+}
+
+void BitBuffer::GetCurrentOffset(
+    size_t* out_byte_offset, size_t* out_bit_offset) {
+  MOZ_ASSERT(out_byte_offset != NULL);
+  MOZ_ASSERT(out_bit_offset != NULL);
+  *out_byte_offset = byte_offset_;
+  *out_bit_offset = bit_offset_;
+}
+
+bool BitBuffer::Seek(size_t byte_offset, size_t bit_offset) {
+  if (byte_offset > byte_count_ || bit_offset > 7 ||
+      (byte_offset == byte_count_ && bit_offset > 0)) {
+    return false;
+  }
+  byte_offset_ = byte_offset;
+  bit_offset_ = bit_offset;
+  return true;
+}
+}
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/RiceDeltaDecoder.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RICE_DELTA_DECODER_H
+#define RICE_DELTA_DECODER_H
+
+namespace mozilla {
+namespace safebrowsing {
+
+class RiceDeltaDecoder {
+public:
+  // This decoder is tailored for safebrowsing v4, including the
+  // bit reading order and how the remainder part is interpreted.
+  // The caller just needs to feed the byte stream received from
+  // network directly. Note that the input buffer must be mutable
+  // since the decoder will do some pre-processing before decoding.
+  RiceDeltaDecoder(uint8_t* aEncodedData, size_t aEncodedDataSize);
+
+  bool Decode(uint32_t aRiceParameter,
+              uint32_t aFirstValue,
+              uint32_t aNumEntries,
+              uint32_t* aDecodedData);
+
+private:
+  uint8_t* mEncodedData;
+  size_t mEncodedDataSize;
+};
+
+} // namespace safebrowsing
+} // namespace mozilla
+
+#endif  // UPDATE_V4_DECODER_H
--- a/toolkit/components/url-classifier/moz.build
+++ b/toolkit/components/url-classifier/moz.build
@@ -26,16 +26,17 @@ UNIFIED_SOURCES += [
     'LookupCache.cpp',
     'LookupCacheV4.cpp',
     'nsCheckSummedOutputStream.cpp',
     'nsUrlClassifierDBService.cpp',
     'nsUrlClassifierProxies.cpp',
     'nsUrlClassifierUtils.cpp',
     'protobuf/safebrowsing.pb.cc',
     'ProtocolParser.cpp',
+    'RiceDeltaDecoder.cpp',
 ]
 
 # define conflicting LOG() macros
 SOURCES += [
     'nsUrlClassifierPrefixSet.cpp',
     'nsUrlClassifierStreamUpdater.cpp',
     'VariableLengthPrefixSet.cpp',
 ]
--- a/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierUtils.cpp
@@ -102,20 +102,18 @@ static void
 InitListUpdateRequest(ThreatType aThreatType,
                       const char* aStateBase64,
                       ListUpdateRequest* aListUpdateRequest)
 {
   aListUpdateRequest->set_threat_type(aThreatType);
   aListUpdateRequest->set_platform_type(GetPlatformType());
   aListUpdateRequest->set_threat_entry_type(URL);
 
-  // Only RAW data is supported for now.
-  // TODO: Bug 1285848 Supports Rice-Golomb encoding.
   Constraints* contraints = new Constraints();
-  contraints->add_supported_compressions(RAW);
+  contraints->add_supported_compressions(RICE);
   aListUpdateRequest->set_allocated_constraints(contraints);
 
   // Only set non-empty state.
   if (aStateBase64[0] != '\0') {
     nsCString stateBinary;
     nsresult rv = Base64Decode(nsCString(aStateBase64), stateBinary);
     if (NS_SUCCEEDED(rv)) {
       aListUpdateRequest->set_state(stateBinary.get(), stateBinary.Length());
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/gtest/TestRiceDeltaDecoder.cpp
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#include "gtest/gtest.h"
+#include "RiceDeltaDecoder.h"
+#include "mozilla/ArrayUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::safebrowsing;
+
+struct TestingData {
+  std::vector<uint32_t> mExpectedDecoded;
+  std::vector<uint8_t> mEncoded;
+  uint32_t mRiceParameter;
+};
+
+static bool runOneTest(TestingData& aData);
+
+// In this batch of tests, the encoded data would be like
+// what we originally receive from the network. See comment
+// in |runOneTest| for more detail.
+TEST(RiceDeltaDecoder, Empty) {
+
+  // The following structure and testing data is copied from Chromium source code:
+  //
+  // https://chromium.googlesource.com/chromium/src.git/+/950f9975599768b6a08c7146cb4befa161be87aa/components/safe_browsing_db/v4_rice_unittest.cc#75
+  //
+  // and will be translated to our own testing format.
+
+  struct RiceDecodingTestInfo {
+    uint32_t mRiceParameter;
+    std::vector<uint32_t> mDeltas;
+    std::string mEncoded;
+
+    RiceDecodingTestInfo(uint32_t aRiceParameter,
+                         const std::vector<uint32_t>& aDeltas,
+                         const std::string& aEncoded)
+      : mRiceParameter(aRiceParameter)
+      , mDeltas(aDeltas)
+      , mEncoded(aEncoded)
+    {
+    }
+  };
+
+  // Copyright 2016 The Chromium Authors. All rights reserved.
+  // Use of this source code is governed by a BSD-style license that can be
+  // found in the media/webrtc/trunk/webrtc/LICENSE.
+
+  // ----- Start of Chromium test code ----
+  const std::vector<RiceDecodingTestInfo> TESTING_DATA_CHROMIUM = {
+      RiceDecodingTestInfo(2, {15, 9}, "\xf7\x2"),
+      RiceDecodingTestInfo(
+          28, {1777762129, 2093280223, 924369848},
+          "\xbf\xa8\x3f\xfb\xfc\xfb\x5e\x27\xe6\xc3\x1d\xc6\x38"),
+      RiceDecodingTestInfo(
+          28, {62763050, 1046523781, 192522171, 1800511020, 4442775, 582142548},
+          "\x54\x60\x7b\xe7\x0a\x5f\xc1\xdc\xee\x69\xde"
+          "\xfe\x58\x3c\xa3\xd6\xa5\xf2\x10\x8c\x4a\x59"
+          "\x56\x00"),
+      RiceDecodingTestInfo(
+          28, {26067715, 344823336, 8420095, 399843890, 95029378, 731622412,
+               35811335, 1047558127, 1117722715, 78698892},
+          "\x06\x86\x1b\x23\x14\xcb\x46\xf2\xaf\x07\x08\xc9\x88\x54\x1f\x41\x04"
+          "\xd5\x1a\x03\xeb\xe6\x3a\x80\x13\x91\x7b\xbf\x83\xf3\xb7\x85\xf1\x29"
+          "\x18\xb3\x61\x09"),
+      RiceDecodingTestInfo(
+          27, {225846818, 328287420, 166748623, 29117720, 552397365, 350353215,
+               558267528, 4738273, 567093445, 28563065, 55077698, 73091685,
+               339246010, 98242620, 38060941, 63917830, 206319759, 137700744},
+          "\x89\x98\xd8\x75\xbc\x44\x91\xeb\x39\x0c\x3e\x30\x9a\x78\xf3\x6a\xd4"
+          "\xd9\xb1\x9f\xfb\x70\x3e\x44\x3e\xa3\x08\x67\x42\xc2\x2b\x46\x69\x8e"
+          "\x3c\xeb\xd9\x10\x5a\x43\x9a\x32\xa5\x2d\x4e\x77\x0f\x87\x78\x20\xb6"
+          "\xab\x71\x98\x48\x0c\x9e\x9e\xd7\x23\x0c\x13\x43\x2c\xa9\x01"),
+      RiceDecodingTestInfo(
+          28, {339784008, 263128563, 63871877, 69723256, 826001074, 797300228,
+               671166008, 207712688},
+          std::string("\x21\xc5\x02\x91\xf9\x82\xd7\x57\xb8\xe9\x3c\xf0\xc8\x4f"
+                      "\xe8\x64\x8d\x77\x62\x04\xd6\x85\x3f\x1c\x97\x00\x04\x1b"
+                      "\x17\xc6",
+                      30)),
+      RiceDecodingTestInfo(
+          28, {471820069, 196333855, 855579133, 122737976, 203433838, 85354544,
+               1307949392, 165938578, 195134475, 553930435, 49231136},
+          "\x95\x9c\x7d\xb0\x8f\xe8\xd9\xbd\xfe\x8c\x7f\x81\x53\x0d\x75\xdc\x4e"
+          "\x40\x18\x0c\x9a\x45\x3d\xa8\xdc\xfa\x26\x59\x40\x9e\x16\x08\x43\x77"
+          "\xc3\x4e\x04\x01\xa4\xe6\x5d\x00"),
+      RiceDecodingTestInfo(
+          27, {87336845, 129291033, 30906211, 433549264, 30899891, 53207875,
+               11959529, 354827862, 82919275, 489637251, 53561020, 336722992,
+               408117728, 204506246, 188216092, 9047110, 479817359, 230317256},
+          "\x1a\x4f\x69\x2a\x63\x9a\xf6\xc6\x2e\xaf\x73\xd0\x6f\xd7\x31\xeb\x77"
+          "\x1d\x43\xe3\x2b\x93\xce\x67\x8b\x59\xf9\x98\xd4\xda\x4f\x3c\x6f\xb0"
+          "\xe8\xa5\x78\x8d\x62\x36\x18\xfe\x08\x1e\x78\xd8\x14\x32\x24\x84\x61"
+          "\x1c\xf3\x37\x63\xc4\xa0\x88\x7b\x74\xcb\x64\xc8\x5c\xba\x05"),
+      RiceDecodingTestInfo(
+          28, {297968956, 19709657, 259702329, 76998112, 1023176123, 29296013,
+               1602741145, 393745181, 177326295, 55225536, 75194472},
+          "\xf1\x94\x0a\x87\x6c\x5f\x96\x90\xe3\xab\xf7\xc0\xcb\x2d\xe9\x76\xdb"
+          "\xf8\x59\x63\xc1\x6f\x7c\x99\xe3\x87\x5f\xc7\x04\xde\xb9\x46\x8e\x54"
+          "\xc0\xac\x4a\x03\x0d\x6c\x8f\x00"),
+      RiceDecodingTestInfo(
+          28, {532220688, 780594691, 436816483, 163436269, 573044456, 1069604,
+               39629436, 211410997, 227714491, 381562898, 75610008, 196754597,
+               40310339, 15204118, 99010842},
+          "\x41\x2c\xe4\xfe\x06\xdc\x0d\xbd\x31\xa5\x04\xd5\x6e\xdd\x9b\x43\xb7"
+          "\x3f\x11\x24\x52\x10\x80\x4f\x96\x4b\xd4\x80\x67\xb2\xdd\x52\xc9\x4e"
+          "\x02\xc6\xd7\x60\xde\x06\x92\x52\x1e\xdd\x35\x64\x71\x26\x2c\xfe\xcf"
+          "\x81\x46\xb2\x79\x01"),
+      RiceDecodingTestInfo(
+          28, {219354713, 389598618, 750263679, 554684211, 87381124, 4523497,
+               287633354, 801308671, 424169435, 372520475, 277287849},
+          "\xb2\x2c\x26\x3a\xcd\x66\x9c\xdb\x5f\x07\x2e\x6f\xe6\xf9\x21\x10\x52"
+          "\xd5\x94\xf4\x82\x22\x48\xf9\x9d\x24\xf6\xff\x2f\xfc\x6d\x3f\x21\x65"
+          "\x1b\x36\x34\x56\xea\xc4\x21\x00"),
+  };
+
+  // ----- End of Chromium test code ----
+
+  for (auto tdc : TESTING_DATA_CHROMIUM) {
+    // Populate chromium testing data to our native testing data struct.
+    TestingData d;
+
+    d.mRiceParameter = tdc.mRiceParameter; // Populate rice parameter.
+
+    // Populate encoded data from std::string to vector<uint8>.
+    d.mEncoded.resize(tdc.mEncoded.size());
+    memcpy(&d.mEncoded[0], tdc.mEncoded.c_str(), tdc.mEncoded.size());
+
+    // Populate deltas to expected decoded data. The first value would be just
+    // set to an arbitrary value, say 7, to avoid any assumption to the
+    // first value in the implementation.
+    d.mExpectedDecoded.resize(tdc.mDeltas.size() + 1);
+    for (size_t i = 0; i < d.mExpectedDecoded.size(); i++) {
+      if (0 == i) {
+        d.mExpectedDecoded[i] = 7; // "7" is an arbitrary starting value
+      } else {
+        d.mExpectedDecoded[i] = d.mExpectedDecoded[i - 1] + tdc.mDeltas[i - 1];
+      }
+    }
+
+    ASSERT_TRUE(runOneTest(d));
+  }
+}
+
+static bool
+runOneTest(TestingData& aData)
+{
+  RiceDeltaDecoder decoder(&aData.mEncoded[0], aData.mEncoded.size());
+
+  std::vector<uint32_t> decoded(aData.mExpectedDecoded.size());
+
+  decoded[0] = aData.mExpectedDecoded[0];
+  bool rv = decoder.Decode(aData.mRiceParameter,
+                           decoded[0], // first value.
+                           decoded.size() - 1, // # of entries (first value not included).
+                           &decoded[1]);
+
+  return rv && decoded == aData.mExpectedDecoded;
+}
--- a/toolkit/components/url-classifier/tests/gtest/moz.build
+++ b/toolkit/components/url-classifier/tests/gtest/moz.build
@@ -6,16 +6,17 @@
 
 LOCAL_INCLUDES += [
     '../..',
 ]
 
 UNIFIED_SOURCES += [
     'TestChunkSet.cpp',
     'TestPerProviderDirectory.cpp',
+    'TestRiceDeltaDecoder.cpp',
     'TestSafebrowsingHash.cpp',
     'TestSafeBrowsingProtobuf.cpp',
     'TestTable.cpp',
     'TestUrlClassifierTableUpdateV4.cpp',
     'TestUrlClassifierUtils.cpp',
     'TestVariableLengthPrefixSet.cpp',
 ]
 
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1504,8 +1504,156 @@ let AutoCompletePopup = {
       results.push(result);
     }
 
     return results;
   }
 }
 
 AutoCompletePopup.init();
+
+/**
+ * DateTimePickerListener is the communication channel between the input box
+ * (content) for date/time input types and its picker (chrome).
+ */
+let DateTimePickerListener = {
+  /**
+   * On init, just listen for the event to open the picker, once the picker is
+   * opened, we'll listen for update and close events.
+   */
+  init: function() {
+    addEventListener("MozOpenDateTimePicker", this);
+    this._inputElement = null;
+
+    addEventListener("unload", () => {
+      this.uninit();
+    });
+  },
+
+  uninit: function() {
+    removeEventListener("MozOpenDateTimePicker", this);
+    this._inputElement = null;
+  },
+
+  /**
+   * Cleanup function called when picker is closed.
+   */
+  close: function() {
+    this.removeListeners();
+    this._inputElement.setDateTimePickerState(false);
+    this._inputElement = null;
+  },
+
+  /**
+   * Called after picker is opened to start listening for input box update
+   * events.
+   */
+  addListeners: function() {
+    addEventListener("MozUpdateDateTimePicker", this);
+    addEventListener("MozCloseDateTimePicker", this);
+    addEventListener("pagehide", this);
+
+    addMessageListener("FormDateTime:PickerValueChanged", this);
+    addMessageListener("FormDateTime:PickerClosed", this);
+  },
+
+  /**
+   * Stop listeneing for events when picker is closed.
+   */
+  removeListeners: function() {
+    removeEventListener("MozUpdateDateTimePicker", this);
+    removeEventListener("MozCloseDateTimePicker", this);
+    removeEventListener("pagehide", this);
+
+    removeMessageListener("FormDateTime:PickerValueChanged", this);
+    removeMessageListener("FormDateTime:PickerClosed", this);
+  },
+
+  /**
+   * Helper function that returns the CSS direction property of the element.
+   */
+  getComputedDirection: function(aElement) {
+    return aElement.ownerDocument.defaultView.getComputedStyle(aElement)
+      .getPropertyValue("direction");
+  },
+
+  /**
+   * Helper function that returns the rect of the element, which is the position
+   * in "screen" coordinates.
+   */
+  getBoundingContentRect: function(aElement) {
+    return BrowserUtils.getElementBoundingScreenRect(aElement);
+  },
+
+  /**
+   * nsIMessageListener.
+   */
+  receiveMessage: function(aMessage) {
+    switch (aMessage.name) {
+      case "FormDateTime:PickerClosed": {
+        this.close();
+        break;
+      }
+      case "FormDateTime:PickerValueChanged": {
+        this._inputElement.updateDateTimeInputBox(aMessage.data);
+        break;
+      }
+      default:
+        break;
+    }
+  },
+
+  /**
+   * nsIDOMEventListener, for chrome events sent by the input element and other
+   * DOM events.
+   */
+  handleEvent: function(aEvent) {
+    switch (aEvent.type) {
+      case "MozOpenDateTimePicker": {
+        if (!(aEvent.originalTarget instanceof content.HTMLInputElement)) {
+          return;
+        }
+        this._inputElement = aEvent.originalTarget;
+        this._inputElement.setDateTimePickerState(true);
+        this.addListeners();
+
+        let value = this._inputElement.getDateTimeInputBoxValue();
+        sendAsyncMessage("FormDateTime:OpenPicker", {
+          rect: this.getBoundingContentRect(this._inputElement),
+          dir: this.getComputedDirection(this._inputElement),
+          type: this._inputElement.type,
+          detail: {
+            // Pass partial value if it's available, otherwise pass input
+            // element's value.
+            value: Object.keys(value).length > 0 ? value
+                                                 : this._inputElement.value,
+            step: this._inputElement.step,
+            min: this._inputElement.min,
+            max: this._inputElement.max,
+          },
+        });
+        break;
+      }
+      case "MozUpdateDateTimePicker": {
+        let value = this._inputElement.getDateTimeInputBoxValue();
+        sendAsyncMessage("FormDateTime:UpdatePicker", { value });
+        break;
+      }
+      case "MozCloseDateTimePicker": {
+        sendAsyncMessage("FormDateTime:ClosePicker");
+        this.close();
+        break;
+      }
+      case "pagehide": {
+        if (this._inputElement &&
+            this._inputElement.ownerDocument == aEvent.target) {
+          sendAsyncMessage("FormDateTime:ClosePicker");
+          this.close();
+        }
+        break;
+      }
+      default:
+        break;
+    }
+  },
+}
+
+DateTimePickerListener.init();
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -66,16 +66,18 @@ toolkit.jar:
    content/global/treeUtils.js
    content/global/viewZoomOverlay.js
    content/global/bindings/autocomplete.xml    (widgets/autocomplete.xml)
    content/global/bindings/browser.xml         (widgets/browser.xml)
    content/global/bindings/button.xml          (widgets/button.xml)
    content/global/bindings/checkbox.xml        (widgets/checkbox.xml)
    content/global/bindings/colorpicker.xml     (widgets/colorpicker.xml)
    content/global/bindings/datetimepicker.xml  (widgets/datetimepicker.xml)
+   content/global/bindings/datetimebox.xml     (widgets/datetimebox.xml)
+   content/global/bindings/datetimebox.css     (widgets/datetimebox.css)
 *  content/global/bindings/dialog.xml          (widgets/dialog.xml)
    content/global/bindings/editor.xml          (widgets/editor.xml)
    content/global/bindings/expander.xml        (widgets/expander.xml)
    content/global/bindings/filefield.xml       (widgets/filefield.xml)
 *  content/global/bindings/findbar.xml         (widgets/findbar.xml)
    content/global/bindings/general.xml         (widgets/general.xml)
    content/global/bindings/groupbox.xml        (widgets/groupbox.xml)
    content/global/bindings/listbox.xml         (widgets/listbox.xml)
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -276,16 +276,20 @@
           return this._loadContext;
         ]]></getter>
       </property>
 
       <property name="autoCompletePopup"
                 onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
                 readonly="true"/>
 
+      <property name="dateTimePicker"
+                onget="return document.getElementById(this.getAttribute('datetimepicker'))"
+                readonly="true"/>
+
       <property name="docShellIsActive">
         <getter>
           <![CDATA[
             return this.docShell && this.docShell.isActive;
           ]]>
         </getter>
         <setter>
           <![CDATA[
new file mode 100644
--- /dev/null
+++ b/toolkit/content/widgets/datetimebox.css
@@ -0,0 +1,44 @@
+/* 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/. */
+
+@namespace url("http://www.w3.org/1999/xhtml");
+@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.datetime-input-box-wrapper {
+  -moz-appearance: none;
+  display: inline-flex;
+  cursor: default;
+  background-color: inherit;
+  color: inherit;
+}
+
+.datetime-input {
+  -moz-appearance: none;
+  text-align: center;
+  padding: 0;
+  border: 0;
+  margin: 0;
+  ime-mode: disabled;
+}
+
+.datetime-separator {
+  margin: 0 !important;
+}
+
+.datetime-input[readonly],
+.datetime-input[disabled] {
+  color: GrayText;
+  -moz-user-select: none;
+}
+
+.datetime-reset-button {
+  background-image: url(chrome://global/skin/icons/input-clear.svg);
+  background-repeat: no-repeat;
+  background-size: 12px, 12px;
+  border: none;
+  height: 12px;
+  width: 12px;
+  align-self: center;
+  justify-content: flex-end;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -0,0 +1,806 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<bindings id="datetimeboxBindings"
+   xmlns="http://www.mozilla.org/xbl"
+   xmlns:html="http://www.w3.org/1999/xhtml"
+   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+   xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="time-input"
+           extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
+    <resources>
+      <stylesheet src="chrome://global/content/textbox.css"/>
+      <stylesheet src="chrome://global/skin/textbox.css"/>
+      <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+    </resources>
+
+    <implementation>
+      <constructor>
+      <![CDATA[
+        // TODO: Bug 1301312 - localization for input type=time input.
+        this.mHour12 = true;
+        this.mAMIndicator = "AM";
+        this.mPMIndicator = "PM";
+        this.mPlaceHolder = "--";
+        this.mSeparatorText = ":";
+        this.mMillisecSeparatorText = ".";
+        this.mMaxLength = 2;
+        this.mMillisecMaxLength = 3;
+        this.mDefaultStep = 60 * 1000; // in milliseconds
+
+        this.mMinHourInHour12 = 1;
+        this.mMaxHourInHour12 = 12;
+        this.mMinMinute = 0;
+        this.mMaxMinute = 59;
+        this.mMinSecond = 0;
+        this.mMaxSecond = 59;
+        this.mMinMillisecond = 0;
+        this.mMaxMillisecond = 999;
+
+        this.mHourPageUpDownInterval = 3;
+        this.mMinSecPageUpDownInterval = 10;
+
+        this.mHourField =
+          document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+        this.mHourField.setAttribute("typeBuffer", "");
+        this.mMinuteField =
+          document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+        this.mMinuteField.setAttribute("typeBuffer", "");
+        this.mDayPeriodField =
+          document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+        this.mDayPeriodField.classList.remove("numeric");
+
+        this.mHourField.placeholder = this.mPlaceHolder;
+        this.mMinuteField.placeholder = this.mPlaceHolder;
+        this.mDayPeriodField.placeholder = this.mPlaceHolder;
+
+        this.mHourField.setAttribute("min", this.mMinHourInHour12);
+        this.mHourField.setAttribute("max", this.mMaxHourInHour12);
+        this.mMinuteField.setAttribute("min", this.mMinMinute);
+        this.mMinuteField.setAttribute("max", this.mMaxMinute);
+
+        this.mMinuteSeparator =
+           document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+        this.mMinuteSeparator.textContent = this.mSeparatorText;
+        this.mSpaceSeparator =
+          document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+        // space between time and am/pm field
+        this.mSpaceSeparator.textContent = " ";
+
+        this.mSecondSeparator = null;
+        this.mSecondField = null;
+        this.mMillisecSeparator = null;
+        this.mMillisecField = null;
+
+        if (this.mInputElement.value) {
+          this.setFieldsFromInputValue();
+        }
+        ]]>
+      </constructor>
+
+      <method name="insertSeparator">
+        <parameter name="aSeparatorText"/>
+        <body>
+        <![CDATA[
+          let container = this.mHourField.parentNode;
+          const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+          let separator = document.createElementNS(HTML_NS, "span");
+          separator.textContent = aSeparatorText;
+          separator.setAttribute("class", "datetime-separator");
+          container.insertBefore(separator, this.mSpaceSeparator);
+
+          return separator;
+        ]]>
+        </body>
+      </method>
+
+      <method name="insertAdditionalField">
+        <parameter name="aPlaceHolder"/>
+        <parameter name="aMin"/>
+        <parameter name="aMax"/>
+        <parameter name="aSize"/>
+        <parameter name="aMaxLength"/>
+        <body>
+        <![CDATA[
+          let container = this.mHourField.parentNode;
+          const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+          let field = document.createElementNS(HTML_NS, "input");
+          field.classList.add("textbox-input", "datetime-input", "numeric");
+          field.setAttribute("size", aSize);
+          field.setAttribute("maxlength", aMaxLength);
+          field.setAttribute("min", aMin);
+          field.setAttribute("max", aMax);
+          field.setAttribute("typeBuffer", "");
+          field.disabled = this.mInputElement.disabled;
+          field.readOnly = this.mInputElement.readOnly;
+          field.tabIndex = this.mInputElement.tabIndex;
+          field.placeholder = aPlaceHolder;
+          container.insertBefore(field, this.mSpaceSeparator);
+
+          return field;
+        ]]>
+        </body>
+      </method>
+
+      <method name="setFieldsFromInputValue">
+        <body>
+        <![CDATA[
+          let value = this.mInputElement.value;
+          if (!value) {
+            this.clearInputFields(true);
+            return;
+          }
+
+          this.log("setFieldsFromInputValue: " + value);
+          let [hour, minute, second] = value.split(':');
+
+          this.setFieldValue(this.mHourField, hour);
+          this.setFieldValue(this.mMinuteField, minute);
+          if (this.mHour12) {
+            this.mDayPeriodField.value = (hour >= this.mMaxHourInHour12) ?
+              this.mPMIndicator : this.mAMIndicator;
+          }
+
+          if (!this.isEmpty(second)) {
+            let index = second.indexOf(".");
+            let millisecond;
+            if (index != -1) {
+              millisecond = second.substring(index + 1);
+              second = second.substring(0, index);
+            }
+
+            if (!this.mSecondField) {
+              this.mSecondSeparator = this.insertSeparator(this.mSeparatorText);
+              this.mSecondField = this.insertAdditionalField(this.mPlaceHolder,
+                this.mMinSecond, this.mMaxSecond, this.mMaxLength,
+                this.mMaxLength);
+            }
+            this.setFieldValue(this.mSecondField, second);
+
+            if (!this.isEmpty(millisecond)) {
+              if (!this.mMillisecField) {
+                this.mMillisecSeparator = this.insertSeparator(
+                  this.mMillisecSeparatorText);
+                this.mMillisecField = this.insertAdditionalField(
+                  this.mPlaceHolder, this.mMinMillisecond, this.mMaxMillisecond,
+                  this.mMillisecMaxLength, this.mMillisecMaxLength);
+              }
+              this.setFieldValue(this.mMillisecField, millisecond);
+            } else if (this.mMillisecField) {
+              this.mMillisecField.remove();
+              this.mMillisecField = null;
+
+              this.mMillisecSeparator.remove();
+              this.mMillisecSeparator = null;
+            }
+          } else {
+            if (this.mSecondField) {
+              this.mSecondField.remove();
+              this.mSecondField = null;
+
+              this.mSecondSeparator.remove();
+              this.mSecondSeparator = null;
+            }
+
+            if (this.mMillisecField) {
+              this.mMillisecField.remove();
+              this.mMillisecField = null;
+
+              this.mMillisecSeparator.remove();
+              this.mMillisecSeparator = null;
+            }
+          }
+          this.notifyPicker();
+        ]]>
+        </body>
+      </method>
+
+      <method name="setInputValueFromFields">
+        <body>
+        <![CDATA[
+          if (this.isEmpty(this.mHourField.value) ||
+              this.isEmpty(this.mMinuteField.value) ||
+              (this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) ||
+              (this.mSecondField && this.isEmpty(this.mSecondField.value))) {
+            // We still need to notify picker in case any of the field has
+            // changed. If we can set input element value, then notifyPicker
+            // will be called in setFieldsFromInputValue().
+            this.notifyPicker();
+            return;
+          }
+
+          let hour = Number(this.mHourField.value);
+          if (this.mHour12) {
+            let dayPeriod = this.mDayPeriodField.value;
+            if (dayPeriod == this.mPMIndicator &&
+                hour < this.mMaxHourInHour12) {
+              hour += this.mMaxHourInHour12;
+            } else if (dayPeriod == this.mAMIndicator &&
+                       hour == this.mMaxHourInHour12) {
+              hour = 0;
+            }
+          }
+
+          hour = (hour < 10) ? ("0" + hour) : hour;
+
+          let time = hour + ":" + this.mMinuteField.value;
+          if (this.mSecondField) {
+            time += ":" + this.mSecondField.value;
+          }
+
+          if (this.mMillisecField) {
+            time += "." + this.mMillisecField.value;
+          }
+
+          this.log("setInputValueFromFields: " + time);
+          this.mInputElement.setUserInput(time);
+        ]]>
+        </body>
+      </method>
+
+      <method name="setFieldsFromPicker">
+        <parameter name="aValue"/>
+        <body>
+        <![CDATA[
+          let hour = aValue.hour;
+          let minute = aValue.minute;
+          this.log("setFieldsFromPicker: " + hour + ":" + minute);
+
+          if (!this.isEmpty(hour)) {
+            this.setFieldValue(this.mHourField, hour);
+            if (this.mHour12) {
+              this.mDayPeriodField.value =
+                (hour >= this.mMaxHourInHour12) ? this.mPMIndicator
+                                                : this.mAMIndicator;
+            }
+          }
+
+          if (!this.isEmpty(minute)) {
+            this.setFieldValue(this.mMinuteField, minute);
+          }
+        ]]>
+        </body>
+       </method>
+
+      <method name="clearInputFields">
+        <parameter name="aFromInputElement"/>
+        <body>
+        <![CDATA[
+          this.log("clearInputFields");
+
+          if (this.isDisabled() || this.isReadonly()) {
+            return;
+          }
+
+          if (this.mHourField && !this.mHourField.disabled &&
+              !this.mHourField.readOnly) {
+            this.mHourField.value = "";
+          }
+
+          if (this.mMinuteField && !this.mMinuteField.disabled &&
+              !this.mMinuteField.readOnly) {
+            this.mMinuteField.value = "";
+          }
+
+          if (this.mSecondField && !this.mSecondField.disabled &&
+              !this.mSecondField.readOnly) {
+            this.mSecondField.value = "";
+          }
+
+          if (this.mMillisecField && !this.mMillisecField.disabled &&
+              !this.mMillisecField.readOnly) {
+            this.mMillisecField.value = "";
+          }
+
+          if (this.mDayPeriodField && !this.mDayPeriodField.disabled &&
+              !this.mDayPeriodField.readOnly) {
+            this.mDayPeriodField.value = "";
+          }
+
+          if (!aFromInputElement) {
+            this.mInputElement.setUserInput("");
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="incrementFieldValue">
+        <parameter name="aTargetField"/>
+        <parameter name="aTimes"/>
+        <body>
+        <![CDATA[
+          let value;
+
+          // Use current time if field is empty.
+          if (this.isEmpty(aTargetField.value)) {
+            let now = new Date();
+
+            if (aTargetField == this.mHourField) {
+              value = now.getHours() % this.mMaxHourInHour12 ||
+                this.mMaxHourInHour12;
+            } else if (aTargetField == this.mMinuteField) {
+              value = now.getMinutes();
+            } else if (aTargetField == this.mSecondField) {
+              value = now.getSeconds();
+            } else if (aTargetField == this.mMillisecondsField) {
+              value = now.getMilliseconds();
+            } else {
+              this.log("Field not supported in incrementFieldValue.");
+              return;
+            }
+          } else {
+            value = Number(aTargetField.value);
+          }
+
+          let min = aTargetField.getAttribute("min");
+          let max = aTargetField.getAttribute("max");
+
+          value += aTimes;
+          if (value > max) {
+            value -= (max - min + 1);
+          } else if (value < min) {
+            value += (max - min + 1);
+          }
+          this.setFieldValue(aTargetField, value);
+          aTargetField.select();
+        ]]>
+        </body>
+      </method>
+
+      <method name="handleKeyboardNav">
+        <parameter name="aEvent"/>
+        <body>
+        <![CDATA[
+          if (this.isDisabled() || this.isReadonly()) {
+            return;
+          }
+
+          let targetField = aEvent.originalTarget;
+          let key = aEvent.key;
+
+          if (this.mDayPeriodField &&
+              targetField == this.mDayPeriodField) {
+            // Home/End key does nothing on AM/PM field.
+            if (key == "Home" || key == "End") {
+              return;
+            }
+
+            this.mDayPeriodField.value =
+              this.mDayPeriodField.value == this.mAMIndicator ?
+                this.mPMIndicator : this.mAMIndicator;
+            this.mDayPeriodField.select();
+            this.setInputValueFromFields();
+            return;
+          }
+
+          switch (key) {
+            case "ArrowUp":
+              this.incrementFieldValue(targetField, 1);
+              break;
+            case "ArrowDown":
+              this.incrementFieldValue(targetField, -1);
+              break;
+            case "PageUp":
+              this.incrementFieldValue(targetField,
+                targetField == this.mHourField ? this.mHourPageUpDownInterval
+                                               : this.mMinSecPageUpDownInterval);
+              break;
+            case "PageDown":
+              this.incrementFieldValue(targetField,
+                targetField == this.mHourField ? (0 - this.mHourPageUpDownInterval)
+                                               : (0 - this.mMinSecPageUpDownInterval));
+              break;
+            case "Home":
+              let min = targetField.getAttribute("min");
+              this.setFieldValue(targetField, min);
+              targetField.select();
+              break;
+            case "End":
+              let max = targetField.getAttribute("max");
+              this.setFieldValue(targetField, max);
+              targetField.select();
+              break;
+          }
+          this.setInputValueFromFields();
+        ]]>
+        </body>
+      </method>
+
+      <method name="handleKeypress">
+        <parameter name="aEvent"/>
+        <body>
+        <![CDATA[
+          if (this.isDisabled() || this.isReadonly()) {
+            return;
+          }
+
+          let targetField = aEvent.originalTarget;
+          let key = aEvent.key;
+
+          if (this.mDayPeriodField &&
+              targetField == this.mDayPeriodField) {
+            if (key == "a" || key == "A") {
+              this.mDayPeriodField.value = this.mAMIndicator;
+              this.mDayPeriodField.select();
+            } else if (key == "p" || key == "P") {
+              this.mDayPeriodField.value = this.mPMIndicator;
+              this.mDayPeriodField.select();
+            }
+            return;
+          }
+
+          if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
+            let buffer = targetField.getAttribute("typeBuffer") || "";
+
+            buffer = buffer.concat(key);
+            this.setFieldValue(targetField, buffer);
+            targetField.select();
+
+            let n = Number(buffer);
+            let max = targetField.getAttribute("max");
+            if (buffer.length >= targetField.maxLength || n * 10 > max) {
+              buffer = "";
+              this.advanceToNextField();
+            }
+            targetField.setAttribute("typeBuffer", buffer);
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="setFieldValue">
+       <parameter name="aField"/>
+       <parameter name="aValue"/>
+        <body>
+        <![CDATA[
+          let value = Number(aValue);
+          if (isNaN(value)) {
+            this.log("NaN on setFieldValue!");
+            return;
+          }
+
+          if (aField.maxLength == this.mMaxLength) { // For hour, minute and second
+            if (aField == this.mHourField && this.mHour12) {
+              value = (value > this.mMaxHourInHour12) ?
+                value - this.mMaxHourInHour12 : value;
+              if (aValue == "00") {
+                value = this.mMaxHourInHour12;
+              }
+            }
+            // prepend zero
+            if (value < 10) {
+              value = "0" + value;
+            }
+          } else if (aField.maxLength == this.mMillisecMaxLength) {
+            // prepend zeroes
+            if (value < 10) {
+              value = "00" + value;
+            } else if (value < 100) {
+              value = "0" + value;
+            }
+          }
+
+          aField.value = value;
+        ]]>
+        </body>
+      </method>
+
+      <method name="isValueAvailable">
+        <body>
+        <![CDATA[
+          // Picker only cares about hour:minute.
+          return !this.isEmpty(this.mHourField.value) ||
+                 !this.isEmpty(this.mMinuteField.value);
+        ]]>
+        </body>
+      </method>
+
+      <method name="getCurrentValue">
+        <body>
+        <![CDATA[
+          let hour;
+          if (!this.isEmpty(this.mHourField.value)) {
+            hour = Number(this.mHourField.value);
+            if (this.mHour12) {
+              let dayPeriod = this.mDayPeriodField.value;
+              if (dayPeriod == this.mPMIndicator &&
+                  hour < this.mMaxHourInHour12) {
+                hour += this.mMaxHourInHour12;
+              } else if (dayPeriod == this.mAMIndicator &&
+                         hour == this.mMaxHourInHour12) {
+                hour = 0;
+              }
+            }
+           }
+
+          let minute;
+          if (!this.isEmpty(this.mMinuteField.value)) {
+            minute = Number(this.mMinuteField.value);
+          }
+
+          // Picker only needs hour/minute.
+          let time = { hour, minute };
+
+          this.log("getCurrentValue: " + JSON.stringify(time));
+          return time;
+        ]]>
+        </body>
+      </method>
+    </implementation>
+  </binding>
+
+  <binding id="datetime-input-base">
+    <resources>
+      <stylesheet src="chrome://global/content/textbox.css"/>
+      <stylesheet src="chrome://global/skin/textbox.css"/>
+      <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+    </resources>
+
+    <content>
+      <html:div class="datetime-input-box-wrapper"
+                xbl:inherits="context,disabled,readonly">
+        <html:span>
+          <html:input anonid="input-one"
+                      class="textbox-input datetime-input numeric"
+                      size="2" maxlength="2"
+                      xbl:inherits="disabled,readonly,tabindex"/>
+          <html:span anonid="sep-first" class="datetime-separator"></html:span>
+          <html:input anonid="input-two"
+                      class="textbox-input datetime-input numeric"
+                      size="2" maxlength="2"
+                      xbl:inherits="disabled,readonly,tabindex"/>
+          <html:span anonid="sep-second" class="datetime-separator"></html:span>
+          <html:input anonid="input-three"
+                      class="textbox-input datetime-input numeric"
+                      size="2" maxlength="2"
+                      xbl:inherits="disabled,readonly,tabindex"/>
+        </html:span>
+
+        <html:button class="datetime-reset-button" anoid="reset-button"
+                     tabindex="-1" xbl:inherits="disabled"
+                     onclick="document.getBindingParent(this).clearInputFields(false);"/>
+      </html:div>
+    </content>
+
+    <implementation implements="nsIDateTimeInputArea">
+      <constructor>
+      <![CDATA[
+        this.DEBUG = false;
+        this.mInputElement = this.parentNode;
+
+        this.mMin = this.mInputElement.min;
+        this.mMax = this.mInputElement.max;
+        this.mStep = this.mInputElement.step;
+        this.mIsPickerOpen = false;
+      ]]>
+      </constructor>
+
+      <method name="log">
+        <parameter name="aMsg"/>
+        <body>
+        <![CDATA[
+          if (this.DEBUG) {
+            dump("[DateTimeBox] " + aMsg + "\n");
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="focusInnerTextBox">
+        <body>
+        <![CDATA[
+          this.log("focusInnerTextBox");
+          document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus();
+        ]]>
+        </body>
+      </method>
+
+      <method name="blurInnerTextBox">
+        <body>
+        <![CDATA[
+          this.log("blurInnerTextBox");
+          if (this.mLastFocusedField) {
+            this.mLastFocusedField.blur();
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="notifyInputElementValueChanged">
+        <body>
+        <![CDATA[
+          this.log("inputElementValueChanged");
+          this.setFieldsFromInputValue();
+        ]]>
+        </body>
+      </method>
+
+      <method name="setValueFromPicker">
+        <parameter name="aValue"/>
+        <body>
+        <![CDATA[
+          this.setFieldsFromPicker(aValue);
+        ]]>
+        </body>
+      </method>
+
+      <method name="advanceToNextField">
+        <parameter name="aReverse"/>
+        <body>
+        <![CDATA[
+          this.log("advanceToNextField");
+
+          let focusedInput = this.mLastFocusedField;
+          let next = aReverse ? focusedInput.previousElementSibling
+                              : focusedInput.nextElementSibling;
+          if (!next && !aReverse) {
+            this.setInputValueFromFields();
+            return;
+          }
+
+          while (next) {
+            if (next.type == "text" && !next.disabled) {
+              next.focus();
+              break;
+            }
+            next = aReverse ? next.previousElementSibling
+                            : next.nextElementSibling;
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="setPickerState">
+        <parameter name="aIsOpen"/>
+        <body>
+        <![CDATA[
+          this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
+          this.mIsPickerOpen = aIsOpen;
+        ]]>
+        </body>
+      </method>
+
+      <method name="isEmpty">
+        <parameter name="aValue"/>
+        <body>
+          return (aValue == undefined || 0 === aValue.length);
+        </body>
+      </method>
+
+      <method name="clearInputFields">
+        <body>
+          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        </body>
+      </method>
+
+      <method name="setFieldsFromInputValue">
+        <body>
+          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        </body>
+      </method>
+
+      <method name="setInputValueFromFields">
+        <body>
+          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        </body>
+      </method>
+
+      <method name="setFieldsFromPicker">
+        <body>
+          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        </body>
+      </method>
+
+      <method name="handleKeypress">
+        <body>
+          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        </body>
+      </method>
+
+      <method name="handleKeyboardNav">
+        <body>
+          throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        </body>
+      </method>
+
+      <method name="notifyPicker">
+        <body>
+        <![CDATA[
+          if (this.mIsPickerOpen && this.isValueAvailable()) {
+            this.mInputElement.updateDateTimePicker(this.getCurrentValue());
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="isDisabled">
+        <body>
+        <![CDATA[
+          return this.hasAttribute("disabled");
+        ]]>
+        </body>
+      </method>
+
+      <method name="isReadonly">
+        <body>
+        <![CDATA[
+          return this.hasAttribute("readonly");
+        ]]>
+        </body>
+      </method>
+
+    </implementation>
+
+    <handlers>
+      <handler event="focus">
+      <![CDATA[
+        this.log("focus on: " + event.originalTarget);
+
+        let target = event.originalTarget;
+        if (target.type == "text") {
+          this.mLastFocusedField = target;
+          target.select();
+        }
+      ]]>
+      </handler>
+
+      <handler event="blur">
+      <![CDATA[
+        this.setInputValueFromFields();
+      ]]>
+      </handler>
+
+      <handler event="click">
+      <![CDATA[
+        // XXX: .originalTarget is not expected.
+        // When clicking on one of the inner text boxes, the .originalTarget is
+        // a HTMLDivElement and when clicking on the reset button, it's a
+        // HTMLButtonElement but it's not equal to our reset-button.
+        this.log("click on: " + event.originalTarget);
+        if (event.defaultPrevented || this.isDisabled() || this.isReadonly()) {
+          return;
+        }
+
+        if (!(event.originalTarget instanceof HTMLButtonElement)) {
+          this.mInputElement.openDateTimePicker(this.getCurrentValue());
+        }
+      ]]>
+      </handler>
+
+      <handler event="keypress" phase="capturing">
+      <![CDATA[
+        let key = event.key;
+        this.log("keypress: " + key);
+
+        if (key == "Backspace" || key == "Tab") {
+          return;
+        }
+
+        if (key == "Enter" || key == " ") {
+          // Close picker on Enter and Space.
+          this.mInputElement.closeDateTimePicker();
+        }
+
+        if (key == "ArrowUp" || key == "ArrowDown" ||
+            key == "PageUp" || key == "PageDown" ||
+            key == "Home" || key == "End") {
+          this.handleKeyboardNav(event);
+        } else if (key == "ArrowRight" || key == "ArrowLeft") {
+          this.advanceToNextField((key == "ArrowRight" ? false : true));
+        } else {
+          this.handleKeypress(event);
+        }
+
+        event.preventDefault();
+      ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+</bindings>
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/DateTimePickerHelper.jsm
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const DEBUG = false;
+function debug(aStr) {
+  if (DEBUG) {
+    dump("-*- DateTimePickerHelper: " + aStr + "\n");
+  }
+}
+
+this.EXPORTED_SYMBOLS = [
+  "DateTimePickerHelper"
+];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+/*
+ * DateTimePickerHelper receives message from content side (input box) and
+ * is reposible for opening, closing and updating the picker. Similary,
+ * DateTimePickerHelper listens for picker's events and notifies the content
+ * side (input box) about them.
+ */
+this.DateTimePickerHelper = {
+  picker: null,
+  weakBrowser: null,
+
+  MESSAGES: [
+    "FormDateTime:OpenPicker",
+    "FormDateTime:ClosePicker",
+    "FormDateTime:UpdatePicker"
+  ],
+
+  init: function() {
+    for (let msg of this.MESSAGES) {
+      Services.mm.addMessageListener(msg, this);
+    }
+  },
+
+  uninit: function() {
+    for (let msg of this.MESSAGES) {
+      Services.mm.removeMessageListener(msg, this);
+    }
+  },
+
+  // nsIMessageListener
+  receiveMessage: function(aMessage) {
+    debug("receiveMessage: " + aMessage.name);
+    switch (aMessage.name) {
+      case "FormDateTime:OpenPicker": {
+        this.showPicker(aMessage.target, aMessage.data);
+        break;
+      }
+      case "FormDateTime:ClosePicker": {
+        if (!this.picker) {
+          return;
+        }
+        this.picker.hidePopup();
+        break;
+      }
+      case "FormDateTime:UpdatePicker": {
+        let value = aMessage.data.value;
+        debug("Input box value is now: " + value.hour + ":" + value.minute);
+        // TODO: updating picker will be handled in Bug 1283384.
+        break;
+      }
+      default:
+        break;
+    }
+  },
+
+  // nsIDOMEventListener
+  handleEvent: function(aEvent) {
+    debug("handleEvent: " + aEvent.type);
+    switch (aEvent.type) {
+      case "DateTimePickerValueChanged": {
+        this.updateInputBoxValue(aEvent);
+        break;
+      }
+      case "popuphidden": {
+        let browser = this.weakBrowser ? this.weakBrowser.get() : null;
+        if (browser) {
+          browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
+        }
+        this.close();
+        break;
+      }
+      default:
+        break;
+    }
+  },
+
+  // Called when picker value has changed, notify input box about it.
+  updateInputBoxValue: function(aEvent) {
+    // TODO: parse data based on input type.
+    const { hour, minute } = aEvent.detail;
+    debug("hour: " + hour + ", minute: " + minute);
+    let browser = this.weakBrowser ? this.weakBrowser.get() : null;
+    if (browser) {
+      browser.messageManager.sendAsyncMessage(
+        "FormDateTime:PickerValueChanged", { hour, minute });
+    }
+  },
+
+  // Get picker from browser and show it anchored to the input box.
+  showPicker: function(aBrowser, aData) {
+    let rect = aData.rect;
+    let dir = aData.dir;
+    let type = aData.type;
+    let detail = aData.detail;
+    debug("Opening picker with details: " + JSON.stringify(detail));
+
+    let window = aBrowser.ownerDocument.defaultView;
+    let tabbrowser = window.gBrowser;
+    if (Services.focus.activeWindow != window ||
+        tabbrowser.selectedBrowser != aBrowser) {
+      // We were sent a message from a window or tab that went into the
+      // background, so we'll ignore it for now.
+      return;
+    }
+
+    this.weakBrowser = Cu.getWeakReference(aBrowser);
+    this.picker = aBrowser.dateTimePicker;
+    if (!this.picker) {
+      debug("aBrowser.dateTimePicker not found, exiting now.");
+      return;
+    }
+    this.picker.hidden = false;
+    this.picker.openPopupAtScreenRect("after_start", rect.left, rect.top,
+                                      rect.width, rect.height, false, false);
+    this.addPickerListeners();
+  },
+
+  // Picker is closed, do some cleanup.
+  close: function() {
+    this.removePickerListeners();
+    this.picker = null;
+    this.weakBrowser = null;
+  },
+
+  // Listen to picker's event.
+  addPickerListeners: function() {
+    if (!this.picker) {
+      return;
+    }
+    this.picker.addEventListener("popuphidden", this);
+    this.picker.addEventListener("DateTimePickerValueChanged", this);
+  },
+
+  // Stop listening to picker's event.
+  removePickerListeners: function() {
+    if (!this.picker) {
+      return;
+    }
+    this.picker.removeEventListener("popuphidden", this);
+    this.picker.removeEventListener("DateTimePickerValueChanged", this);
+  },
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -30,16 +30,17 @@ EXTRA_JS_MODULES += [
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CanonicalJSON.jsm',
     'CertUtils.jsm',
     'CharsetMenu.jsm',
     'ClientID.jsm',
     'Color.jsm',
     'Console.jsm',
+    'DateTimePickerHelper.jsm',
     'debug.js',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'FileUtils.jsm',
     'Finder.jsm',
     'FinderHighlighter.jsm',
     'FinderIterator.jsm',
     'Geometry.jsm',
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -971,21 +971,34 @@ var loadManifestFromWebManifest = Task.a
 
   addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
 
   function getLocale(aLocale) {
     // Use the raw manifest, here, since we need values with their
     // localization placeholders still in place.
     let rawManifest = extension.rawManifest;
 
+    let creator = rawManifest.author;
+    let homepageURL = rawManifest.homepage_url;
+
+    // Allow developer to override creator and homepage_url.
+    if (rawManifest.developer) {
+      if (rawManifest.developer.name) {
+        creator = rawManifest.developer.name;
+      }
+      if (rawManifest.developer.url) {
+        homepageURL = rawManifest.developer.url;
+      }
+    }
+
     let result = {
       name: extension.localize(rawManifest.name, aLocale),
       description: extension.localize(rawManifest.description, aLocale),
-      creator: extension.localize(rawManifest.creator, aLocale),
-      homepageURL: extension.localize(rawManifest.homepage_url, aLocale),
+      creator: extension.localize(creator, aLocale),
+      homepageURL: extension.localize(homepageURL, aLocale),
 
       developers: null,
       translators: null,
       contributors: null,
       locales: [aLocale],
     };
     return result;
   }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js
@@ -14,21 +14,21 @@ profileDir.append("extensions");
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 startupManager();
 
 const { GlobalManager, Management } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
 
 function promiseAddonStartup() {
   return new Promise(resolve => {
     let listener = (evt, extension) => {
-      Management.off("startup", listener);
+      Management.off("ready", listener);
       resolve(extension);
     };
 
-    Management.on("startup", listener);
+    Management.on("ready", listener);
   });
 }
 
 function promiseInstallWebExtension(aData) {
   let addonFile = createTempWebExtensionFile(aData);
 
   return promiseInstallAllFiles([addonFile]).then(() => {
     Services.obs.notifyObservers(addonFile, "flush-cache-entry", null);
@@ -348,8 +348,57 @@ add_task(function* test_experiments_api(
   let addon = addons.pop();
   equal(addon.id, ID, "Add-on should be installed as an API extension");
 
   addons = yield new Promise(resolve => AddonManager.getAddonsByTypes(["extension"], resolve));
   equal(addons.pop().id, ID, "Add-on type should be aliased to extension");
 
   addon.uninstall();
 });
+
+add_task(function* developerShouldOverride() {
+  let addon = yield promiseInstallWebExtension({
+    manifest: {
+      default_locale: "en",
+      developer: {
+        name: "__MSG_name__",
+        url: "__MSG_url__"
+      },
+      author: "Will be overridden by developer",
+      homepage_url: "https://will.be.overridden",
+    },
+    files: {
+      "_locales/en/messages.json": `{
+        "name": {
+          "message": "en name"
+        },
+        "url": {
+          "message": "https://example.net/en"
+        }
+      }`
+    }
+  });
+
+  addon = yield promiseAddonByID(addon.id);
+  equal(addon.creator, "en name");
+  equal(addon.homepageURL, "https://example.net/en");
+  addon.uninstall();
+});
+
+add_task(function* developerEmpty() {
+  for (let developer of [{}, null, {name: null, url: null}]) {
+    let addon = yield promiseInstallWebExtension({
+      manifest: {
+        author: "Some author",
+        developer: developer,
+        homepage_url: "https://example.net",
+        manifest_version: 2,
+        name: "Web Extension Name",
+        version: "1.0",
+      }
+    });
+
+    addon = yield promiseAddonByID(addon.id);
+    equal(addon.creator, "Some author");
+    equal(addon.homepageURL, "https://example.net");
+    addon.uninstall();
+  }
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/shared/icons/input-clear.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
+<path id="Combined-Shape" d="M6,12c3.3,0,6-2.7,6-6S9.3,0,6,0S0,2.7,0,6S2.7,12,6,12z M9,8.1L8.1,9L6,6.9L3.9,9L3,8.1L5.1,6L3,3.9
+	L3.9,3L6,5.1L8.1,3L9,3.9L6.9,6L9,8.1z"/>
+</svg>
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -18,16 +18,17 @@ toolkit.jar:
   skin/classic/global/aboutReader.css                      (../../shared/aboutReader.css)
   skin/classic/global/aboutReaderContent.css               (../../shared/aboutReaderContent.css)
 * skin/classic/global/aboutReaderControls.css              (../../shared/aboutReaderControls.css)
   skin/classic/global/aboutSupport.css                     (../../shared/aboutSupport.css)
   skin/classic/global/appPicker.css                        (../../shared/appPicker.css)
   skin/classic/global/config.css                           (../../shared/config.css)
   skin/classic/global/icons/find-arrows.svg                (../../shared/icons/find-arrows.svg)
   skin/classic/global/icons/info.svg                       (../../shared/incontent-icons/info.svg)
+  skin/classic/global/icons/input-clear.svg                (../../shared/icons/input-clear.svg)
   skin/classic/global/icons/loading.png                    (../../shared/icons/loading.png)
   skin/classic/global/icons/loading@2x.png                 (../../shared/icons/loading@2x.png)
   skin/classic/global/icons/warning.svg                    (../../shared/incontent-icons/warning.svg)
   skin/classic/global/icons/blocked.svg                    (../../shared/incontent-icons/blocked.svg)
   skin/classic/global/alerts/alert-common.css              (../../shared/alert-common.css)
   skin/classic/global/narrate.css                          (../../shared/narrate.css)
   skin/classic/global/narrateControls.css                  (../../shared/narrateControls.css)
   skin/classic/global/narrate/arrow.svg                    (../../shared/narrate/arrow.svg)
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -180,20 +180,16 @@ static uint32_t gNumberOfWidgetsNeedingE
 
 -(void)setGLOpaque:(BOOL)aOpaque;
 
 // Overlay drawing functions for traditional CGContext drawing
 - (void)drawTitleString;
 - (void)drawTitlebarHighlight;
 - (void)maskTopCornersInContext:(CGContextRef)aContext;
 
-// Called using performSelector:withObject:afterDelay:0 to release
-// aWidgetArray (and its contents) the next time through the run loop.
-- (void)releaseWidgets:(NSArray*)aWidgetArray;
-
 #if USE_CLICK_HOLD_CONTEXTMENU
  // called on a timer two seconds after a mouse down to see if we should display
  // a context menu (click-hold)
 - (void)clickHoldCallback:(id)inEvent;
 #endif
 
 #ifdef ACCESSIBILITY
 - (id<mozAccessible>)accessible;
@@ -3140,16 +3136,32 @@ GLPresenter::BeginFrame(LayoutDeviceIntS
 
 void
 GLPresenter::EndFrame()
 {
   mGLContext->SwapBuffers();
   mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
 }
 
+class WidgetsReleaserRunnable final : public mozilla::Runnable
+{
+public:
+  explicit WidgetsReleaserRunnable(nsTArray<nsCOMPtr<nsIWidget>>&& aWidgetArray)
+    : mWidgetArray(aWidgetArray)
+  {
+  }
+
+  // Do nothing; all this runnable does is hold a reference the widgets in
+  // mWidgetArray, and those references will be dropped when this runnable
+  // is destroyed.
+
+private:
+  nsTArray<nsCOMPtr<nsIWidget>> mWidgetArray;
+};
+
 #pragma mark -
 
 @implementation ChildView
 
 // globalDragPboard is non-null during native drag sessions that did not originate
 // in our native NSView (it is set in |draggingEntered:|). It is unset when the
 // drag session ends for this view, either with the mouse exiting or when a drop
 // occurs in this view.
@@ -3946,57 +3958,41 @@ NSEvent* gLastDragMouseDownEvent = nil;
 }
 
 - (void)drawTitlebarHighlight
 {
   DrawTitlebarHighlight([self bounds].size, [self cornerRadius],
                         mGeckoChild->DevPixelsToCocoaPoints(1));
 }
 
-- (void)releaseWidgets:(NSArray*)aWidgetArray
-{
-  if (!aWidgetArray) {
-    return;
-  }
-  NSInteger count = [aWidgetArray count];
-  for (NSInteger i = 0; i < count; ++i) {
-    NSNumber* pointer = (NSNumber*) [aWidgetArray objectAtIndex:i];
-    nsIWidget* widget = (nsIWidget*) [pointer unsignedIntegerValue];
-    NS_RELEASE(widget);
-  }
-}
-
 - (void)viewWillDraw
 {
   nsAutoRetainCocoaObject kungFuDeathGrip(self);
 
   if (mGeckoChild) {
     // The OS normally *will* draw our NSWindow, no matter what we do here.
     // But Gecko can delete our parent widget(s) (along with mGeckoChild)
     // while processing a paint request, which closes our NSWindow and
     // makes the OS throw an NSInternalInconsistencyException assertion when
     // it tries to draw it.  Sometimes the OS also aborts the browser process.
     // So we need to retain our parent(s) here and not release it/them until
     // the next time through the main thread's run loop.  When we do this we
     // also need to retain and release mGeckoChild, which holds a strong
-    // reference to us (otherwise we might have been deleted by the time
-    // releaseWidgets: is called on us).  See bug 550392.
+    // reference to us.  See bug 550392.
     nsIWidget* parent = mGeckoChild->GetParent();
     if (parent) {
-      NSMutableArray* widgetArray = [NSMutableArray arrayWithCapacity:3];
+      nsTArray<nsCOMPtr<nsIWidget>> widgetArray;
       while (parent) {
-        NS_ADDREF(parent);
-        [widgetArray addObject:[NSNumber numberWithUnsignedInteger:(NSUInteger)parent]];
+        widgetArray.AppendElement(parent);
         parent = parent->GetParent();
       }
-      NS_ADDREF(mGeckoChild);
-      [widgetArray addObject:[NSNumber numberWithUnsignedInteger:(NSUInteger)mGeckoChild]];
-      [self performSelector:@selector(releaseWidgets:)
-                 withObject:widgetArray
-                 afterDelay:0];
+      widgetArray.AppendElement(mGeckoChild);
+      nsCOMPtr<nsIRunnable> releaserRunnable =
+        new WidgetsReleaserRunnable(Move(widgetArray));
+      NS_DispatchToMainThread(releaserRunnable);
     }
 
     if ([self isUsingOpenGL]) {
       if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
         ClientLayerManager *manager = static_cast<ClientLayerManager*>(mGeckoChild->GetLayerManager());
         manager->AsShadowForwarder()->WindowOverlayChanged();
       }
     }
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -607,25 +607,25 @@ double nsIWidget::DefaultScaleOverride()
 
 //-------------------------------------------------------------------------
 //
 // Add a child to the list of children
 //
 //-------------------------------------------------------------------------
 void nsBaseWidget::AddChild(nsIWidget* aChild)
 {
-  MOZ_RELEASE_ASSERT(!aChild->GetNextSibling() && !aChild->GetPrevSibling(),
-                     "aChild not properly removed from its old child list");
+  MOZ_ASSERT(!aChild->GetNextSibling() && !aChild->GetPrevSibling(),
+             "aChild not properly removed from its old child list");
 
   if (!mFirstChild) {
     mFirstChild = mLastChild = aChild;
   } else {
     // append to the list
-    MOZ_RELEASE_ASSERT(mLastChild);
-    MOZ_RELEASE_ASSERT(!mLastChild->GetNextSibling());
+    MOZ_ASSERT(mLastChild);
+    MOZ_ASSERT(!mLastChild->GetNextSibling());
     mLastChild->SetNextSibling(aChild);
     aChild->SetPrevSibling(mLastChild);
     mLastChild = aChild;
   }
 }
 
 
 //-------------------------------------------------------------------------
--- a/widget/tests/test_keycodes.xul
+++ b/widget/tests/test_keycodes.xul
@@ -268,22 +268,19 @@ function* runKeyEventTests()
   {
     aEvent.preventDefault();
   }
 
   const SHOULD_DELIVER_NONE             = 0x0;
   const SHOULD_DELIVER_KEYDOWN          = 0x1;
   const SHOULD_DELIVER_KEYPRESS         = 0x2;
   const SHOULD_DELIVER_KEYUP            = 0x4;
-  const SHOULD_NOT_CAUSE_INPUT          = 0x8;
   const SHOULD_DELIVER_ALL              = SHOULD_DELIVER_KEYDOWN |
                                           SHOULD_DELIVER_KEYPRESS |
                                           SHOULD_DELIVER_KEYUP;
-  const SHOULD_DELIVER_ALL_BUT_NOT_CAUSE_INPUT = SHOULD_DELIVER_ALL |
-                                          SHOULD_NOT_CAUSE_INPUT;
   const SHOULD_DELIVER_KEYDOWN_KEYUP    = SHOULD_DELIVER_KEYDOWN |
                                           SHOULD_DELIVER_KEYUP;
   const SHOULD_DELIVER_KEYDOWN_KEYPRESS = SHOULD_DELIVER_KEYDOWN |
                                           SHOULD_DELIVER_KEYPRESS;
 
   // The first parameter is the complete input event. The second parameter is
   // what to test against. The third parameter is which key events should be
   // delived for the event.
@@ -360,19 +357,18 @@ function* runKeyEventTests()
 
         if (firedEventType != "") {
           var e = eventList[i];
           if (e.type == "keypress") {
             var isCtrlExpected =
               !!(aEvent.modifiers.ctrlKey || aEvent.modifiers.ctrlRightKey);
             var isAltExpected =
               !!(aEvent.modifiers.altKey || aEvent.modifiers.altRightKey);
-            if (IS_WIN && aEvent.modifiers.altGrKey) {
-              isCtrlExpected = isAltExpected =
-                ((aShouldDelivedEvent & SHOULD_NOT_CAUSE_INPUT) != 0);
+            if (IS_WIN && (aEvent.modifiers.altGrKey || isCtrlExpected && isAltExpected)) {
+              isCtrlExpected = isAltExpected = (aEvent.chars == "");
             }
             is(e.ctrlKey, isCtrlExpected, name + ", Ctrl mismatch");
             is(e.metaKey, !!(aEvent.modifiers.metaKey || aEvent.modifiers.metaRightKey), name + ", Command mismatch");
             is(e.altKey, isAltExpected, name + ", Alt mismatch");
             is(e.shiftKey, !!(aEvent.modifiers.shiftKey || aEvent.modifiers.shiftRightKey), name + ", Shift mismatch");
           }
 
           var expectedKeyValue =
@@ -1909,43 +1905,16 @@ function* runKeyEventTests()
 
   function testKeysOnWindows()
   {
     // On Windows, you can use Spy++ or Winspector (free) to watch window messages.
     // The keyCode is given by the wParam of the last WM_KEYDOWN message. The
     // chars string is given by the wParam of the WM_CHAR message. unmodifiedChars
     // is not needed on Windows.
 
-    // Plain text input
-    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
-                   modifiers:{}, chars:"a"},
-                  "a", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
-    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
-                   modifiers:{}, chars:"b"},
-                  "b", "KeyB", nsIDOMKeyEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
-    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
-                   modifiers:{shiftKey:1}, chars:"A"},
-                  "A", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
-
-    // Ctrl keys
-    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
-                   modifiers:{ctrlKey:1}, chars:"\u0001"},
-                  "a", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
-    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
-                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001"},
-                  "A", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
-
-    // Alt keys
-    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
-                   modifiers:{altKey:1}, chars:"a"},
-                  "a", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
-    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
-                   modifiers:{altKey:1, shiftKey:1}, chars:"A"},
-                  "A", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
-
     // Shift-ctrl-alt generates no WM_CHAR, but we still get a keypress
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
                    modifiers:{altKey:1, ctrlKey:1, shiftKey:1}, chars:""},
                   "A", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
     // Greek plain text
     yield testKey({layout:KEYBOARD_LAYOUT_GREEK, keyCode:WIN_VK_A,
                    modifiers:{}, chars:"\u03b1"},
@@ -2090,317 +2059,888 @@ function* runKeyEventTests()
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_INSERT,
                    modifiers:{}, chars:""},
                   "Insert", "Insert", nsIDOMKeyEvent.DOM_VK_INSERT, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_DELETE,
                    modifiers:{}, chars:""},
                   "Delete", "Delete", nsIDOMKeyEvent.DOM_VK_DELETE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
     // Backspace and Enter are handled with special path in mozilla::widget::NativeKey.  So, let's test them with modifiers too.
+    // Note that when both Ctrl and Alt are pressed, they don't cause WM_(SYS)CHAR message.
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
                    modifiers:{ctrlKey:1}, chars:"\u007F"},
                   "Backspace", "Backspace", nsIDOMKeyEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
                    modifiers:{altKey:1}, chars:"\u0008"},
                   "Backspace", "Backspace", nsIDOMKeyEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_BACK,
+                   modifiers:{ctrl:1, altKey:1}, chars:""},
+                  "Backspace", "Backspace", nsIDOMKeyEvent.DOM_VK_BACK_SPACE, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
                    modifiers:{ctrlKey:1}, chars:"\n"},
                   "Enter", "Enter", nsIDOMKeyEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
                    modifiers:{altKey:1}, chars:"\r"},
                   "Enter", "Enter", nsIDOMKeyEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_RETURN,
+                   modifiers:{ctrl:1, altKey:1}, chars:""},
+                  "Enter", "Enter", nsIDOMKeyEvent.DOM_VK_RETURN, "", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
     // US
     // Alphabet
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
                    modifiers:{}, chars:"a"},
                   "a", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
                    modifiers:{shiftKey:1}, chars:"A"},
                   "A", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+                   modifiers:{ctrlKey:1}, chars:"\u0001"},
+                  "a", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0001"},
+                  "A", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+                   modifiers:{altKey:1}, chars:"a"},
+                  "a", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "a", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_A,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"A"},
+                  "A", "KeyA", nsIDOMKeyEvent.DOM_VK_A, "A", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
                    modifiers:{}, chars:"b"},
                   "b", "KeyB", nsIDOMKeyEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
                    modifiers:{shiftKey:1}, chars:"B"},
                   "B", "KeyB", nsIDOMKeyEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+                   modifiers:{ctrlKey:1}, chars:"\u0002"},
+                  "b", "KeyB", nsIDOMKeyEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0002"},
+                  "B", "KeyB", nsIDOMKeyEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+                   modifiers:{altKey:1}, chars:"b"},
+                  "b", "KeyB", nsIDOMKeyEvent.DOM_VK_B, "b", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_B,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"B"},
+                  "B", "KeyB", nsIDOMKeyEvent.DOM_VK_B, "B", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
                    modifiers:{}, chars:"c"},
                   "c", "KeyC", nsIDOMKeyEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
                    modifiers:{shiftKey:1}, chars:"C"},
                   "C", "KeyC", nsIDOMKeyEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+                   modifiers:{ctrlKey:1}, chars:"\u0003"},
+                  "c", "KeyC", nsIDOMKeyEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0003"},
+                  "C", "KeyC", nsIDOMKeyEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+                   modifiers:{altKey:1}, chars:"c"},
+                  "c", "KeyC", nsIDOMKeyEvent.DOM_VK_C, "c", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_C,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"C"},
+                  "C", "KeyC", nsIDOMKeyEvent.DOM_VK_C, "C", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
                    modifiers:{}, chars:"d"},
                   "d", "KeyD", nsIDOMKeyEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
                    modifiers:{shiftKey:1}, chars:"D"},
                   "D", "KeyD", nsIDOMKeyEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+                   modifiers:{ctrlKey:1}, chars:"\u0004"},
+                  "d", "KeyD", nsIDOMKeyEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0004"},
+                  "D", "KeyD", nsIDOMKeyEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+                   modifiers:{altKey:1}, chars:"d"},
+                  "d", "KeyD", nsIDOMKeyEvent.DOM_VK_D, "d", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_D,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"D"},
+                  "D", "KeyD", nsIDOMKeyEvent.DOM_VK_D, "D", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
                    modifiers:{}, chars:"e"},
                   "e", "KeyE", nsIDOMKeyEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
                    modifiers:{shiftKey:1}, chars:"E"},
                   "E", "KeyE", nsIDOMKeyEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+                   modifiers:{ctrlKey:1}, chars:"\u0005"},
+                  "e", "KeyE", nsIDOMKeyEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0005"},
+                  "E", "KeyE", nsIDOMKeyEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+                   modifiers:{altKey:1}, chars:"e"},
+                  "e", "KeyE", nsIDOMKeyEvent.DOM_VK_E, "e", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_E,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"E"},
+                  "E", "KeyE", nsIDOMKeyEvent.DOM_VK_E, "E", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
                    modifiers:{}, chars:"f"},
                   "f", "KeyF", nsIDOMKeyEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
                    modifiers:{shiftKey:1}, chars:"F"},
                   "F", "KeyF", nsIDOMKeyEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+                   modifiers:{ctrlKey:1}, chars:"\u0006"},
+                  "f", "KeyF", nsIDOMKeyEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0006"},
+                  "F", "KeyF", nsIDOMKeyEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+                   modifiers:{altKey:1}, chars:"f"},
+                  "f", "KeyF", nsIDOMKeyEvent.DOM_VK_F, "f", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_F,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"F"},
+                  "F", "KeyF", nsIDOMKeyEvent.DOM_VK_F, "F", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
                    modifiers:{}, chars:"g"},
                   "g", "KeyG", nsIDOMKeyEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
                    modifiers:{shiftKey:1}, chars:"G"},
                   "G", "KeyG", nsIDOMKeyEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+                   modifiers:{ctrlKey:1}, chars:"\u0007"},
+                  "g", "KeyG", nsIDOMKeyEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0007"},
+                  "G", "KeyG", nsIDOMKeyEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+                   modifiers:{altKey:1}, chars:"g"},
+                  "g", "KeyG", nsIDOMKeyEvent.DOM_VK_G, "g", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_G,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"G"},
+                  "G", "KeyG", nsIDOMKeyEvent.DOM_VK_G, "G", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
                    modifiers:{}, chars:"h"},
                   "h", "KeyH", nsIDOMKeyEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
                    modifiers:{shiftKey:1}, chars:"H"},
                   "H", "KeyH", nsIDOMKeyEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+                   modifiers:{ctrlKey:1}, chars:"\u0008"},
+                  "h", "KeyH", nsIDOMKeyEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0008"},
+                  "H", "KeyH", nsIDOMKeyEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+                   modifiers:{altKey:1}, chars:"h"},
+                  "h", "KeyH", nsIDOMKeyEvent.DOM_VK_H, "h", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_H,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"H"},
+                  "H", "KeyH", nsIDOMKeyEvent.DOM_VK_H, "H", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
                    modifiers:{}, chars:"i"},
                   "i", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
                    modifiers:{shiftKey:1}, chars:"I"},
                   "I", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+                   modifiers:{ctrlKey:1}, chars:"\u0009"},
+                  "i", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0009"},
+                  "I", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+                   modifiers:{altKey:1}, chars:"i"},
+                  "i", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "i", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_I,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"I"},
+                  "I", "KeyI", nsIDOMKeyEvent.DOM_VK_I, "I", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
                    modifiers:{}, chars:"j"},
                   "j", "KeyJ", nsIDOMKeyEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
                    modifiers:{shiftKey:1}, chars:"J"},
                   "J", "KeyJ", nsIDOMKeyEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+                   modifiers:{ctrlKey:1}, chars:"\u000A"},
+                  "j", "KeyJ", nsIDOMKeyEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000A"},
+                  "J", "KeyJ", nsIDOMKeyEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+                   modifiers:{altKey:1}, chars:"j"},
+                  "j", "KeyJ", nsIDOMKeyEvent.DOM_VK_J, "j", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_J,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"J"},
+                  "J", "KeyJ", nsIDOMKeyEvent.DOM_VK_J, "J", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
                    modifiers:{}, chars:"k"},
                   "k", "KeyK", nsIDOMKeyEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
                    modifiers:{shiftKey:1}, chars:"K"},
                   "K", "KeyK", nsIDOMKeyEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+                   modifiers:{ctrlKey:1}, chars:"\u000B"},
+                  "k", "KeyK", nsIDOMKeyEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000B"},
+                  "K", "KeyK", nsIDOMKeyEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+                   modifiers:{altKey:1}, chars:"k"},
+                  "k", "KeyK", nsIDOMKeyEvent.DOM_VK_K, "k", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_K,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"K"},
+                  "K", "KeyK", nsIDOMKeyEvent.DOM_VK_K, "K", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
                    modifiers:{}, chars:"l"},
                   "l", "KeyL", nsIDOMKeyEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
                    modifiers:{shiftKey:1}, chars:"L"},
                   "L", "KeyL", nsIDOMKeyEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+                   modifiers:{ctrlKey:1}, chars:"\u000C"},
+                  "l", "KeyL", nsIDOMKeyEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000C"},
+                  "L", "KeyL", nsIDOMKeyEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+                   modifiers:{altKey:1}, chars:"l"},
+                  "l", "KeyL", nsIDOMKeyEvent.DOM_VK_L, "l", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_L,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"L"},
+                  "L", "KeyL", nsIDOMKeyEvent.DOM_VK_L, "L", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
                    modifiers:{}, chars:"m"},
                   "m", "KeyM", nsIDOMKeyEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
                    modifiers:{shiftKey:1}, chars:"M"},
                   "M", "KeyM", nsIDOMKeyEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+                   modifiers:{ctrlKey:1}, chars:"\u000D"},
+                  "m", "KeyM", nsIDOMKeyEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000D"},
+                  "M", "KeyM", nsIDOMKeyEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+                   modifiers:{altKey:1}, chars:"m"},
+                  "m", "KeyM", nsIDOMKeyEvent.DOM_VK_M, "m", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_M,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"M"},
+                  "M", "KeyM", nsIDOMKeyEvent.DOM_VK_M, "M", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
                    modifiers:{}, chars:"n"},
                   "n", "KeyN", nsIDOMKeyEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
                    modifiers:{shiftKey:1}, chars:"N"},
                   "N", "KeyN", nsIDOMKeyEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+                   modifiers:{ctrlKey:1}, chars:"\u000E"},
+                  "n", "KeyN", nsIDOMKeyEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000E"},
+                  "N", "KeyN", nsIDOMKeyEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+                   modifiers:{altKey:1}, chars:"n"},
+                  "n", "KeyN", nsIDOMKeyEvent.DOM_VK_N, "n", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_N,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"N"},
+                  "N", "KeyN", nsIDOMKeyEvent.DOM_VK_N, "N", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
                    modifiers:{}, chars:"o"},
                   "o", "KeyO", nsIDOMKeyEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
                    modifiers:{shiftKey:1}, chars:"O"},
                   "O", "KeyO", nsIDOMKeyEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+                   modifiers:{ctrlKey:1}, chars:"\u000F"},
+                  "o", "KeyO", nsIDOMKeyEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u000F"},
+                  "O", "KeyO", nsIDOMKeyEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+                   modifiers:{altKey:1}, chars:"o"},
+                  "o", "KeyO", nsIDOMKeyEvent.DOM_VK_O, "o", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_O,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"O"},
+                  "O", "KeyO", nsIDOMKeyEvent.DOM_VK_O, "O", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
                    modifiers:{}, chars:"p"},
                   "p", "KeyP", nsIDOMKeyEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
                    modifiers:{shiftKey:1}, chars:"P"},
                   "P", "KeyP", nsIDOMKeyEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+                   modifiers:{ctrlKey:1}, chars:"\u0010"},
+                  "p", "KeyP", nsIDOMKeyEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0010"},
+                  "P", "KeyP", nsIDOMKeyEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+                   modifiers:{altKey:1}, chars:"p"},
+                  "p", "KeyP", nsIDOMKeyEvent.DOM_VK_P, "p", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_P,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"P"},
+                  "P", "KeyP", nsIDOMKeyEvent.DOM_VK_P, "P", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
                    modifiers:{}, chars:"q"},
                   "q", "KeyQ", nsIDOMKeyEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
                    modifiers:{shiftKey:1}, chars:"Q"},
                   "Q", "KeyQ", nsIDOMKeyEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+                   modifiers:{ctrlKey:1}, chars:"\u0011"},
+                  "q", "KeyQ", nsIDOMKeyEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0011"},
+                  "Q", "KeyQ", nsIDOMKeyEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+                   modifiers:{altKey:1}, chars:"q"},
+                  "q", "KeyQ", nsIDOMKeyEvent.DOM_VK_Q, "q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Q,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"Q"},
+                  "Q", "KeyQ", nsIDOMKeyEvent.DOM_VK_Q, "Q", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
                    modifiers:{}, chars:"r"},
                   "r", "KeyR", nsIDOMKeyEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
                    modifiers:{shiftKey:1}, chars:"R"},
                   "R", "KeyR", nsIDOMKeyEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+                   modifiers:{ctrlKey:1}, chars:"\u0012"},
+                  "r", "KeyR", nsIDOMKeyEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0012"},
+                  "R", "KeyR", nsIDOMKeyEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+                   modifiers:{altKey:1}, chars:"r"},
+                  "r", "KeyR", nsIDOMKeyEvent.DOM_VK_R, "r", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_R,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"R"},
+                  "R", "KeyR", nsIDOMKeyEvent.DOM_VK_R, "R", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
                    modifiers:{}, chars:"s"},
                   "s", "KeyS", nsIDOMKeyEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
                    modifiers:{shiftKey:1}, chars:"S"},
                   "S", "KeyS", nsIDOMKeyEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+                   modifiers:{ctrlKey:1}, chars:"\u0013"},
+                  "s", "KeyS", nsIDOMKeyEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0013"},
+                  "S", "KeyS", nsIDOMKeyEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+                   modifiers:{altKey:1}, chars:"s"},
+                  "s", "KeyS", nsIDOMKeyEvent.DOM_VK_S, "s", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_S,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"S"},
+                  "S", "KeyS", nsIDOMKeyEvent.DOM_VK_S, "S", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
                    modifiers:{}, chars:"t"},
                   "t", "KeyT", nsIDOMKeyEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
                    modifiers:{shiftKey:1}, chars:"T"},
                   "T", "KeyT", nsIDOMKeyEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+                   modifiers:{ctrlKey:1}, chars:"\u0014"},
+                  "t", "KeyT", nsIDOMKeyEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0014"},
+                  "T", "KeyT", nsIDOMKeyEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+                   modifiers:{altKey:1}, chars:"t"},
+                  "t", "KeyT", nsIDOMKeyEvent.DOM_VK_T, "t", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_T,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"T"},
+                  "T", "KeyT", nsIDOMKeyEvent.DOM_VK_T, "T", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
                    modifiers:{}, chars:"u"},
                   "u", "KeyU", nsIDOMKeyEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
                    modifiers:{shiftKey:1}, chars:"U"},
                   "U", "KeyU", nsIDOMKeyEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+                   modifiers:{ctrlKey:1}, chars:"\u0015"},
+                  "u", "KeyU", nsIDOMKeyEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0015"},
+                  "U", "KeyU", nsIDOMKeyEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+                   modifiers:{altKey:1}, chars:"u"},
+                  "u", "KeyU", nsIDOMKeyEvent.DOM_VK_U, "u", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_U,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"U"},
+                  "U", "KeyU", nsIDOMKeyEvent.DOM_VK_U, "U", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
                    modifiers:{}, chars:"v"},
                   "v", "KeyV", nsIDOMKeyEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
                    modifiers:{shiftKey:1}, chars:"V"},
                   "V", "KeyV", nsIDOMKeyEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+                   modifiers:{ctrlKey:1}, chars:"\u0016"},
+                  "v", "KeyV", nsIDOMKeyEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0016"},
+                  "V", "KeyV", nsIDOMKeyEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+                   modifiers:{altKey:1}, chars:"v"},
+                  "v", "KeyV", nsIDOMKeyEvent.DOM_VK_V, "v", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_V,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"V"},
+                  "V", "KeyV", nsIDOMKeyEvent.DOM_VK_V, "V", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
                    modifiers:{}, chars:"w"},
                   "w", "KeyW", nsIDOMKeyEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
                    modifiers:{shiftKey:1}, chars:"W"},
                   "W", "KeyW", nsIDOMKeyEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+                   modifiers:{ctrlKey:1}, chars:"\u0017"},
+                  "w", "KeyW", nsIDOMKeyEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0017"},
+                  "W", "KeyW", nsIDOMKeyEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+                   modifiers:{altKey:1}, chars:"w"},
+                  "w", "KeyW", nsIDOMKeyEvent.DOM_VK_W, "w", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_W,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"W"},
+                  "W", "KeyW", nsIDOMKeyEvent.DOM_VK_W, "W", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
                    modifiers:{}, chars:"x"},
                   "x", "KeyX", nsIDOMKeyEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
                    modifiers:{shiftKey:1}, chars:"X"},
                   "X", "KeyX", nsIDOMKeyEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+                   modifiers:{ctrlKey:1}, chars:"\u0018"},
+                  "x", "KeyX", nsIDOMKeyEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0018"},
+                  "X", "KeyX", nsIDOMKeyEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+                   modifiers:{altKey:1}, chars:"x"},
+                  "x", "KeyX", nsIDOMKeyEvent.DOM_VK_X, "x", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_X,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"X"},
+                  "X", "KeyX", nsIDOMKeyEvent.DOM_VK_X, "X", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
                    modifiers:{}, chars:"y"},
                   "y", "KeyY", nsIDOMKeyEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
                    modifiers:{shiftKey:1}, chars:"Y"},
                   "Y", "KeyY", nsIDOMKeyEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+                   modifiers:{ctrlKey:1}, chars:"\u0019"},
+                  "y", "KeyY", nsIDOMKeyEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u0019"},
+                  "Y", "KeyY", nsIDOMKeyEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+                   modifiers:{altKey:1}, chars:"y"},
+                  "y", "KeyY", nsIDOMKeyEvent.DOM_VK_Y, "y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Y,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"Y"},
+                  "Y", "KeyY", nsIDOMKeyEvent.DOM_VK_Y, "Y", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
                    modifiers:{}, chars:"z"},
                   "z", "KeyZ", nsIDOMKeyEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
                    modifiers:{shiftKey:1}, chars:"Z"},
                   "Z", "KeyZ", nsIDOMKeyEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+                   modifiers:{ctrlKey:1}, chars:"\u001A"},
+                  "z", "KeyZ", nsIDOMKeyEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001A"},
+                  "Z", "KeyZ", nsIDOMKeyEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+                   modifiers:{altKey:1}, chars:"z"},
+                  "z", "KeyZ", nsIDOMKeyEvent.DOM_VK_Z, "z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_Z,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"Z"},
+                  "Z", "KeyZ", nsIDOMKeyEvent.DOM_VK_Z, "Z", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
     // Numeric
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
                    modifiers:{}, chars:"0"},
                   "0", "Digit0", nsIDOMKeyEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
                    modifiers:{shiftKey:1}, chars:")"},
                   ")", "Digit0", nsIDOMKeyEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "0", "Digit0", nsIDOMKeyEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  ")", "Digit0", nsIDOMKeyEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+                   modifiers:{altKey:1}, chars:"0"},
+                  "0", "Digit0", nsIDOMKeyEvent.DOM_VK_0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_0,
+                   modifiers:{altKey:1, shiftKey:1}, chars:")"},
+                  ")", "Digit0", nsIDOMKeyEvent.DOM_VK_0, ")", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
                    modifiers:{}, chars:"1"},
                   "1", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
                    modifiers:{shiftKey:1}, chars:"!"},
                   "!", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "1", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "!", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+                   modifiers:{altKey:1}, chars:"1"},
+                  "1", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_1,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"!"},
+                  "!", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
                    modifiers:{}, chars:"2"},
                   "2", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
                    modifiers:{shiftKey:1}, chars:"@"},
                   "@", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "2", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "@", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+                   modifiers:{altKey:1}, chars:"2"},
+                  "2", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_2,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"@"},
+                  "@", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
                    modifiers:{}, chars:"3"},
                   "3", "Digit3", nsIDOMKeyEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
                    modifiers:{shiftKey:1}, chars:"#"},
                   "#", "Digit3", nsIDOMKeyEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "3", "Digit3", nsIDOMKeyEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "#", "Digit3", nsIDOMKeyEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+                   modifiers:{altKey:1}, chars:"3"},
+                  "3", "Digit3", nsIDOMKeyEvent.DOM_VK_3, "3", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_3,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"#"},
+                  "#", "Digit3", nsIDOMKeyEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
                    modifiers:{}, chars:"4"},
                   "4", "Digit4", nsIDOMKeyEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
                    modifiers:{shiftKey:1}, chars:"$"},
                   "$", "Digit4", nsIDOMKeyEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "4", "Digit4", nsIDOMKeyEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "$", "Digit4", nsIDOMKeyEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+                   modifiers:{altKey:1}, chars:"4"},
+                  "4", "Digit4", nsIDOMKeyEvent.DOM_VK_4, "4", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_4,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"$"},
+                  "$", "Digit4", nsIDOMKeyEvent.DOM_VK_4, "$", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
                    modifiers:{}, chars:"5"},
                   "5", "Digit5", nsIDOMKeyEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
                    modifiers:{shiftKey:1}, chars:"%"},
                   "%", "Digit5", nsIDOMKeyEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "5", "Digit5", nsIDOMKeyEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "%", "Digit5", nsIDOMKeyEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+                   modifiers:{altKey:1}, chars:"5"},
+                  "5", "Digit5", nsIDOMKeyEvent.DOM_VK_5, "5", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_5,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"%"},
+                  "%", "Digit5", nsIDOMKeyEvent.DOM_VK_5, "%", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
                    modifiers:{}, chars:"6"},
                   "6", "Digit6", nsIDOMKeyEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
                    modifiers:{shiftKey:1}, chars:"^"},
                   "^", "Digit6", nsIDOMKeyEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "6", "Digit6", nsIDOMKeyEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001E"},
+                  "^", "Digit6", nsIDOMKeyEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+                   modifiers:{altKey:1}, chars:"6"},
+                  "6", "Digit6", nsIDOMKeyEvent.DOM_VK_6, "6", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_6,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"^"},
+                  "^", "Digit6", nsIDOMKeyEvent.DOM_VK_6, "^", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
                    modifiers:{}, chars:"7"},
                   "7", "Digit7", nsIDOMKeyEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
                    modifiers:{shiftKey:1}, chars:"&"},
                   "&", "Digit7", nsIDOMKeyEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "7", "Digit7", nsIDOMKeyEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "&", "Digit7", nsIDOMKeyEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+                   modifiers:{altKey:1}, chars:"7"},
+                  "7", "Digit7", nsIDOMKeyEvent.DOM_VK_7, "7", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_7,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"&"},
+                  "&", "Digit7", nsIDOMKeyEvent.DOM_VK_7, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
                    modifiers:{}, chars:"8"},
                   "8", "Digit8", nsIDOMKeyEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
                    modifiers:{shiftKey:1}, chars:"*"},
                   "*", "Digit8", nsIDOMKeyEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+                   modifiers:{ctrlKey:1, }, chars:""},
+                  "8", "Digit8", nsIDOMKeyEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "*", "Digit8", nsIDOMKeyEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+                   modifiers:{altKey:1}, chars:"8"},
+                  "8", "Digit8", nsIDOMKeyEvent.DOM_VK_8, "8", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_8,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"*"},
+                  "*", "Digit8", nsIDOMKeyEvent.DOM_VK_8, "*", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
                    modifiers:{}, chars:"9"},
                   "9", "Digit9", nsIDOMKeyEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
                    modifiers:{shiftKey:1}, chars:"("},
                   "(", "Digit9", nsIDOMKeyEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "9", "Digit9", nsIDOMKeyEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "(", "Digit9", nsIDOMKeyEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+                   modifiers:{altKey:1}, chars:"9"},
+                  "9", "Digit9", nsIDOMKeyEvent.DOM_VK_9, "9", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_9,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"("},
+                  "(", "Digit9", nsIDOMKeyEvent.DOM_VK_9, "(", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
     // OEM keys
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
                    modifiers:{}, chars:"-"},
                   "-", "Minus", nsIDOMKeyEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
                    modifiers:{shiftKey:1}, chars:"_"},
                   "_", "Minus", nsIDOMKeyEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "-", "Minus", nsIDOMKeyEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:"\u001F"},
+                  "_", "Minus", nsIDOMKeyEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+                   modifiers:{altKey:1}, chars:"-"},
+                  "-", "Minus", nsIDOMKeyEvent.DOM_VK_HYPHEN_MINUS, "-", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_MINUS,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"_"},
+                  "_", "Minus", nsIDOMKeyEvent.DOM_VK_HYPHEN_MINUS, "_", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
                    modifiers:{}, chars:"="},
                   "=", "Equal", nsIDOMKeyEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
                    modifiers:{shiftKey:1}, chars:"+"},
                   "+", "Equal", nsIDOMKeyEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "=", "Equal", nsIDOMKeyEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "+", "Equal", nsIDOMKeyEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+                   modifiers:{altKey:1}, chars:"="},
+                  "=", "Equal", nsIDOMKeyEvent.DOM_VK_EQUALS, "=", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PLUS,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"+"},
+                  "+", "Equal", nsIDOMKeyEvent.DOM_VK_EQUALS, "+", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
                    modifiers:{}, chars:"["},
                   "[", "BracketLeft", nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
                    modifiers:{shiftKey:1}, chars:"{"},
                   "{", "BracketLeft", nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+                   modifiers:{ctrlKey:1}, chars:"\u001B"},
+                  "[", "BracketLeft", nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "{", "BracketLeft", nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+                   modifiers:{altKey:1}, chars:"["},
+                  "[", "BracketLeft", nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, "[", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_4,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"{"},
+                  "{", "BracketLeft", nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET, "{", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
                    modifiers:{}, chars:"]"},
                   "]", "BracketRight", nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
                    modifiers:{shiftKey:1}, chars:"}"},
                   "}", "BracketRight", nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+                   modifiers:{ctrlKey:1}, chars:"\u001D"},
+                  "]", "BracketRight", nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "}", "BracketRight", nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+                   modifiers:{altKey:1}, chars:"]"},
+                  "]", "BracketRight", nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, "]", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_6,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"}"},
+                  "}", "BracketRight", nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET, "}", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
                    modifiers:{}, chars:";"},
                   ";", "Semicolon", nsIDOMKeyEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
                    modifiers:{shiftKey:1}, chars:":"},
                   ":", "Semicolon", nsIDOMKeyEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  ";", "Semicolon", nsIDOMKeyEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  ":", "Semicolon", nsIDOMKeyEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+                   modifiers:{altKey:1}, chars:";"},
+                  ";", "Semicolon", nsIDOMKeyEvent.DOM_VK_SEMICOLON, ";", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_1,
+                   modifiers:{altKey:1, shiftKey:1}, chars:":"},
+                  ":", "Semicolon", nsIDOMKeyEvent.DOM_VK_SEMICOLON, ":", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
                    modifiers:{}, chars:"'"},
                   "'", "Quote", nsIDOMKeyEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
                    modifiers:{shiftKey:1}, chars:"\""},
                   "\"", "Quote", nsIDOMKeyEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "'", "Quote", nsIDOMKeyEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "\"", "Quote", nsIDOMKeyEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+                   modifiers:{altKey:1}, chars:"'"},
+                  "'", "Quote", nsIDOMKeyEvent.DOM_VK_QUOTE, "'", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_7,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"\""},
+                  "\"", "Quote", nsIDOMKeyEvent.DOM_VK_QUOTE, "\"", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
                    modifiers:{}, chars:"\\"},
                   "\\", "Backslash", nsIDOMKeyEvent.DOM_VK_BACK_SLASH, "\\", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,
                    modifiers:{shiftKey:1}, chars:"|"},
                   "|", "Backslash", nsIDOMKeyEvent.DOM_VK_BACK_SLASH, "|", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_5,