Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 07 Oct 2016 09:44:29 -0400
changeset 422189 70758fd844ecca6f084414a52406dd58795d0715
parent 422146 af534f12f8fee939e100f3b78b22dd712cecfbb4 (current diff)
parent 422188 ea8624a9b11e89b831b830cef70c47ae67ccead1 (diff)
child 422190 e802f011180f0c5dcec0f05511ed4c5bb64ddae6
child 422230 6dd3d752338c47e1c23cfc863ea4fa65e3946c88
child 423523 8ddc482bbb844d7c0783ce6894d76da315be0e99
push id31701
push userbmo:james@hoppipolla.co.uk
push dateFri, 07 Oct 2016 14:21:46 +0000
reviewersmerge
milestone52.0a1
Merge m-c to inbound. a=merge
dom/push/PushService.jsm
widget/cocoa/nsChildView.mm
widget/windows/nsWindow.cpp
--- a/.eslintignore
+++ b/.eslintignore
@@ -97,17 +97,17 @@ devtools/client/shared/webgl-utils.js
 devtools/client/shared/developer-toolbar.js
 devtools/client/shared/components/test/**
 devtools/client/shared/redux/middleware/test/**
 devtools/client/shared/test/**
 !devtools/client/shared/test/test-actor-registry.js
 devtools/client/shared/widgets/*.jsm
 devtools/client/sourceeditor/test/*.js
 devtools/client/webaudioeditor/**
-#devtools/client/webconsole/**
+devtools/client/webconsole/**
 !devtools/client/webconsole/panel.js
 !devtools/client/webconsole/jsterm.js
 !devtools/client/webconsole/console-commands.js
 devtools/client/webide/**
 !devtools/client/webide/components/webideCli.js
 devtools/server/*.js
 devtools/server/*.jsm
 !devtools/server/child.js
--- 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/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/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.5.498
+Current extension version is: 1.6.221
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf', ['exports
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdf = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.498';
-var pdfjsBuild = '1564dc3';
+var pdfjsVersion = '1.6.221';
+var pdfjsBuild = 'f8bd3d4';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -846,25 +846,25 @@ var Util = (function UtilClosure() {
   };
 
   Util.extendObj = function extendObj(obj1, obj2) {
     for (var key in obj2) {
       obj1[key] = obj2[key];
     }
   };
 
-  Util.getInheritableProperty = function Util_getInheritableProperty(dict,
-                                                                     name) {
+  Util.getInheritableProperty =
+      function Util_getInheritableProperty(dict, name, getArray) {
     while (dict && !dict.has(name)) {
       dict = dict.get('Parent');
     }
     if (!dict) {
       return null;
     }
-    return dict.get(name);
+    return getArray ? dict.getArray(name) : dict.get(name);
   };
 
   Util.inherit = function Util_inherit(sub, base, prototype) {
     sub.prototype = Object.create(base.prototype);
     sub.prototype.constructor = sub;
     for (var prop in prototype) {
       sub.prototype[prop] = prototype[prop];
     }
@@ -1988,16 +1988,18 @@ AnnotationElementFactory.prototype =
         return new TextAnnotationElement(parameters);
 
       case AnnotationType.WIDGET:
         var fieldType = parameters.data.fieldType;
 
         switch (fieldType) {
           case 'Tx':
             return new TextWidgetAnnotationElement(parameters);
+          case 'Ch':
+            return new ChoiceWidgetAnnotationElement(parameters);
         }
         return new WidgetAnnotationElement(parameters);
 
       case AnnotationType.POPUP:
         return new PopupAnnotationElement(parameters);
 
       case AnnotationType.HIGHLIGHT:
         return new HighlightAnnotationElement(parameters);
@@ -2312,19 +2314,17 @@ var TextAnnotationElement = (function Te
   return TextAnnotationElement;
 })();
 
 /**
  * @class
  * @alias WidgetAnnotationElement
  */
 var WidgetAnnotationElement = (function WidgetAnnotationElementClosure() {
-  function WidgetAnnotationElement(parameters) {
-    var isRenderable = parameters.renderInteractiveForms ||
-      (!parameters.data.hasAppearance && !!parameters.data.fieldValue);
+  function WidgetAnnotationElement(parameters, isRenderable) {
     AnnotationElement.call(this, parameters, isRenderable);
   }
 
   Util.inherit(WidgetAnnotationElement, AnnotationElement, {
     /**
      * Render the widget annotation's HTML element in the empty container.
      *
      * @public
@@ -2344,17 +2344,19 @@ var WidgetAnnotationElement = (function 
  * @class
  * @alias TextWidgetAnnotationElement
  */
 var TextWidgetAnnotationElement = (
     function TextWidgetAnnotationElementClosure() {
   var TEXT_ALIGNMENT = ['left', 'center', 'right'];
 
   function TextWidgetAnnotationElement(parameters) {
-    WidgetAnnotationElement.call(this, parameters);
+    var isRenderable = parameters.renderInteractiveForms ||
+      (!parameters.data.hasAppearance && !!parameters.data.fieldValue);
+    WidgetAnnotationElement.call(this, parameters, isRenderable);
   }
 
   Util.inherit(TextWidgetAnnotationElement, WidgetAnnotationElement, {
     /**
      * Render the text widget annotation's HTML element in the empty container.
      *
      * @public
      * @memberof TextWidgetAnnotationElement
@@ -2442,16 +2444,74 @@ var TextWidgetAnnotationElement = (
     }
   });
 
   return TextWidgetAnnotationElement;
 })();
 
 /**
  * @class
+ * @alias ChoiceWidgetAnnotationElement
+ */
+var ChoiceWidgetAnnotationElement = (
+    function ChoiceWidgetAnnotationElementClosure() {
+  function ChoiceWidgetAnnotationElement(parameters) {
+    WidgetAnnotationElement.call(this, parameters,
+                                 parameters.renderInteractiveForms);
+  }
+
+  Util.inherit(ChoiceWidgetAnnotationElement, WidgetAnnotationElement, {
+    /**
+     * Render the choice widget annotation's HTML element in the empty
+     * container.
+     *
+     * @public
+     * @memberof ChoiceWidgetAnnotationElement
+     * @returns {HTMLSectionElement}
+     */
+    render: function ChoiceWidgetAnnotationElement_render() {
+      this.container.className = 'choiceWidgetAnnotation';
+
+      var selectElement = document.createElement('select');
+      selectElement.disabled = this.data.readOnly;
+
+      if (!this.data.combo) {
+        // List boxes have a size and (optionally) multiple selection.
+        selectElement.size = this.data.options.length;
+
+        if (this.data.multiSelect) {
+          selectElement.multiple = true;
+        }
+      }
+
+      // Insert the options into the choice field.
+      for (var i = 0, ii = this.data.options.length; i < ii; i++) {
+        var option = this.data.options[i];
+
+        var optionElement = document.createElement('option');
+        optionElement.textContent = option.displayValue;
+        optionElement.value = option.exportValue;
+
+        if (this.data.fieldValue.indexOf(option.displayValue) >= 0) {
+          optionElement.setAttribute('selected', true);
+        }
+
+        selectElement.appendChild(optionElement);
+      }
+
+      this.container.appendChild(selectElement);
+      return this.container;
+    }
+  });
+
+  return ChoiceWidgetAnnotationElement;
+})();
+
+/**
+ * @class
  * @alias PopupAnnotationElement
  */
 var PopupAnnotationElement = (function PopupAnnotationElementClosure() {
   function PopupAnnotationElement(parameters) {
     var isRenderable = !!(parameters.data.title || parameters.data.contents);
     AnnotationElement.call(this, parameters, isRenderable);
   }
 
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf.worker', ['
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdfWorker = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.498';
-var pdfjsBuild = '1564dc3';
+var pdfjsVersion = '1.6.221';
+var pdfjsBuild = 'f8bd3d4';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -1832,25 +1832,25 @@ var Util = (function UtilClosure() {
   };
 
   Util.extendObj = function extendObj(obj1, obj2) {
     for (var key in obj2) {
       obj1[key] = obj2[key];
     }
   };
 
-  Util.getInheritableProperty = function Util_getInheritableProperty(dict,
-                                                                     name) {
+  Util.getInheritableProperty =
+      function Util_getInheritableProperty(dict, name, getArray) {
     while (dict && !dict.has(name)) {
       dict = dict.get('Parent');
     }
     if (!dict) {
       return null;
     }
-    return dict.get(name);
+    return getArray ? dict.getArray(name) : dict.get(name);
   };
 
   Util.inherit = function Util_inherit(sub, base, prototype) {
     sub.prototype = Object.create(base.prototype);
     sub.prototype.constructor = sub;
     for (var prop in prototype) {
       sub.prototype[prop] = prototype[prop];
     }
@@ -24655,17 +24655,20 @@ var Parser = (function ParserClosure() {
           stream = this.makeFilter(stream, filter.name, maybeLength, params);
           // after the first stream the length variable is invalid
           maybeLength = null;
         }
       }
       return stream;
     },
     makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
-      if (stream.dict.get('Length') === 0 && !maybeLength) {
+      // Since the 'Length' entry in the stream dictionary can be completely
+      // wrong, e.g. zero for non-empty streams, only skip parsing the stream
+      // when we can be absolutely certain that it actually is empty.
+      if (maybeLength === 0) {
         warn('Empty "' + name + '" stream.');
         return new NullStream(stream);
       }
       try {
         if (params && this.xref) {
           params = this.xref.fetchIfRef(params);
         }
         var xrefStreamStats = this.xref.stats.streamTypes;
@@ -39363,16 +39366,18 @@ AnnotationFactory.prototype = /** @lends
 
       case 'Widget':
         var fieldType = Util.getInheritableProperty(dict, 'FT');
         fieldType = isName(fieldType) ? fieldType.name : null;
 
         switch (fieldType) {
           case 'Tx':
             return new TextWidgetAnnotation(parameters);
+          case 'Ch':
+            return new ChoiceWidgetAnnotation(parameters);
         }
         warn('Unimplemented widget field type "' + fieldType + '", ' +
              'falling back to base field type.');
         return new WidgetAnnotation(parameters);
 
       case 'Popup':
         return new PopupAnnotation(parameters);
 
@@ -39876,29 +39881,31 @@ var AnnotationBorderStyle = (function An
 var WidgetAnnotation = (function WidgetAnnotationClosure() {
   function WidgetAnnotation(params) {
     Annotation.call(this, params);
 
     var dict = params.dict;
     var data = this.data;
 
     data.annotationType = AnnotationType.WIDGET;
-    data.fieldValue = stringToPDFString(
-      Util.getInheritableProperty(dict, 'V') || '');
+    data.fieldValue = Util.getInheritableProperty(dict, 'V',
+                                                  /* getArray = */ true);
     data.alternativeText = stringToPDFString(dict.get('TU') || '');
     data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
     var fieldType = Util.getInheritableProperty(dict, 'FT');
     data.fieldType = isName(fieldType) ? fieldType.name : null;
     this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
 
     data.fieldFlags = Util.getInheritableProperty(dict, 'Ff');
     if (!isInt(data.fieldFlags) || data.fieldFlags < 0) {
       data.fieldFlags = 0;
     }
 
+    data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
+
     // Hide signatures because we cannot validate them.
     if (data.fieldType === 'Sig') {
       this.setFlags(AnnotationFlag.HIDDEN);
     }
 
     // Building the full field name by collecting the field and
     // its ancestors 'T' data and joining them using '.'.
     var fieldName = [];
@@ -39950,32 +39957,34 @@ var WidgetAnnotation = (function WidgetA
 
   return WidgetAnnotation;
 })();
 
 var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
   function TextWidgetAnnotation(params) {
     WidgetAnnotation.call(this, params);
 
+    // The field value is always a string.
+    this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
+
     // Determine the alignment of text in the field.
     var alignment = Util.getInheritableProperty(params.dict, 'Q');
     if (!isInt(alignment) || alignment < 0 || alignment > 2) {
       alignment = null;
     }
     this.data.textAlignment = alignment;
 
     // Determine the maximum length of text in the field.
     var maximumLength = Util.getInheritableProperty(params.dict, 'MaxLen');
     if (!isInt(maximumLength) || maximumLength < 0) {
       maximumLength = null;
     }
     this.data.maxLen = maximumLength;
 
     // Process field flags for the display layer.
-    this.data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
     this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
     this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) &&
                      !this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) &&
                      !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) &&
                      !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
                      this.data.maxLen !== null;
   }
 
@@ -40009,16 +40018,72 @@ var TextWidgetAnnotation = (function Tex
           return operatorList;
         });
     }
   });
 
   return TextWidgetAnnotation;
 })();
 
+var ChoiceWidgetAnnotation = (function ChoiceWidgetAnnotationClosure() {
+  function ChoiceWidgetAnnotation(params) {
+    WidgetAnnotation.call(this, params);
+
+    // Determine the options. The options array may consist of strings or
+    // arrays. If the array consists of arrays, then the first element of
+    // each array is the export value and the second element of each array is
+    // the display value. If the array consists of strings, then these
+    // represent both the export and display value. In this case, we convert
+    // it to an array of arrays as well for convenience in the display layer.
+    this.data.options = [];
+
+    var options = params.dict.getArray('Opt');
+    if (isArray(options)) {
+      for (var i = 0, ii = options.length; i < ii; i++) {
+        var option = options[i];
+
+        this.data.options[i] = {
+          exportValue: isArray(option) ? option[0] : option,
+          displayValue: isArray(option) ? option[1] : option,
+        };
+      }
+    }
+
+    // Determine the field value. In this case, it may be a string or an
+    // array of strings. For convenience in the display layer, convert the
+    // string to an array of one string as well.
+    if (!isArray(this.data.fieldValue)) {
+      this.data.fieldValue = [this.data.fieldValue];
+    }
+
+    // Process field flags for the display layer.
+    this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
+    this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
+  }
+
+  Util.inherit(ChoiceWidgetAnnotation, WidgetAnnotation, {
+    getOperatorList:
+        function ChoiceWidgetAnnotation_getOperatorList(evaluator, task,
+                                                        renderForms) {
+      var operatorList = new OperatorList();
+
+      // Do not render form elements on the canvas when interactive forms are
+      // enabled. The display layer is responsible for rendering them instead.
+      if (renderForms) {
+        return Promise.resolve(operatorList);
+      }
+
+      return Annotation.prototype.getOperatorList.call(this, evaluator, task,
+                                                       renderForms);
+    }
+  });
+
+  return ChoiceWidgetAnnotation;
+})();
+
 var TextAnnotation = (function TextAnnotationClosure() {
   var DEFAULT_ICON_SIZE = 22; // px
 
   function TextAnnotation(parameters) {
     Annotation.call(this, parameters);
 
     this.data.annotationType = AnnotationType.TEXT;
 
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -97,17 +97,18 @@
 }
 
 .annotationLayer .textAnnotation img {
   position: absolute;
   cursor: pointer;
 }
 
 .annotationLayer .textWidgetAnnotation input,
-.annotationLayer .textWidgetAnnotation textarea {
+.annotationLayer .textWidgetAnnotation textarea,
+.annotationLayer .choiceWidgetAnnotation select {
   background-color: rgba(0, 54, 255, 0.13);
   border: 1px solid transparent;
   box-sizing: border-box;
   font-size: 9px;
   height: 100%;
   padding: 0 3px;
   vertical-align: top;
   width: 100%;
@@ -115,29 +116,32 @@
 
 .annotationLayer .textWidgetAnnotation textarea {
   font: message-box;
   font-size: 9px;
   resize: none;
 }
 
 .annotationLayer .textWidgetAnnotation input[disabled],
-.annotationLayer .textWidgetAnnotation textarea[disabled] {
+.annotationLayer .textWidgetAnnotation textarea[disabled],
+.annotationLayer .choiceWidgetAnnotation select[disabled] {
   background: none;
   border: 1px solid transparent;
   cursor: not-allowed;
 }
 
 .annotationLayer .textWidgetAnnotation input:hover,
-.annotationLayer .textWidgetAnnotation textarea:hover {
+.annotationLayer .textWidgetAnnotation textarea:hover,
+.annotationLayer .choiceWidgetAnnotation select:hover {
   border: 1px solid #000;
 }
 
 .annotationLayer .textWidgetAnnotation input:focus,
-.annotationLayer .textWidgetAnnotation textarea:focus {
+.annotationLayer .textWidgetAnnotation textarea:focus,
+.annotationLayer .choiceWidgetAnnotation select:focus {
   background: none;
   border: 1px solid transparent;
 }
 
 .annotationLayer .textWidgetAnnotation input.comb {
   font-family: monospace;
   padding-left: 2px;
   padding-right: 0;
@@ -1931,18 +1935,21 @@ html[dir='rtl'] #documentPropertiesOverl
   #printContainer {
     height: 100%;
   }
   /* wrapper around (scaled) print canvas elements */
   #printContainer > div {
     position: relative;
     top: 0;
     left: 0;
-    height: 100%;
-    overflow: hidden;
+    width: 1px;
+    height: 1px;
+    overflow: visible;
+    page-break-after: always;
+    page-break-inside: avoid;
   }
   #printContainer canvas {
     display: block;
   }
 }
 
 .visibleLargeView,
 .visibleMediumView,
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -5663,48 +5663,44 @@ var PDFPageView = (function PDFPageViewC
       return promise;
     },
 
     beforePrint: function PDFPageView_beforePrint(printContainer) {
       var CustomStyle = pdfjsLib.CustomStyle;
       var pdfPage = this.pdfPage;
 
       var viewport = pdfPage.getViewport(1);
-      // Use the same hack we use for high dpi displays for printing to get
-      // better output until bug 811002 is fixed in FF.
-      var PRINT_OUTPUT_SCALE = 2;
+
       var canvas = document.createElement('canvas');
 
-      // The logical size of the canvas.
-      canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
-      canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
-
-      // The rendered size of the canvas, relative to the size of canvasWrapper.
-      canvas.style.width = (PRINT_OUTPUT_SCALE * 100) + '%';
-
-      var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
-                                (1 / PRINT_OUTPUT_SCALE) + ')';
-      CustomStyle.setProp('transform' , canvas, cssScale);
-      CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
+      // The size of the canvas in pixels for printing.
+      var PRINT_RESOLUTION = 150;
+      var PRINT_UNITS = PRINT_RESOLUTION / 72.0;
+      canvas.width = Math.floor(viewport.width * PRINT_UNITS);
+      canvas.height = Math.floor(viewport.height * PRINT_UNITS);
+
+      // The physical size of the canvas as specified by the PDF document.
+      canvas.style.width = Math.floor(viewport.width * CSS_UNITS) + 'px';
+      canvas.style.height = Math.floor(viewport.height * CSS_UNITS) + 'px';
 
       var canvasWrapper = document.createElement('div');
       canvasWrapper.appendChild(canvas);
       printContainer.appendChild(canvasWrapper);
 
       canvas.mozPrintCallback = function(obj) {
         var ctx = obj.context;
 
         ctx.save();
         ctx.fillStyle = 'rgb(255, 255, 255)';
         ctx.fillRect(0, 0, canvas.width, canvas.height);
         ctx.restore();
-        ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
 
         var renderContext = {
           canvasContext: ctx,
+          transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
           viewport: viewport,
           intent: 'print'
         };
 
         pdfPage.render(renderContext).promise.then(function() {
           // Tell the printEngine that rendering this canvas/page has finished.
           obj.done();
         }, function(error) {
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -91,16 +91,17 @@ function getFrameScript() {
 }
 
 flags.testing = true;
 registerCleanupFunction(() => {
   flags.testing = false;
   Services.prefs.clearUserPref("devtools.dump.emit");
   Services.prefs.clearUserPref("devtools.toolbox.host");
   Services.prefs.clearUserPref("devtools.toolbox.previousHost");
+  Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
 });
 
 registerCleanupFunction(function* cleanup() {
   while (gBrowser.tabs.length > 1) {
     yield closeTabAndToolbox(gBrowser.selectedTab);
   }
 });
 
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -309,18 +309,22 @@ pref("devtools.webconsole.persistlog", f
 // in the Web Console to display a timestamp, or |false| to not display
 // any timestamps.
 pref("devtools.webconsole.timestampMessages", false);
 
 // Web Console automatic multiline mode: |true| if you want incomplete statements
 // to automatically trigger multiline editing (equivalent to shift + enter).
 pref("devtools.webconsole.autoMultiline", true);
 
-// Enable the experimental webconsole frontend (work in progress)
+// Enable the experimental webconsole frontend
+#if defined(NIGHTLY_BUILD)
+pref("devtools.webconsole.new-frontend-enabled", true);
+#else
 pref("devtools.webconsole.new-frontend-enabled", false);
+#endif
 
 // Enable the experimental support for source maps in console (work in progress)
 pref("devtools.sourcemap.locations.enabled", false);
 
 // The number of lines that are displayed in the web console.
 pref("devtools.hud.loglimit", 1000);
 
 // The number of lines that are displayed in the web console for the Net,
--- a/devtools/client/shared/components/reps/array.js
+++ b/devtools/client/shared/components/reps/array.js
@@ -33,36 +33,33 @@ define(function (require, exports, modul
 
       for (let i = 0; i < array.length && i < max; i++) {
         try {
           let value = array[i];
 
           delim = (i == array.length - 1 ? "" : ", ");
 
           items.push(ItemRep({
-            key: i,
             object: value,
             // Hardcode tiny mode to avoid recursive handling.
             mode: "tiny",
             delim: delim
           }));
         } catch (exc) {
           items.push(ItemRep({
-            key: i,
             object: exc,
             mode: "tiny",
             delim: delim
           }));
         }
       }
 
       if (array.length > max) {
         let objectLink = this.props.objectLink || DOM.span;
         items.push(Caption({
-          key: "more",
           object: objectLink({
             object: this.props.object
           }, (array.length - max) + " more…")
         }));
       }
 
       return items;
     },
@@ -119,34 +116,34 @@ define(function (require, exports, modul
       let items;
       let brackets;
       let needSpace = function (space) {
         return space ? { left: "[ ", right: " ]"} : { left: "[", right: "]"};
       };
 
       if (mode == "tiny") {
         let isEmpty = object.length === 0;
-        items = DOM.span({className: "length"}, isEmpty ? "" : object.length);
+        items = [DOM.span({className: "length"}, isEmpty ? "" : object.length)];
         brackets = needSpace(false);
       } else {
         let max = (mode == "short") ? 3 : 300;
         items = this.arrayIterator(object, max);
         brackets = needSpace(items.length > 0);
       }
 
       let objectLink = this.props.objectLink || DOM.span;
 
       return (
         DOM.span({
           className: "objectBox objectBox-array"},
           objectLink({
             className: "arrayLeftBracket",
             object: object
           }, brackets.left),
-          items,
+          ...items,
           objectLink({
             className: "arrayRightBracket",
             object: object
           }, brackets.right),
           DOM.span({
             className: "arrayProperties",
             role: "group"}
           )
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -65,34 +65,31 @@ define(function (require, exports, modul
       for (let i = 0; i < array.length && i < max; i++) {
         try {
           let itemGrip = array[i];
           let value = provider ? provider.getValue(itemGrip) : itemGrip;
 
           delim = (i == delimMax ? "" : ", ");
 
           items.push(GripArrayItem(Object.assign({}, this.props, {
-            key: i,
             object: value,
-            delim: delim}
-          )));
+            delim: delim
+          })));
         } catch (exc) {
           items.push(GripArrayItem(Object.assign({}, this.props, {
             object: exc,
-            delim: delim,
-            key: i}
-          )));
+            delim: delim
+          })));
         }
       }
       if (array.length > max || grip.preview.length > array.length) {
         let objectLink = this.props.objectLink || span;
         let leftItemNum = grip.preview.length - max > 0 ?
           grip.preview.length - max : grip.preview.length - array.length;
         items.push(Caption({
-          key: "more",
           object: objectLink({
             object: this.props.object
           }, leftItemNum + " more…")
         }));
       }
 
       return items;
     },
@@ -105,17 +102,17 @@ define(function (require, exports, modul
       let brackets;
       let needSpace = function (space) {
         return space ? { left: "[ ", right: " ]"} : { left: "[", right: "]"};
       };
 
       if (mode == "tiny") {
         let objectLength = this.getLength(object);
         let isEmpty = objectLength === 0;
-        items = span({className: "length"}, isEmpty ? "" : objectLength);
+        items = [span({className: "length"}, isEmpty ? "" : objectLength)];
         brackets = needSpace(false);
       } else {
         let max = (mode == "short") ? 3 : 300;
         items = this.arrayIterator(object, max);
         brackets = needSpace(items.length > 0);
       }
 
       let objectLink = this.props.objectLink || span;
@@ -124,17 +121,17 @@ define(function (require, exports, modul
       return (
         span({
           className: "objectBox objectBox-array"},
           title,
           objectLink({
             className: "arrayLeftBracket",
             object: object
           }, brackets.left),
-          items,
+          ...items,
           objectLink({
             className: "arrayRightBracket",
             object: object
           }, brackets.right),
           span({
             className: "arrayProperties",
             role: "group"}
           )
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -71,17 +71,16 @@ define(function (require, exports, modul
       }
 
       let props = this.getProps(ownProperties, indexes);
       if (props.length < object.ownPropertyLength) {
         // There are some undisplayed props. Then display "more...".
         let objectLink = this.props.objectLink || span;
 
         props.push(Caption({
-          key: "more",
           object: objectLink({
             object: object
           }, ((object ? object.ownPropertyLength : 0) - max) + " more…")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         // NOTE: do not change comp._store.props directly to update a property,
         // it should be re-rendered or cloned with changed props
@@ -109,17 +108,16 @@ define(function (require, exports, modul
         return a - b;
       });
 
       indexes.forEach((i) => {
         let name = Object.keys(ownProperties)[i];
         let prop = ownProperties[name];
         let value = prop.value !== undefined ? prop.value : prop;
         props.push(PropRep(Object.assign({}, this.props, {
-          key: name,
           mode: "tiny",
           name: name,
           object: value,
           equal: ": ",
           delim: ", ",
           defaultRep: Grip
         })));
       });
@@ -185,17 +183,17 @@ define(function (require, exports, modul
 
       return (
         span({className: "objectBox objectBox-object"},
           this.getTitle(object),
           objectLink({
             className: "objectLeftBrace",
             object: object
           }, " { "),
-          props,
+          ...props,
           objectLink({
             className: "objectRightBrace",
             object: object
           }, " }")
         )
       );
     },
   });
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -70,17 +70,16 @@ define(function (require, exports, modul
         }));
       }
 
       if (props.length > max) {
         props.pop();
         let objectLink = this.props.objectLink || span;
 
         props.push(Caption({
-          key: "more",
           object: objectLink({
             object: object
           }, (Object.keys(object).length - max) + " more…")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         props[props.length - 1] = React.cloneElement(
           props[props.length - 1], { delim: "" });
@@ -111,17 +110,16 @@ define(function (require, exports, modul
             value = object[name];
           } catch (exc) {
             continue;
           }
 
           let t = typeof value;
           if (filter(t, value)) {
             props.push(PropRep({
-              key: name,
               mode: mode,
               name: name,
               object: value,
               equal: ": ",
               delim: ", ",
             }));
           }
         }
@@ -147,17 +145,17 @@ define(function (require, exports, modul
 
       return (
         span({className: "objectBox objectBox-object"},
           this.getTitle(object),
           objectLink({
             className: "objectLeftBrace",
             object: object
           }, " { "),
-          props,
+          ...props,
           objectLink({
             className: "objectRightBrace",
             object: object
           }, " }")
         )
       );
     },
   });
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip.html
@@ -26,16 +26,19 @@ window.onload = Task.async(function* () 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanMaxProps();
     yield testUninterestingProps();
 
     // Test that properties are rendered as expected by PropRep
     yield testNestedObject();
     yield testNestedArray();
+
+    // Test that 'more' property doesn't clobber the caption.
+    yield testMoreProp();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test object: `{}`
@@ -194,16 +197,45 @@ window.onload = Task.async(function* () 
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testMoreProp() {
+    // Test object: `{a: undefined, b: 1, more: 2, d: 3}`;
+    const testName = "testMoreProp";
+
+    const defaultOutput = `Object { b: 1, more: 2, d: 3, 1 more… }`;
+    const longOutput = `Object { a: undefined, b: 1, more: 2, d: 3 }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Object`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: longOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Object",
           "actor": "server1.conn0.obj304",
           "extensible": true,
@@ -400,15 +432,59 @@ window.onload = Task.async(function* () 
                 }
               }
             },
             "ownPropertiesLength": 1,
             "safeGetterValues": {}
           },
         };
 
+      case "testMoreProp":
+        return {
+          "type": "object",
+          "class": "Object",
+          "actor": "server1.conn0.obj342",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 4,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {
+              "a": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": {
+                  "type": "undefined"
+                }
+              },
+              "b": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": 1
+              },
+              "more": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": 2
+              },
+              "d": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": 3
+              }
+            },
+            "ownPropertiesLength": 4,
+            "safeGetterValues": {}
+          }
+        };
+
     }
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/test/mochitest/test_reps_object.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_object.html
@@ -25,16 +25,19 @@ window.onload = Task.async(function* () 
 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanMaxProps();
     yield testUninterestingProps();
 
     // Test that properties are rendered as expected by PropRep
     yield testNested();
+
+    // Test that 'more' property doesn't clobber the caption.
+    yield testMoreProp();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     const stub = {};
@@ -178,13 +181,43 @@ window.onload = Task.async(function* () 
       {
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, "testNestedObject", componentUnderTest, stub);
   }
-});
+
+  function testMoreProp() {
+    const stub = {
+      a: undefined,
+      b: 1,
+      'more': 2,
+      d: 3
+    };
+    const defaultOutput = `Object { b: 1, more: 2, d: 3, 1 more… }`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `Object`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, "testMoreProp", componentUnderTest, stub);
+  }});
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -361,21 +361,21 @@ checkbox:-moz-focusring {
 }
 
 /* Icon-only buttons */
 .devtools-button:empty::before,
 .devtools-toolbarbutton:not([label]):not([disabled]) > image {
   opacity: 0.8;
 }
 
-.devtools-button:hover:empty::before,
+.devtools-button:hover:empty:not(:disabled):before,
 .devtools-button.checked:empty::before,
 .devtools-button[checked]:empty::before,
 .devtools-button[open]:empty::before,
-.devtools-toolbarbutton:not([label]):hover > image,
+.devtools-toolbarbutton:not([label]):not([disabled=true]):hover > image,
 .devtools-toolbarbutton:not([label])[checked=true] > image,
 .devtools-toolbarbutton:not([label])[open=true] > image {
   opacity: 1;
 }
 
 .devtools-button:disabled,
 .devtools-button[disabled],
 .devtools-toolbarbutton[disabled] {
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -1181,59 +1181,59 @@ JSTerm.prototype = {
 
       case KeyCodes.DOM_VK_PAGE_UP:
         if (this.autocompletePopup.isOpen) {
           inputUpdated = this.complete(this.COMPLETE_PAGEUP);
           if (inputUpdated) {
             this._autocompletePopupNavigated = true;
           }
         } else {
-          this.hud.outputWrapper.scrollTop =
+          this.hud.outputScroller.scrollTop =
             Math.max(0,
-              this.hud.outputWrapper.scrollTop -
-              this.hud.outputWrapper.clientHeight
+              this.hud.outputScroller.scrollTop -
+              this.hud.outputScroller.clientHeight
             );
         }
         event.preventDefault();
         break;
 
       case KeyCodes.DOM_VK_PAGE_DOWN:
         if (this.autocompletePopup.isOpen) {
           inputUpdated = this.complete(this.COMPLETE_PAGEDOWN);
           if (inputUpdated) {
             this._autocompletePopupNavigated = true;
           }
         } else {
-          this.hud.outputWrapper.scrollTop =
-            Math.min(this.hud.outputWrapper.scrollHeight,
-              this.hud.outputWrapper.scrollTop +
-              this.hud.outputWrapper.clientHeight
+          this.hud.outputScroller.scrollTop =
+            Math.min(this.hud.outputScroller.scrollHeight,
+              this.hud.outputScroller.scrollTop +
+              this.hud.outputScroller.clientHeight
             );
         }
         event.preventDefault();
         break;
 
       case KeyCodes.DOM_VK_HOME:
         if (this.autocompletePopup.isOpen) {
           this.autocompletePopup.selectedIndex = 0;
           event.preventDefault();
         } else if (inputValue.length <= 0) {
-          this.hud.outputWrapper.scrollTop = 0;
+          this.hud.outputScroller.scrollTop = 0;
           event.preventDefault();
         }
         break;
 
       case KeyCodes.DOM_VK_END:
         if (this.autocompletePopup.isOpen) {
           this.autocompletePopup.selectedIndex =
             this.autocompletePopup.itemCount - 1;
           event.preventDefault();
         } else if (inputValue.length <= 0) {
-          this.hud.outputWrapper.scrollTop =
-            this.hud.outputWrapper.scrollHeight;
+          this.hud.outputScroller.scrollTop =
+            this.hud.outputScroller.scrollHeight;
           event.preventDefault();
         }
         break;
 
       case KeyCodes.DOM_VK_LEFT:
         if (this.autocompletePopup.isOpen || this.lastCompletion.value) {
           this.clearCompletion();
         }
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -26,17 +26,17 @@ const ConsoleOutput = createClass({
     serviceContainer: PropTypes.shape({
       attachRefToHud: PropTypes.func.isRequired,
     }),
     autoscroll: PropTypes.bool.isRequired,
   },
 
   componentDidMount() {
     scrollToBottom(this.outputNode);
-    this.props.serviceContainer.attachRefToHud("outputWrapper", this.outputNode);
+    this.props.serviceContainer.attachRefToHud("outputScroller", this.outputNode);
   },
 
   componentWillUpdate(nextProps, nextState) {
     if (!this.outputNode) {
       return;
     }
 
     const outputNode = this.outputNode;
--- a/devtools/client/webconsole/new-console-output/main.js
+++ b/devtools/client/webconsole/new-console-output/main.js
@@ -14,11 +14,10 @@ const { BrowserLoader } = Cu.import("res
 // Initialize module loader and load all modules of the new inline
 // preview feature. The entire code-base doesn't need any extra
 // privileges and runs entirely in content scope.
 const NewConsoleOutputWrapper = BrowserLoader({
   baseURI: "resource://devtools/client/webconsole/new-console-output/",
   window}).require("./new-console-output-wrapper");
 
 this.NewConsoleOutput = function (parentNode, jsterm, toolbox, owner, serviceContainer) {
-  console.log("Creating NewConsoleOutput", parentNode, NewConsoleOutputWrapper);
   return new NewConsoleOutputWrapper(parentNode, jsterm, toolbox, owner, serviceContainer);
 };
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -15,43 +15,43 @@ const TEST_URI =
     }
   </script>
   `;
 
 add_task(function* () {
   let hud = yield openNewTabAndConsole(TEST_URI);
   info("Web Console opened");
 
-  const outputWrapper = hud.ui.outputWrapper;
+  const outputScroller = hud.ui.outputScroller;
 
   yield waitFor(() => findMessages(hud, "").length == 100);
 
-  let currentPosition = outputWrapper.scrollTop;
+  let currentPosition = outputScroller.scrollTop;
   const bottom = currentPosition;
 
   EventUtils.sendMouseEvent({type: "click"}, hud.jsterm.inputNode);
 
   // Page up.
   EventUtils.synthesizeKey("VK_PAGE_UP", {});
-  isnot(outputWrapper.scrollTop, currentPosition,
+  isnot(outputScroller.scrollTop, currentPosition,
     "scroll position changed after page up");
 
   // Page down.
-  currentPosition = outputWrapper.scrollTop;
+  currentPosition = outputScroller.scrollTop;
   EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
-  ok(outputWrapper.scrollTop > currentPosition,
+  ok(outputScroller.scrollTop > currentPosition,
      "scroll position now at bottom");
 
   // Home
   EventUtils.synthesizeKey("VK_HOME", {});
-  is(outputWrapper.scrollTop, 0, "scroll position now at top");
+  is(outputScroller.scrollTop, 0, "scroll position now at top");
 
   // End
   EventUtils.synthesizeKey("VK_END", {});
-  let scrollTop = outputWrapper.scrollTop;
+  let scrollTop = outputScroller.scrollTop;
   ok(scrollTop > 0 && Math.abs(scrollTop - bottom) <= 5,
      "scroll position now at bottom");
 
   // Clear output
   info("try ctrl-l to clear output");
   let clearShortcut;
   if (Services.appinfo.OS === "Darwin") {
     clearShortcut = WCUL10n.getStr("webconsole.clear.keyOSX");
@@ -61,11 +61,11 @@ add_task(function* () {
   synthesizeKeyShortcut(clearShortcut);
   yield waitFor(() => findMessages(hud, "").length == 0);
   is(hud.jsterm.inputNode.getAttribute("focused"), "true", "jsterm input is focused");
 
   // Focus filter
   info("try ctrl-f to focus filter");
   synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
   ok(!hud.jsterm.inputNode.getAttribute("focused"), "jsterm input is not focused");
-  is(hud.ui.filterBox, outputWrapper.ownerDocument.activeElement,
+  is(hud.ui.filterBox, outputScroller.ownerDocument.activeElement,
     "filter input is focused");
 });
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -537,47 +537,29 @@ WebConsoleFrame.prototype = {
    */
   _initUI: function () {
     this.document = this.window.document;
     this.rootElement = this.document.documentElement;
     this.NEW_CONSOLE_OUTPUT_ENABLED = !this.isBrowserConsole
       && !this.owner.target.chrome
       && Services.prefs.getBoolPref(PREF_NEW_FRONTEND_ENABLED);
 
-    this._initDefaultFilterPrefs();
-
-    // Register the controller to handle "select all" properly.
-    this._commandController = new CommandController(this);
-    this.window.controllers.insertControllerAt(0, this._commandController);
-
-    this._contextMenuHandler = new ConsoleContextMenu(this);
-
-    let doc = this.document;
-
-    this.filterBox = doc.querySelector(".hud-filter-box");
-    this.outputNode = doc.getElementById("output-container");
-    this.outputWrapper = doc.getElementById("output-wrapper");
-
-    this.completeNode = doc.querySelector(".jsterm-complete-node");
-    this.inputNode = doc.querySelector(".jsterm-input-node");
-
-    this._setFilterTextBoxEvents();
-    this._initFilterButtons();
+    this.outputNode = this.document.getElementById("output-container");
+    this.outputWrapper = this.document.getElementById("output-wrapper");
+    this.completeNode = this.document.querySelector(".jsterm-complete-node");
+    this.inputNode = this.document.querySelector(".jsterm-input-node");
+
+    // In the old frontend, the area that scrolls is outputWrapper, but in the new
+    // frontend this will be reassigned.
+    this.outputScroller = this.outputWrapper;
 
     // Update the character width and height needed for the popup offset
     // calculations.
     this._updateCharSize();
 
-    let clearButton =
-      doc.getElementsByClassName("webconsole-clear-console-button")[0];
-    clearButton.addEventListener("command", () => {
-      this.owner._onClearButton();
-      this.jsterm.clearOutput(true);
-    });
-
     this.jsterm = new JSTerm(this);
     this.jsterm.init();
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
 
     if (this.NEW_CONSOLE_OUTPUT_ENABLED) {
       // @TODO Remove this once JSTerm is handled with React/Redux.
       this.window.jsterm = this.jsterm;
@@ -591,20 +573,37 @@ WebConsoleFrame.prototype = {
       this.experimentalOutputNode.removeAttribute("tabindex");
       this.outputNode.hidden = true;
       this.outputNode.parentNode.appendChild(this.experimentalOutputNode);
       // @TODO Once the toolbox has been converted to React, see if passing
       // in JSTerm is still necessary.
 
       this.newConsoleOutput = new this.window.NewConsoleOutput(
         this.experimentalOutputNode, this.jsterm, toolbox, this.owner);
-      console.log("Created newConsoleOutput", this.newConsoleOutput);
-
-      let filterToolbar = doc.querySelector(".hud-console-filter-toolbar");
+
+      let filterToolbar = this.document.querySelector(".hud-console-filter-toolbar");
       filterToolbar.hidden = true;
+    } else {
+      // Register the controller to handle "select all" properly.
+      this._commandController = new CommandController(this);
+      this.window.controllers.insertControllerAt(0, this._commandController);
+
+      this._contextMenuHandler = new ConsoleContextMenu(this);
+
+      this._initDefaultFilterPrefs();
+      this.filterBox = this.document.querySelector(".hud-filter-box");
+      this._setFilterTextBoxEvents();
+      this._initFilterButtons();
+      let clearButton =
+        this.document.getElementsByClassName("webconsole-clear-console-button")[0];
+      clearButton.addEventListener("command", () => {
+        this.owner._onClearButton();
+        this.jsterm.clearOutput(true);
+      });
+
     }
 
     this.resize();
     this.window.addEventListener("resize", this.resize, true);
     this.jsterm.on("sidebar-opened", this.resize);
     this.jsterm.on("sidebar-closed", this.resize);
 
     if (toolbox) {
--- 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/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/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/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/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/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52343
+52346
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -18231,16 +18231,18 @@ cannonball/SM
 cannot
 canny/UTR
 canoe/MDS
 canoeing
 canoeist/SM
 canola/M
 canon/MS
 canonical/Y
+canonicalization/MS
+canonicalize/DGS
 canonization/SM
 canonize/DSG
 canoodle/DSG
 canopy/GDSM
 canst
 cant's
 cant/CZRDGS
 cantabile
@@ -37479,16 +37481,17 @@ outhouse/SM
 outing/M
 outlaid
 outlandish/PY
 outlandishness/M
 outlast/DSG
 outlaw/SGMD
 outlay/SGM
 outlet/SM
+outlier/SM
 outline/MGDS
 outlive/GDS
 outlook/MS
 outlying
 outmaneuver/GDS
 outmatch/GDS
 outmoded
 outnumber/DSG
--- 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"])
--- 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/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/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
--- 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;
@@ -3137,16 +3133,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.
@@ -3943,57 +3955,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/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,
+                   modifiers:{ctrlKey:1}, chars:"\u001C"},
+                  "\\", "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:{ctrlKey:1, 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,
+                   modifiers:{altKey: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,
+                   modifiers:{altKey:1, 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_COMMA,
                    modifiers:{}, chars:","},
                   ",", "Comma", nsIDOMKeyEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
                    modifiers:{shiftKey:1}, chars:"<"},
                   "<", "Comma", nsIDOMKeyEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  ",", "Comma", nsIDOMKeyEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "<", "Comma", nsIDOMKeyEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+                   modifiers:{altKey:1}, chars:","},
+                  ",", "Comma", nsIDOMKeyEvent.DOM_VK_COMMA, ",", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_COMMA,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"<"},
+                  "<", "Comma", nsIDOMKeyEvent.DOM_VK_COMMA, "<", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
                    modifiers:{}, chars:"."},
                   ".", "Period", nsIDOMKeyEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
                    modifiers:{shiftKey:1}, chars:">"},
                   ">", "Period", nsIDOMKeyEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  ".", "Period", nsIDOMKeyEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  ">", "Period", nsIDOMKeyEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+                   modifiers:{altKey:1}, chars:"."},
+                  ".", "Period", nsIDOMKeyEvent.DOM_VK_PERIOD, ".", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_PERIOD,
+                   modifiers:{altKey:1, shiftKey:1}, chars:">"},
+                  ">", "Period", nsIDOMKeyEvent.DOM_VK_PERIOD, ">", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
                    modifiers:{}, chars:"/"},
                   "/", "Slash", nsIDOMKeyEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
                    modifiers:{shiftKey:1}, chars:"?"},
                   "?", "Slash", nsIDOMKeyEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "/", "Slash", nsIDOMKeyEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "?", "Slash", nsIDOMKeyEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+                   modifiers:{altKey:1}, chars:"/"},
+                  "/", "Slash", nsIDOMKeyEvent.DOM_VK_SLASH, "/", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_2,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"?"},
+                  "?", "Slash", nsIDOMKeyEvent.DOM_VK_SLASH, "?", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
                    modifiers:{}, chars:"`"},
                   "`", "Backquote", nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
                    modifiers:{shiftKey:1}, chars:"~"},
                   "~", "Backquote", nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+                   modifiers:{ctrlKey:1}, chars:""},
+                  "`", "Backquote", nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+                   modifiers:{ctrlKey:1, shiftKey:1}, chars:""},
+                  "~", "Backquote", nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+                   modifiers:{altKey:1}, chars:"`"},
+                  "`", "Backquote", nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "`", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+    yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_OEM_3,
+                   modifiers:{altKey:1, shiftKey:1}, chars:"~"},
+                  "~", "Backquote", nsIDOMKeyEvent.DOM_VK_BACK_QUOTE, "~", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
     // Numpad
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD0,
                    modifiers:{numLockKey:1}, chars:"0"},
                   "0", "Numpad0", nsIDOMKeyEvent.DOM_VK_NUMPAD0, "0", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
     yield testKey({layout:KEYBOARD_LAYOUT_EN_US, keyCode:WIN_VK_NUMPAD1,
                    modifiers:{numLockKey:1}, chars:"1"},
                   "1", "Numpad1", nsIDOMKeyEvent.DOM_VK_NUMPAD1, "1", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_NUMPAD);
@@ -2791,17 +3331,17 @@ function* runKeyEventTests()
                   "!", "Slash", nsIDOMKeyEvent.DOM_VK_EXCLAMATION, "!", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
 
     // AltGr
     yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_0,
                    modifiers:{altGrKey:1}, chars:"@"},
                   "@", "Digit0", nsIDOMKeyEvent.DOM_VK_0, "@", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_1,
                    modifiers:{altGrKey:1}, chars:""},
-                  "&", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL_BUT_NOT_CAUSE_INPUT, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
+                  "&", "Digit1", nsIDOMKeyEvent.DOM_VK_1, "&", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     //yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_2,
     //               modifiers:{altGrKey:1}, chars:""},
     //              "Dead", "Digit2", nsIDOMKeyEvent.DOM_VK_2, "2", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD); // Dead-key
     yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_3,
                    modifiers:{altGrKey:1}, chars:"#"},
                   "#", "Digit3", nsIDOMKeyEvent.DOM_VK_3, "#", SHOULD_DELIVER_ALL, KeyboardEvent.DOM_KEY_LOCATION_STANDARD);
     yield testKey({layout:KEYBOARD_LAYOUT_FRENCH, keyCode:WIN_VK_4,
                    modifiers:{altGrKey:1}, chars:"{"},
--- a/widget/windows/KeyboardLayout.cpp
+++ b/widget/windows/KeyboardLayout.cpp
@@ -1454,19 +1454,19 @@ NativeKey::InitWithKeyChar()
     mVirtualKeyCode = mOriginalVirtualKeyCode;
   }
 
   KeyboardLayout* keyboardLayout = KeyboardLayout::GetInstance();
   mDOMKeyCode =
     keyboardLayout->ConvertNativeKeyCodeToDOMKeyCode(mOriginalVirtualKeyCode);
   // Be aware, keyboard utilities can change non-printable keys to printable
   // keys.  In such case, we should make the key value as a printable key.
-  // FYI: IsFollowedByNonControlCharMessage() returns true only when it's
+  // FYI: IsFollowedByPrintableCharMessage() returns true only when it's
   //      handling a keydown message.
-  mKeyNameIndex = IsFollowedByNonControlCharMessage() ?
+  mKeyNameIndex = IsFollowedByPrintableCharMessage() ?
     KEY_NAME_INDEX_USE_STRING :
     keyboardLayout->ConvertNativeKeyCodeToKeyNameIndex(mOriginalVirtualKeyCode);
   mCodeNameIndex =
     KeyboardLayout::ConvertScanCodeToCodeNameIndex(
       GetScanCodeWithExtendedFlag());
 
   // If next message of WM_(SYS)KEYDOWN is WM_*CHAR message and the key
   // combination is not reserved by the system, let's consume it now.
@@ -1514,23 +1514,25 @@ NativeKey::InitWithKeyChar()
       //       handling WM_KEYDOWN.
       // TODO: Like Edge, we shouldn't dispatch two sets of keyboard events
       //       for a Unicode character in non-BMP because its key value looks
       //       broken and not good thing for our editor if only one keydown or
       //       keypress event's default is prevented.  I guess, we should store
       //       key message information globally and we should wait following
       //       WM_KEYDOWN if following WM_CHAR is a part of a Unicode character.
       mCommittedCharsAndModifiers.Clear();
+      Modifiers modifiers =
+        mModKeyState.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
       for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
-        char16_t ch = static_cast<char16_t>(mFollowingCharMsgs[i].wParam);
-        // Skip control characters.
-        if (IsControlChar(ch)) {
+        // Ignore non-printable char messages.
+        if (!IsPrintableCharMessage(mFollowingCharMsgs[i])) {
           continue;
         }
-        mCommittedCharsAndModifiers.Append(ch, mModKeyState.GetModifiers());
+        char16_t ch = static_cast<char16_t>(mFollowingCharMsgs[i].wParam);
+        mCommittedCharsAndModifiers.Append(ch, modifiers);
       }
     }
     // Remove odd char messages if there are.
     RemoveFollowingOddCharMessages();
   }
 }
 
 NativeKey::~NativeKey()
@@ -1654,23 +1656,24 @@ NativeKey::IsFollowedByDeadCharMessage()
 {
   if (mFollowingCharMsgs.IsEmpty()) {
     return false;
   }
   return IsDeadCharMessage(mFollowingCharMsgs[0]);
 }
 
 bool
-NativeKey::IsFollowedByNonControlCharMessage() const
+NativeKey::IsFollowedByPrintableCharMessage() const
 {
-  if (mFollowingCharMsgs.IsEmpty()) {
-    return false;
+  for (size_t i = 0; i < mFollowingCharMsgs.Length(); ++i) {
+    if (IsPrintableCharMessage(mFollowingCharMsgs[i])) {
+      return true;
+    }
   }
-  return mFollowingCharMsgs[0].message == WM_CHAR &&
-         !IsControlChar(static_cast<char16_t>(mFollowingCharMsgs[0].wParam));
+  return false;
 }
 
 bool
 NativeKey::IsReservedBySystem() const
 {
   // Alt+Space key is handled by OS, we shouldn't touch it.
   if (mModKeyState.IsAlt() && !mModKeyState.IsControl() &&
       mVirtualKeyCode == VK_SPACE) {
@@ -2484,27 +2487,34 @@ NativeKey::HandleKeyDownMessage(bool* aE
 
   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
     ("%p   NativeKey::HandleKeyDownMessage(), tries to be dispatching "
      "keypress events due to no following char messages...", this));
   return DispatchKeyPressEventsWithoutCharMessage();
 }
 
 bool
+NativeKey::HandleCharMessage(bool* aEventDispatched) const
+{
+  MOZ_ASSERT(IsCharOrSysCharMessage(mMsg));
+  return HandleCharMessage(mMsg, aEventDispatched);
+}
+
+bool
 NativeKey::HandleCharMessage(const MSG& aCharMsg,
                              bool* aEventDispatched) const
 {
-  MOZ_ASSERT(IsKeyDownMessage() || IsPrintableCharMessage(mMsg));
-  MOZ_ASSERT(IsPrintableCharMessage(aCharMsg.message));
+  MOZ_ASSERT(IsKeyDownMessage() || IsCharOrSysCharMessage(mMsg));
+  MOZ_ASSERT(IsCharOrSysCharMessage(aCharMsg.message));
 
   if (aEventDispatched) {
     *aEventDispatched = false;
   }
 
-  if (IsPrintableCharMessage(mMsg) && IsAnotherInstanceRemovingCharMessage()) {
+  if (IsCharOrSysCharMessage(mMsg) && IsAnotherInstanceRemovingCharMessage()) {
     MOZ_LOG(sNativeKeyLogger, LogLevel::Warning,
       ("%p   NativeKey::HandleCharMessage(), WARNING, does nothing because "
        "the message should be handled in another instance removing this "
        "message", this));
     // Consume this for now because it will be handled by another instance.
     return true;
   }
 
@@ -2512,197 +2522,87 @@ NativeKey::HandleCharMessage(const MSG& 
   // eKeyPress event for it and passes the message to next wndproc.
   if (IsReservedBySystem()) {
     MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
       ("%p   NativeKey::HandleCharMessage(), doesn't dispatch keypress "
        "event because the key combination is reserved by the system", this));
     return false;
   }
 
-  // Bug 818235: Ignore Ctrl+Enter.
-  if (!mModKeyState.IsAlt() && mModKeyState.IsControl() &&
-      mVirtualKeyCode == VK_RETURN) {
+  // When a control key is inputted by a key, it should be handled without
+  // WM_*CHAR messages at receiving WM_*KEYDOWN message.  So, when we receive
+  // WM_*CHAR message directly, we see a control character here.
+  if (IsControlCharMessage(aCharMsg)) {
+    // In this case, we don't need to dispatch eKeyPress event because:
+    // 1. We're the only browser which dispatches "keypress" event for
+    //    non-printable characters (Although, both Chrome and Edge dispatch
+    //    "keypress" event for some keys accidentally.  For example, "IntlRo"
+    //    key with Ctrl of Japanese keyboard layout).
+    // 2. Currently, we may handle shortcut keys with "keydown" event if
+    //    it's reserved or something.  So, we shouldn't dispatch "keypress"
+    //    event without it.
+    // Note that this does NOT mean we stop dispatching eKeyPress event for
+    // key presses causes a control character when Ctrl is pressed.  In such
+    // case, DispatchKeyPressEventsWithoutCharMessage() dispatches eKeyPress
+    // instead of this method.
     MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
       ("%p   NativeKey::HandleCharMessage(), doesn't dispatch keypress "
-       "event due to Ctrl+Enter", this));
+       "event because received a control character input without WM_KEYDOWN",
+       this));
     return false;
   }
 
-  // XXXmnakao I think that if aNativeKeyDown is null, such lonely WM_CHAR
-  //           should cause composition events because they are not caused
-  //           by actual keyboard operation.
-
-  static const char16_t U_EQUAL = 0x3D;
+  // XXXmnakano I think that if mMsg is WM_CHAR, i.e., it comes without
+  //            preceding WM_KEYDOWN, we should should dispatch composition
+  //            events instead of eKeyPress because they are not caused by
+  //            actual keyboard operation.
 
   // First, handle normal text input or non-printable key case here.
-  if ((!mModKeyState.IsAlt() && !mModKeyState.IsControl()) ||
-      mModKeyState.IsAltGr() ||
-      (mOriginalVirtualKeyCode &&
-       !KeyboardLayout::IsPrintableCharKey(mOriginalVirtualKeyCode))) {
-    WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
-    if (!IsControlChar(static_cast<char16_t>(aCharMsg.wParam))) {
-      keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam);
-    } else {
-      keypressEvent.mKeyCode = mDOMKeyCode;
-    }
-    nsresult rv = mDispatcher->BeginNativeInputTransaction();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
-        ("%p   NativeKey::HandleCharMessage(), FAILED due to "
-         "BeginNativeInputTransaction() failure", this));
-      return true;
-    }
-
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
-      ("%p   NativeKey::HandleCharMessage(), initializing keypress "
-       "event...", this));
-
-    // When AltGr (Alt+Ctrl) is pressed, that causes normal text input.
-    // At this time, if either alt or ctrl flag is set, EditorBase ignores the
-    // keypress event.  For avoiding this issue, we should remove ctrl and alt
-    // flags.
-    ModifierKeyState modKeyState(mModKeyState);
-    modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
-    nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState, &aCharMsg);
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-      ("%p   NativeKey::HandleCharMessage(), dispatching keypress event...",
-       this));
-    bool dispatched =
-      mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
-                                               const_cast<NativeKey*>(this));
-    if (aEventDispatched) {
-      *aEventDispatched = dispatched;
-    }
-    if (mWidget->Destroyed()) {
-      MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-        ("%p   NativeKey::HandleCharMessage(), keypress event caused "
-         "destroying the widget", this));
-      return true;
-    }
-    bool consumed = status == nsEventStatus_eConsumeNoDefault;
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-      ("%p   NativeKey::HandleCharMessage(), dispatched keypress event, "
-       "dispatched=%s, consumed=%s",
-       this, GetBoolName(dispatched), GetBoolName(consumed)));
-    return consumed;
-  }
-
-  // XXX It seems that following code was implemented for shortcut key
-  //     handling.  However, it's now handled in WM_KEYDOWN message handler.
-  //     So, this actually runs only when WM_CHAR is sent/posted without
-  //     WM_KEYDOWN.  I think that we don't need to keypress event in such
-  //     case especially for shortcut keys.
-
-  char16_t uniChar;
-  // Ctrl+A Ctrl+Z, see Programming Windows 3.1 page 110 for details
-  if (mModKeyState.IsControl() &&
-      IsControlChar(static_cast<char16_t>(aCharMsg.wParam))) {
-    // Bug 16486: Need to account for shift here.
-    uniChar = aCharMsg.wParam - 1 + (mModKeyState.IsShift() ? 'A' : 'a');
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-      ("%p   NativeKey::HandleCharMessage(), computing charCode for ASCII "
-       "control characters which are inputted with Ctrl key, uniChar=%s",
-       this, GetCharacterCodeName(uniChar).get()));
-  } else if (mModKeyState.IsControl() && aCharMsg.wParam <= 0x1F) {
-    // XXX Looks like that this block won't run since the condition is
-    //     included in the first |if|'s condition.
-    // Bug 50255: <ctrl><[> and <ctrl><]> are not being processed.
-    // also fixes ctrl+\ (x1c), ctrl+^ (x1e) and ctrl+_ (x1f)
-    // for some reason the keypress handler need to have the uniChar code set
-    // with the addition of a upper case A not the lower case.
-    uniChar = aCharMsg.wParam - 1 + 'A';
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-      ("%p   NativeKey::HandleCharMessage(), computing charCode for ASCII "
-       "control characters which are inputted with Ctrl key, uniChar=%s",
-       this, GetCharacterCodeName(uniChar).get()));
-  } else if (IsControlChar(static_cast<char16_t>(aCharMsg.wParam)) ||
-             (aCharMsg.wParam == U_EQUAL && mModKeyState.IsControl())) {
-    uniChar = 0;
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-      ("%p   NativeKey::HandleCharMessage(), setting charCode to 0 because "
-       "the character is a control character without Ctrl key or Ctrl+=",
-       this));
-  } else {
-    uniChar = aCharMsg.wParam;
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-      ("%p   NativeKey::HandleCharMessage(), deciding to use given charCode, "
-       "uniChar=%s",
-       this, GetCharacterCodeName(uniChar).get()));
-  }
-
-  // Bug 50255 and Bug 351310: Keep the characters unshifted for shortcuts and
-  // accesskeys and make sure that numbers are always passed as such.
-  if (uniChar && (mModKeyState.IsControl() || mModKeyState.IsAlt())) {
-    char16_t unshiftedCharCode =
-      (mVirtualKeyCode >= '0' && mVirtualKeyCode <= '9') ?
-        mVirtualKeyCode :  mModKeyState.IsShift() ?
-                             ComputeUnicharFromScanCode() : 0;
-    // Ignore diacritics (top bit set) and key mapping errors (char code 0)
-    if (uniChar != unshiftedCharCode &&
-        static_cast<int32_t>(unshiftedCharCode) > 0) {
-      uniChar = unshiftedCharCode;
-      MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-        ("%p   NativeKey::HandleCharMessage(), adjusting computed charCode "
-         "because unshifted charCode is better, uniChar=%s, mModKeyState=%s",
-         this, GetCharacterCodeName(uniChar).get(),
-         ToString(mModKeyState).get()));
-    }
-  }
-
-  // Bug 285161 and Bug 295095: They were caused by the initial fix for
-  // bug 178110.  When pressing (alt|ctrl)+char, the char must be lowercase
-  // unless shift is pressed too.
-  if (!mModKeyState.IsShift() &&
-      (mModKeyState.IsAlt() || mModKeyState.IsControl()) &&
-      uniChar != towlower(uniChar)) {
-    uniChar = towlower(uniChar);
-    MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-      ("%p   NativeKey::HandleCharMessage(), making computed charCode "
-       "lower case character because Shift isn't pressed but Ctrl or Alt is "
-       "pressed, uniChar=%s, mModKeyState=%s",
-       this, GetCharacterCodeName(uniChar).get(),
-       ToString(mModKeyState).get()));
-  }
-
+  WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
+  keypressEvent.mCharCode = static_cast<uint32_t>(aCharMsg.wParam);
   nsresult rv = mDispatcher->BeginNativeInputTransaction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     MOZ_LOG(sNativeKeyLogger, LogLevel::Error,
       ("%p   NativeKey::HandleCharMessage(), FAILED due to "
        "BeginNativeInputTransaction() failure", this));
     return true;
   }
 
   MOZ_LOG(sNativeKeyLogger, LogLevel::Debug,
     ("%p   NativeKey::HandleCharMessage(), initializing keypress "
-     "event after some hacks...", this));
-  WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
-  keypressEvent.mCharCode = uniChar;
-  if (!keypressEvent.mCharCode) {
-    keypressEvent.mKeyCode = mDOMKeyCode;
+     "event...", this));
+
+  ModifierKeyState modKeyState(mModKeyState);
+  // When AltGr is pressed, both Alt and Ctrl are active.  However, when they
+  // are active, EditorBase won't treat the keypress event as inputting a
+  // character.  Therefore, when AltGr is pressed and the key tries to input
+  // a character, let's set them to false.
+  if (modKeyState.IsAltGr() && IsPrintableCharMessage(aCharMsg)) {
+    modKeyState.Unset(MODIFIER_ALT | MODIFIER_CONTROL);
   }
-  nsEventStatus status = InitKeyEvent(keypressEvent, mModKeyState, &aCharMsg);
+  nsEventStatus status = InitKeyEvent(keypressEvent, modKeyState, &aCharMsg);
   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-    ("%p   NativeKey::HandleCharMessage(), dispatching keypress event with "
-     "some hacks...", this));
+    ("%p   NativeKey::HandleCharMessage(), dispatching keypress event...",
+     this));
   bool dispatched =
     mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
                                              const_cast<NativeKey*>(this));
   if (aEventDispatched) {
     *aEventDispatched = dispatched;
   }
   if (mWidget->Destroyed()) {
     MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
       ("%p   NativeKey::HandleCharMessage(), keypress event caused "
        "destroying the widget", this));
     return true;
   }
   bool consumed = status == nsEventStatus_eConsumeNoDefault;
   MOZ_LOG(sNativeKeyLogger, LogLevel::Info,
-    ("%p   NativeKey::HandleCharMessage(), dispatched keypress event with "
-     "some hacks, dispatched=%s, consumed=%s",
+    ("%p   NativeKey::HandleCharMessage(), dispatched keypress event, "
+     "dispatched=%s, consumed=%s",
      this, GetBoolName(dispatched), GetBoolName(consumed)));
   return consumed;
 }
 
 bool
 NativeKey::HandleKeyUpMessage(bool* aEventDispatched) const
 {
   MOZ_ASSERT(IsKeyUpMessage());
@@ -2776,32 +2676,33 @@ NativeKey::NeedsToHandleWithoutFollowing
   }
 
   // If the keydown message is generated for inputting some Unicode characters
   // via SendInput() API, we need to handle it only with WM_*CHAR messages.
   if (mVirtualKeyCode == VK_PACKET) {
     return false;
   }
 
-  // Enter and backspace are always handled here to avoid for example the
-  // confusion between ctrl-enter and ctrl-J.
-  if (mDOMKeyCode == NS_VK_RETURN || mDOMKeyCode == NS_VK_BACK) {
+  // If following char message is for a control character, it should be handled
+  // without WM_CHAR message.  This is typically Ctrl + [a-z].
+  if (mFollowingCharMsgs.Length() == 1 &&
+      IsControlCharMessage(mFollowingCharMsgs[0])) {
     return true;
   }
 
   // If inputting two or more characters, should be dispatched after removing
   // whole following char messages.
   if (mCommittedCharsAndModifiers.mLength > 1) {
     return true;
   }
 
   // If keydown message is followed by WM_CHAR whose wParam isn't a control
   // character, we should dispatch keypress event with the char message
   // even with any modifier state.
-  if (IsFollowedByNonControlCharMessage()) {
+  if (IsFollowedByPrintableCharMessage()) {
     return false;
   }
 
   // If any modifier keys which may cause printable keys becoming non-printable
   // are not pressed, we don't need special handling for the key.
   if (!mModKeyState.IsControl() && !mModKeyState.IsAlt() &&
       !mModKeyState.IsWin()) {
     return false;
@@ -3652,18 +3553,19 @@ KeyboardLayout::InitNativeKey(NativeKey&
   // should be discarded because mKeyValue should have the string to be
   // inputted.
   if (aNativeKey.mMsg.message == WM_CHAR) {
     char16_t ch = static_cast<char16_t>(aNativeKey.mMsg.wParam);
     // But don't set key value as printable key if the character is a control
     // character such as 0x0D at pressing Enter key.
     if (!NativeKey::IsControlChar(ch)) {
       aNativeKey.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
-      aNativeKey.mCommittedCharsAndModifiers.
-        Append(ch, aModKeyState.GetModifiers());
+      Modifiers modifiers =
+        aModKeyState.GetModifiers() & ~(MODIFIER_ALT | MODIFIER_CONTROL);
+      aNativeKey.mCommittedCharsAndModifiers.Append(ch, modifiers);
       return;
     }
   }
 
   // If the key is not a usual printable key, KeyboardLayout class assume that
   // it's not cause dead char nor printable char.  Therefore, there are nothing
   // to do here fore such keys (e.g., function keys).
   // However, this should keep dead key state even if non-printable key is
--- a/widget/windows/KeyboardLayout.h
+++ b/widget/windows/KeyboardLayout.h
@@ -242,21 +242,20 @@ public:
    * initialized with WM_KEYDOWN or WM_SYSKEYDOWN.
    * Returns true if dispatched keydown event or keypress event is consumed.
    * Otherwise, false.
    */
   bool HandleKeyDownMessage(bool* aEventDispatched = nullptr) const;
 
   /**
    * Handles WM_CHAR message or WM_SYSCHAR message.  The instance must be
-   * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them.
+   * initialized with them.
    * Returns true if dispatched keypress event is consumed.  Otherwise, false.
    */
-  bool HandleCharMessage(const MSG& aCharMsg,
-                         bool* aEventDispatched = nullptr) const;
+  bool HandleCharMessage(bool* aEventDispatched = nullptr) const;
 
   /**
    * Handles keyup message.  Returns true if the event is consumed.
    * Otherwise, false.
    */
   bool HandleKeyUpMessage(bool* aEventDispatched = nullptr) const;
 
   /**
@@ -428,31 +427,31 @@ private:
             mMsg.message == MOZ_WM_KEYDOWN);
   }
   bool IsKeyUpMessage() const
   {
     return (mMsg.message == WM_KEYUP ||
             mMsg.message == WM_SYSKEYUP ||
             mMsg.message == MOZ_WM_KEYUP);
   }
-  bool IsPrintableCharMessage(const MSG& aMSG) const
+  bool IsCharOrSysCharMessage(const MSG& aMSG) const
   {
-    return IsPrintableCharMessage(aMSG.message);
+    return IsCharOrSysCharMessage(aMSG.message);
   }
-  bool IsPrintableCharMessage(UINT aMessage) const
+  bool IsCharOrSysCharMessage(UINT aMessage) const
   {
     return (aMessage == WM_CHAR || aMessage == WM_SYSCHAR);
   }
   bool IsCharMessage(const MSG& aMSG) const
   {
     return IsCharMessage(aMSG.message);
   }
   bool IsCharMessage(UINT aMessage) const
   {
-    return (IsPrintableCharMessage(aMessage) || IsDeadCharMessage(aMessage));
+    return (IsCharOrSysCharMessage(aMessage) || IsDeadCharMessage(aMessage));
   }
   bool IsDeadCharMessage(const MSG& aMSG) const
   {
     return IsDeadCharMessage(aMSG.message);
   }
   bool IsDeadCharMessage(UINT aMessage) const
   {
     return (aMessage == WM_DEADCHAR || aMessage == WM_SYSDEADCHAR);
@@ -461,23 +460,33 @@ private:
   {
     return IsSysCharMessage(aMSG.message);
   }
   bool IsSysCharMessage(UINT aMessage) const
   {
     return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR);
   }
   bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const;
-  bool IsFollowedByNonControlCharMessage() const;
+  bool IsFollowedByPrintableCharMessage() const;
   bool IsFollowedByDeadCharMessage() const;
   bool IsKeyMessageOnPlugin() const
   {
     return (mMsg.message == MOZ_WM_KEYDOWN ||
             mMsg.message == MOZ_WM_KEYUP);
   }
+  bool IsPrintableCharMessage(const MSG& aMSG) const
+  {
+    return aMSG.message == WM_CHAR &&
+           !IsControlChar(static_cast<char16_t>(aMSG.wParam));
+  }
+  bool IsControlCharMessage(const MSG& aMSG) const
+  {
+    return IsCharMessage(aMSG.message) &&
+           IsControlChar(static_cast<char16_t>(aMSG.wParam));
+  }
 
   /**
    * IsReservedBySystem() returns true if the key combination is reserved by
    * the system.  Even if it's consumed by web apps, the message should be
    * sent to next wndproc.
    */
   bool IsReservedBySystem() const;
 
@@ -573,16 +582,24 @@ private:
    * IsFocusedWindowChanged() returns true if focused window is changed
    * after the instance is created.
    */
   bool IsFocusedWindowChanged() const
   {
     return mFocusedWndBeforeDispatch != ::GetFocus();
   }
 
+  /**
+   * Handles WM_CHAR message or WM_SYSCHAR message.  The instance must be
+   * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them.
+   * Returns true if dispatched keypress event is consumed.  Otherwise, false.
+   */
+  bool HandleCharMessage(const MSG& aCharMsg,
+                         bool* aEventDispatched = nullptr) const;
+
   // Calls of PeekMessage() from NativeKey might cause nested message handling
   // due to (perhaps) odd API hook.  NativeKey should do nothing if given
   // message is tried to be retrieved by another instance.
 
   /**
    * sLatestInstacne is a pointer to the newest instance of NativeKey which is
    * handling a key or char message(s).
    */
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -6033,18 +6033,17 @@ LRESULT nsWindow::ProcessCharMessage(con
   if (IMEHandler::IsComposingOn(this)) {
     IMEHandler::NotifyIME(this, REQUEST_TO_COMMIT_COMPOSITION);
   }
   // These must be checked here too as a lone WM_CHAR could be received
   // if a child window didn't handle it (for example Alt+Space in a content
   // window)
   ModifierKeyState modKeyState;
   NativeKey nativeKey(this, aMsg, modKeyState);
-  return static_cast<LRESULT>(nativeKey.HandleCharMessage(aMsg,
-                                                          aEventDispatched));
+  return static_cast<LRESULT>(nativeKey.HandleCharMessage(aEventDispatched));
 }
 
 LRESULT nsWindow::ProcessKeyUpMessage(const MSG &aMsg, bool *aEventDispatched)
 {
   ModifierKeyState modKeyState;
   NativeKey nativeKey(this, aMsg, modKeyState);
   return static_cast<LRESULT>(nativeKey.HandleKeyUpMessage(aEventDispatched));
 }